summaryrefslogtreecommitdiff
path: root/src/debug/di
diff options
context:
space:
mode:
Diffstat (limited to 'src/debug/di')
-rw-r--r--src/debug/di/.gitmirror1
-rw-r--r--src/debug/di/CMakeLists.txt77
-rw-r--r--src/debug/di/DI.props86
-rw-r--r--src/debug/di/ICorDebugValueTypes.vsdbin0 -> 96768 bytes
-rw-r--r--src/debug/di/amd64/.gitmirror1
-rw-r--r--src/debug/di/amd64/FloatConversion.asm26
-rw-r--r--src/debug/di/amd64/cordbregisterset.cpp254
-rw-r--r--src/debug/di/amd64/floatconversion.S11
-rw-r--r--src/debug/di/amd64/primitives.cpp12
-rw-r--r--src/debug/di/arm/.gitmirror1
-rw-r--r--src/debug/di/arm/cordbregisterset.cpp150
-rw-r--r--src/debug/di/arm/primitives.cpp7
-rw-r--r--src/debug/di/arm64/.gitmirror1
-rw-r--r--src/debug/di/arm64/cordbregisterset.cpp145
-rw-r--r--src/debug/di/arm64/floatconversion.asm22
-rw-r--r--src/debug/di/arm64/primitives.cpp7
-rw-r--r--src/debug/di/breakpoint.cpp722
-rw-r--r--src/debug/di/classfactory.h82
-rw-r--r--src/debug/di/cordb.cpp572
-rw-r--r--src/debug/di/dbgtransportmanager.cpp231
-rw-r--r--src/debug/di/dbgtransportmanager.h87
-rw-r--r--src/debug/di/dbgtransportpipeline.cpp457
-rw-r--r--src/debug/di/dbi.sln20
-rw-r--r--src/debug/di/dbi.vcxproj143
-rw-r--r--src/debug/di/dirs.proj18
-rw-r--r--src/debug/di/divalue.cpp4564
-rw-r--r--src/debug/di/eventchannel.h264
-rw-r--r--src/debug/di/eventredirectionpipeline.cpp350
-rw-r--r--src/debug/di/eventredirectionpipeline.h145
-rw-r--r--src/debug/di/hash.cpp638
-rw-r--r--src/debug/di/helpers.h210
-rw-r--r--src/debug/di/i386/.gitmirror1
-rw-r--r--src/debug/di/i386/cordbregisterset.cpp222
-rw-r--r--src/debug/di/i386/primitives.cpp10
-rw-r--r--src/debug/di/localeventchannel.cpp499
-rw-r--r--src/debug/di/module.cpp4925
-rw-r--r--src/debug/di/nativepipeline.cpp64
-rw-r--r--src/debug/di/nativepipeline.h228
-rw-r--r--src/debug/di/platformspecific.cpp40
-rw-r--r--src/debug/di/process.cpp15235
-rw-r--r--src/debug/di/publish.cpp1282
-rw-r--r--src/debug/di/remoteeventchannel.cpp342
-rw-r--r--src/debug/di/rsappdomain.cpp1235
-rw-r--r--src/debug/di/rsassembly.cpp320
-rw-r--r--src/debug/di/rsclass.cpp1194
-rw-r--r--src/debug/di/rsenumerator.hpp361
-rw-r--r--src/debug/di/rsfunction.cpp1191
-rw-r--r--src/debug/di/rsmain.cpp2536
-rw-r--r--src/debug/di/rsmda.cpp243
-rw-r--r--src/debug/di/rspriv.h11756
-rw-r--r--src/debug/di/rspriv.inl723
-rw-r--r--src/debug/di/rsregsetcommon.cpp248
-rw-r--r--src/debug/di/rsstackwalk.cpp822
-rw-r--r--src/debug/di/rsthread.cpp11006
-rw-r--r--src/debug/di/rstype.cpp2815
-rw-r--r--src/debug/di/shared.cpp16
-rw-r--r--src/debug/di/shimcallback.cpp1317
-rw-r--r--src/debug/di/shimdatatarget.cpp92
-rw-r--r--src/debug/di/shimdatatarget.h133
-rw-r--r--src/debug/di/shimevents.cpp292
-rw-r--r--src/debug/di/shimlocaldatatarget.cpp471
-rw-r--r--src/debug/di/shimpriv.h1056
-rw-r--r--src/debug/di/shimprocess.cpp1904
-rw-r--r--src/debug/di/shimremotedatatarget.cpp349
-rw-r--r--src/debug/di/shimstackwalk.cpp2264
-rw-r--r--src/debug/di/stdafx.cpp12
-rw-r--r--src/debug/di/stdafx.h63
-rw-r--r--src/debug/di/symbolinfo.cpp1501
-rw-r--r--src/debug/di/symbolinfo.h816
-rw-r--r--src/debug/di/valuehome.cpp1062
-rw-r--r--src/debug/di/windowspipeline.cpp419
71 files changed, 78369 insertions, 0 deletions
diff --git a/src/debug/di/.gitmirror b/src/debug/di/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/di/.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/debug/di/CMakeLists.txt b/src/debug/di/CMakeLists.txt
new file mode 100644
index 0000000000..abede90d2e
--- /dev/null
+++ b/src/debug/di/CMakeLists.txt
@@ -0,0 +1,77 @@
+add_definitions(-DFEATURE_METADATA_CUSTOM_DATA_SOURCE -DFEATURE_METADATA_DEBUGGEE_DATA_SOURCE -DFEATURE_NO_HOST -DFEATURE_METADATA_LOAD_TRUSTED_IMAGES)
+
+set(CORDBDI_SOURCES
+ shimprocess.cpp
+ shimcallback.cpp
+ shimevents.cpp
+ shimdatatarget.cpp
+ shimstackwalk.cpp
+ breakpoint.cpp
+ cordb.cpp
+ divalue.cpp
+ dbgtransportmanager.cpp
+ hash.cpp
+ module.cpp
+ nativepipeline.cpp
+ platformspecific.cpp
+ process.cpp
+ rsappdomain.cpp
+ rsassembly.cpp
+ rsclass.cpp
+ rsfunction.cpp
+ rsmain.cpp
+ rsmda.cpp
+ rsregsetcommon.cpp
+ rsstackwalk.cpp
+ rsthread.cpp
+ rstype.cpp
+ shared.cpp
+ symbolinfo.cpp
+ valuehome.cpp
+)
+
+if(WIN32)
+ #use static crt
+ add_definitions(-MT)
+
+ if (CLR_CMAKE_TARGET_ARCH_AMD64 OR CLR_CMAKE_TARGET_ARCH_ARM64)
+ set(CORDBDI_SOURCES_ASM_FILE ${ARCH_SOURCES_DIR}/floatconversion.asm)
+ endif()
+ if (CLR_CMAKE_TARGET_ARCH_AMD64)
+ set(CORDBDI_SOURCES
+ ${CORDBDI_SOURCES}
+ ${CORDBDI_SOURCES_ASM_FILE}
+ )
+ elseif (CLR_CMAKE_TARGET_ARCH_ARM64 AND NOT DEFINED CLR_CROSS_COMPONENTS_BUILD)
+ convert_to_absolute_path(CORDBDI_SOURCES_ASM_FILE ${CORDBDI_SOURCES_ASM_FILE})
+ get_compile_definitions(ASM_DEFINITIONS)
+ set(ASM_OPTIONS /c /Zi /W3 /errorReport:prompt)
+ # asm files require preprocessing using cl.exe on arm64
+ get_filename_component(name ${CORDBDI_SOURCES_ASM_FILE} NAME_WE)
+ set(ASM_PREPROCESSED_FILE ${CMAKE_CURRENT_BINARY_DIR}/${name}.asm)
+ preprocess_def_file(${CORDBDI_SOURCES_ASM_FILE} ${ASM_PREPROCESSED_FILE})
+ set(CORDBDI_SOURCES_WKS_PREPROCESSED_ASM ${ASM_PREPROCESSED_FILE})
+
+ set_property(SOURCE ${CORDBDI_SOURCES_WKS_PREPROCESSED_ASM} PROPERTY COMPILE_DEFINITIONS ${ASM_DEFINITIONS})
+ set_property(SOURCE ${CORDBDI_SOURCES_WKS_PREPROCESSED_ASM} PROPERTY COMPILE_DEFINITIONS ${ASM_OPTIONS})
+ set(CORDBDI_SOURCES
+ ${CORDBDI_SOURCES}
+ ${CORDBDI_SOURCES_WKS_PREPROCESSED_ASM}
+ )
+ endif()
+elseif(CLR_CMAKE_PLATFORM_UNIX)
+ add_compile_options(-fPIC)
+
+ if(CLR_CMAKE_TARGET_ARCH_AMD64)
+ set(CORDBDI_SOURCES
+ ${CORDBDI_SOURCES}
+ ${ARCH_SOURCES_DIR}/floatconversion.S
+ )
+ endif()
+
+endif(WIN32)
+
+add_precompiled_header(stdafx.h stdafx.cpp CORDBDI_SOURCES)
+
+
+add_library_clr(cordbdi STATIC ${CORDBDI_SOURCES})
diff --git a/src/debug/di/DI.props b/src/debug/di/DI.props
new file mode 100644
index 0000000000..1d7336dab0
--- /dev/null
+++ b/src/debug/di/DI.props
@@ -0,0 +1,86 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <!--*****************************************************-->
+ <!--This MSBuild project file was automatically generated-->
+ <!--from the original SOURCES/DIRS file by the KBC tool.-->
+ <!--*****************************************************-->
+ <!-- These features need to be enabled for each build artifact that wants to use them, they aren't controlled at the SKU level-->
+ <PropertyGroup>
+ <FeatureMetadataCustomDataSource>true</FeatureMetadataCustomDataSource>
+ <FeatureMetadataDebuggeeDataSource>true</FeatureMetadataDebuggeeDataSource>
+ </PropertyGroup>
+ <!--Import the settings-->
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" />
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\src\Debug\XPlatCommon.props"/>
+ <!--Leaf project Properties-->
+ <PropertyGroup>
+ <UserIncludes>
+ $(UserIncludes);
+ ..;
+ ..\..\inc;
+ ..\..\inc\dump;
+ ..\..\..\vm;
+ $(VCToolsIncPath);
+ </UserIncludes>
+ <ClAdditionalOptions>$(ClAdditionalOptions) -DUNICODE -D_UNICODE -DFEATURE_NO_HOST -DFEATURE_METADATA_LOAD_TRUSTED_IMAGES</ClAdditionalOptions>
+ <OutputPath>$(ClrLibDest)</OutputPath>
+ <TargetType>LIBRARY</TargetType>
+ <PCHHeader Condition="'$(CCOVER)' == ''">stdafx.h</PCHHeader>
+ <EnableCxxPCHHeaders Condition="'$(CCOVER)' == ''">true</EnableCxxPCHHeaders>
+ <!--PCH: Both precompiled header and cpp are on the same ..\ path this is likely to be wrong.-->
+ <PCHCompile Condition="'$(CCOVER)' == ''">..\stdafx.cpp</PCHCompile>
+ <LinkNoLibraries>true</LinkNoLibraries>
+ <LinkUseCMT>true</LinkUseCMT>
+ <UseMsvcrt />
+ </PropertyGroup>
+ <!--Leaf Project Items-->
+ <ItemGroup>
+ <ProjectReference Condition="'$(XPlatHostLibBuildDir)'=='HostLocal'" Include="$(ClrSrcDirectory)inc\corguids.nativeproj" />
+ <ProjectReference Condition="'$(XPlatHostLibBuildDir)'=='HostWinx86'" Include="$(ClrSrcDirectory)incx86\corguids.nativeproj" />
+ <ProjectReference Condition="'$(XPlatHostLibBuildDir)'=='HostWinAMD64'" Include="$(ClrSrcDirectory)incamd64\corguids.nativeproj" />
+ </ItemGroup>
+ <ItemGroup>
+ <SourcesPublish Include="..\publish.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <SourcesShim Include="..\ShimProcess.cpp" />
+ <SourcesShim Include="..\ShimCallback.cpp" />
+ <SourcesShim Include="..\ShimEvents.cpp" />
+ <SourcesShim Include="..\ShimDataTarget.cpp" />
+ <SourcesShim Include="..\ShimStackWalk.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <SourcesRightside Include="..\breakpoint.cpp" />
+ <SourcesRightside Include="..\cordb.cpp" />
+ <SourcesRightside Include="..\DbgTransportManager.cpp" />
+ <SourcesRightside Include="..\DIValue.cpp" />
+ <SourcesRightside Include="..\hash.cpp" />
+ <SourcesRightside Include="..\module.cpp" />
+ <SourcesRightside Include="..\NativePipeline.cpp" />
+ <SourcesRightside Include="..\PlatformSpecific.cpp" />
+ <SourcesRightside Include="..\process.cpp" />
+ <SourcesRightside Include="..\RsAppDomain.cpp" />
+ <SourcesRightside Include="..\RsAssembly.cpp" />
+ <SourcesRightside Include="..\RsClass.cpp" />
+ <SourcesRightside Include="..\RsFunction.cpp" />
+ <SourcesRightside Include="..\RsMain.cpp" />
+ <SourcesRightside Include="..\RsMda.cpp" />
+ <SourcesRightside Include="..\RsRegSetCommon.cpp" />
+ <SourcesRightside Include="..\RsStackWalk.cpp" />
+ <SourcesRightside Include="..\RsThread.cpp" />
+ <SourcesRightside Include="..\RsType.cpp" />
+ <SourcesRightside Include="..\shared.cpp" />
+ <SourcesRightside Include="..\symbolinfo.cpp" />
+ <SourcesRightside Include="..\ValueHome.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <CppCompile Include="@(SourcesPublish)" />
+ <CppCompile Include="@(SourcesShim)" />
+ <CppCompile Include="@(SourcesRightside)" />
+ <AssembleAmd64 Condition="'$(BuildArchitecture)' == 'amd64' and '$(CrossTargetArchitecture)' != 'arm64'" Include="..\amd64\floatconversion.asm" />
+ </ItemGroup>
+ <ItemGroup Condition="'$(BuildArchitecture)' == 'arm64'">
+ <PreprocessAssembleArm Include="..\arm64\floatconversion.asm" />
+ <AssembleArm64 Include="$(IntermediateOutputDirectory)\floatconversion.i" />
+ </ItemGroup>
+ <!--Import the targets-->
+</Project>
diff --git a/src/debug/di/ICorDebugValueTypes.vsd b/src/debug/di/ICorDebugValueTypes.vsd
new file mode 100644
index 0000000000..b16ba39fe4
--- /dev/null
+++ b/src/debug/di/ICorDebugValueTypes.vsd
Binary files differ
diff --git a/src/debug/di/amd64/.gitmirror b/src/debug/di/amd64/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/di/amd64/.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/debug/di/amd64/FloatConversion.asm b/src/debug/di/amd64/FloatConversion.asm
new file mode 100644
index 0000000000..0de4a89bb4
--- /dev/null
+++ b/src/debug/di/amd64/FloatConversion.asm
@@ -0,0 +1,26 @@
+; 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.
+
+;// ==++==
+;//
+
+;//
+;// ==--==
+
+;// @dbgtodo Microsoft inspection: remove the implementation from vm\amd64\getstate.asm when we remove the
+;// ipc event to load the float state.
+
+;// this is the same implementation as the function of the same name in vm\amd64\getstate.asm and they must
+;// remain in sync.
+;// Arguments
+;// input: (in rcx) the M128 value to be converted to a double
+;// output: the double corresponding to the M128 input value
+
+_TEXT segment para 'CODE'
+FPFillR8 proc
+ movdqa xmm0, [rcx]
+ ret
+FPFillR8 endp
+
+END
diff --git a/src/debug/di/amd64/cordbregisterset.cpp b/src/debug/di/amd64/cordbregisterset.cpp
new file mode 100644
index 0000000000..8df779e758
--- /dev/null
+++ b/src/debug/di/amd64/cordbregisterset.cpp
@@ -0,0 +1,254 @@
+// 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.
+//*****************************************************************************
+// File: CordbRegisterSet.cpp
+//
+
+//
+//*****************************************************************************
+#include "primitives.h"
+
+
+HRESULT CordbRegisterSet::GetRegistersAvailable(ULONG64* pAvailable)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pAvailable, ULONG64*);
+
+ (*pAvailable) = SETBITULONG64( REGISTER_INSTRUCTION_POINTER )
+ | SETBITULONG64( REGISTER_STACK_POINTER );
+
+ if (!m_quickUnwind || m_active)
+ (*pAvailable) |= SETBITULONG64( REGISTER_AMD64_RBP )
+ | SETBITULONG64( REGISTER_AMD64_RAX )
+ | SETBITULONG64( REGISTER_AMD64_RCX )
+ | SETBITULONG64( REGISTER_AMD64_RDX )
+ | SETBITULONG64( REGISTER_AMD64_RBX )
+ | SETBITULONG64( REGISTER_AMD64_RSI )
+ | SETBITULONG64( REGISTER_AMD64_RDI )
+ | SETBITULONG64( REGISTER_AMD64_R8 )
+ | SETBITULONG64( REGISTER_AMD64_R9 )
+ | SETBITULONG64( REGISTER_AMD64_R10 )
+ | SETBITULONG64( REGISTER_AMD64_R11 )
+ | SETBITULONG64( REGISTER_AMD64_R12 )
+ | SETBITULONG64( REGISTER_AMD64_R13 )
+ | SETBITULONG64( REGISTER_AMD64_R14 )
+ | SETBITULONG64( REGISTER_AMD64_R15 );
+
+ if (m_active)
+ (*pAvailable) |= SETBITULONG64( REGISTER_AMD64_XMM0 )
+ | SETBITULONG64( REGISTER_AMD64_XMM1 )
+ | SETBITULONG64( REGISTER_AMD64_XMM2 )
+ | SETBITULONG64( REGISTER_AMD64_XMM3 )
+ | SETBITULONG64( REGISTER_AMD64_XMM4 )
+ | SETBITULONG64( REGISTER_AMD64_XMM5 )
+ | SETBITULONG64( REGISTER_AMD64_XMM6 )
+ | SETBITULONG64( REGISTER_AMD64_XMM7 )
+ | SETBITULONG64( REGISTER_AMD64_XMM8 )
+ | SETBITULONG64( REGISTER_AMD64_XMM9 )
+ | SETBITULONG64( REGISTER_AMD64_XMM10 )
+ | SETBITULONG64( REGISTER_AMD64_XMM11 )
+ | SETBITULONG64( REGISTER_AMD64_XMM12 )
+ | SETBITULONG64( REGISTER_AMD64_XMM13 )
+ | SETBITULONG64( REGISTER_AMD64_XMM14 )
+ | SETBITULONG64( REGISTER_AMD64_XMM15 );
+
+ return S_OK;
+}
+
+HRESULT CordbRegisterSet::GetRegisters(ULONG64 mask, ULONG32 regCount,
+ CORDB_REGISTER regBuffer[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ UINT iRegister = 0;
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(regBuffer, CORDB_REGISTER, regCount, true, true);
+
+ if ( mask & ( SETBITULONG64( REGISTER_AMD64_XMM0 )
+ | SETBITULONG64( REGISTER_AMD64_XMM1 )
+ | SETBITULONG64( REGISTER_AMD64_XMM2 )
+ | SETBITULONG64( REGISTER_AMD64_XMM3 )
+ | SETBITULONG64( REGISTER_AMD64_XMM4 )
+ | SETBITULONG64( REGISTER_AMD64_XMM5 )
+ | SETBITULONG64( REGISTER_AMD64_XMM6 )
+ | SETBITULONG64( REGISTER_AMD64_XMM7 )
+ | SETBITULONG64( REGISTER_AMD64_XMM8 )
+ | SETBITULONG64( REGISTER_AMD64_XMM9 )
+ | SETBITULONG64( REGISTER_AMD64_XMM10 )
+ | SETBITULONG64( REGISTER_AMD64_XMM11 )
+ | SETBITULONG64( REGISTER_AMD64_XMM12 )
+ | SETBITULONG64( REGISTER_AMD64_XMM13 )
+ | SETBITULONG64( REGISTER_AMD64_XMM14 )
+ | SETBITULONG64( REGISTER_AMD64_XMM15 ) ) )
+ {
+ HRESULT hr = S_OK;
+
+ if (!m_active)
+ return E_INVALIDARG;
+
+ if (!m_thread->m_fFloatStateValid)
+ {
+ EX_TRY
+ {
+ m_thread->LoadFloatState();
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if ( !SUCCEEDED(hr) )
+ {
+ return hr;
+ }
+ LOG( ( LF_CORDB, LL_INFO1000, "CRS::GR: Loaded float state\n" ) );
+ }
+ }
+
+ // Make sure that the registers are really available
+ if ( mask & ( SETBITULONG64( REGISTER_AMD64_RBP )
+ | SETBITULONG64( REGISTER_AMD64_RAX )
+ | SETBITULONG64( REGISTER_AMD64_RCX )
+ | SETBITULONG64( REGISTER_AMD64_RDX )
+ | SETBITULONG64( REGISTER_AMD64_RBX )
+ | SETBITULONG64( REGISTER_AMD64_RSI )
+ | SETBITULONG64( REGISTER_AMD64_RDI )
+ | SETBITULONG64( REGISTER_AMD64_R8 )
+ | SETBITULONG64( REGISTER_AMD64_R9 )
+ | SETBITULONG64( REGISTER_AMD64_R10 )
+ | SETBITULONG64( REGISTER_AMD64_R11 )
+ | SETBITULONG64( REGISTER_AMD64_R12 )
+ | SETBITULONG64( REGISTER_AMD64_R13 )
+ | SETBITULONG64( REGISTER_AMD64_R14 )
+ | SETBITULONG64( REGISTER_AMD64_R15 ) ) )
+ {
+ if (!m_active && m_quickUnwind)
+ return E_INVALIDARG;
+ }
+
+ for ( int i = REGISTER_INSTRUCTION_POINTER
+ ; i<=REGISTER_AMD64_XMM15 && iRegister < regCount
+ ; i++)
+ {
+ if( mask & SETBITULONG64(i) )
+ {
+ switch( i )
+ {
+ case REGISTER_INSTRUCTION_POINTER:
+ regBuffer[iRegister++] = m_rd->PC; break;
+ case REGISTER_STACK_POINTER:
+ regBuffer[iRegister++] = m_rd->SP; break;
+ case REGISTER_AMD64_RBP:
+ regBuffer[iRegister++] = m_rd->Rbp; break;
+ case REGISTER_AMD64_RAX:
+ regBuffer[iRegister++] = m_rd->Rax; break;
+ case REGISTER_AMD64_RBX:
+ regBuffer[iRegister++] = m_rd->Rbx; break;
+ case REGISTER_AMD64_RCX:
+ regBuffer[iRegister++] = m_rd->Rcx; break;
+ case REGISTER_AMD64_RDX:
+ regBuffer[iRegister++] = m_rd->Rdx; break;
+ case REGISTER_AMD64_RSI:
+ regBuffer[iRegister++] = m_rd->Rsi; break;
+ case REGISTER_AMD64_RDI:
+ regBuffer[iRegister++] = m_rd->Rdi; break;
+ case REGISTER_AMD64_R8:
+ regBuffer[iRegister++] = m_rd->R8; break;
+ case REGISTER_AMD64_R9:
+ regBuffer[iRegister++] = m_rd->R9; break;
+ case REGISTER_AMD64_R10:
+ regBuffer[iRegister++] = m_rd->R10; break;
+ case REGISTER_AMD64_R11:
+ regBuffer[iRegister++] = m_rd->R11; break;
+ case REGISTER_AMD64_R12:
+ regBuffer[iRegister++] = m_rd->R12; break;
+ case REGISTER_AMD64_R13:
+ regBuffer[iRegister++] = m_rd->R13; break;
+ case REGISTER_AMD64_R14:
+ regBuffer[iRegister++] = m_rd->R14; break;
+ case REGISTER_AMD64_R15:
+ regBuffer[iRegister++] = m_rd->R15; break;
+
+ case REGISTER_AMD64_XMM0:
+ case REGISTER_AMD64_XMM1:
+ case REGISTER_AMD64_XMM2:
+ case REGISTER_AMD64_XMM3:
+ case REGISTER_AMD64_XMM4:
+ case REGISTER_AMD64_XMM5:
+ case REGISTER_AMD64_XMM6:
+ case REGISTER_AMD64_XMM7:
+ case REGISTER_AMD64_XMM8:
+ case REGISTER_AMD64_XMM9:
+ case REGISTER_AMD64_XMM10:
+ case REGISTER_AMD64_XMM11:
+ case REGISTER_AMD64_XMM12:
+ case REGISTER_AMD64_XMM13:
+ case REGISTER_AMD64_XMM14:
+ case REGISTER_AMD64_XMM15:
+ regBuffer[iRegister++] = *(CORDB_REGISTER*)
+ &(m_thread->m_floatValues[(i - REGISTER_AMD64_XMM0)]);
+ break;
+ }
+ }
+ }
+
+ _ASSERTE( iRegister <= regCount );
+ return S_OK;
+}
+
+
+HRESULT CordbRegisterSet::GetRegistersAvailable(ULONG32 regCount,
+ BYTE pAvailable[])
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pAvailable, CORDB_REGISTER, regCount, true, true);
+
+ // Defer to adapter for v1.0 interface
+ return GetRegistersAvailableAdapter(regCount, pAvailable);
+}
+
+
+HRESULT CordbRegisterSet::GetRegisters(ULONG32 maskCount, BYTE mask[],
+ ULONG32 regCount, CORDB_REGISTER regBuffer[])
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(regBuffer, CORDB_REGISTER, regCount, true, true);
+
+ // Defer to adapter for v1.0 interface
+ return GetRegistersAdapter(maskCount, mask, regCount, regBuffer);
+}
+
+
+// This is just a convenience function to convert a regdisplay into a Context.
+// Since a context has more info than a regdisplay, the conversion isn't perfect
+// and the context can't be fully accurate.
+void CordbRegisterSet::InternalCopyRDToContext(DT_CONTEXT *pInputContext)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+ _ASSERTE(pInputContext);
+
+ if((pInputContext->ContextFlags & DT_CONTEXT_INTEGER)==DT_CONTEXT_INTEGER)
+ {
+ pInputContext->Rax = m_rd->Rax;
+ pInputContext->Rbx = m_rd->Rbx;
+ pInputContext->Rcx = m_rd->Rcx;
+ pInputContext->Rdx = m_rd->Rdx;
+ pInputContext->Rbp = m_rd->Rbp;
+ pInputContext->Rsi = m_rd->Rsi;
+ pInputContext->Rdi = m_rd->Rdi;
+ pInputContext->R8 = m_rd->R8;
+ pInputContext->R9 = m_rd->R9;
+ pInputContext->R10 = m_rd->R10;
+ pInputContext->R11 = m_rd->R11;
+ pInputContext->R12 = m_rd->R12;
+ pInputContext->R13 = m_rd->R13;
+ pInputContext->R14 = m_rd->R14;
+ pInputContext->R15 = m_rd->R15;
+ }
+
+
+ if((pInputContext->ContextFlags & DT_CONTEXT_CONTROL)==DT_CONTEXT_CONTROL)
+ {
+ pInputContext->Rip = m_rd->PC;
+ pInputContext->Rsp = m_rd->SP;
+ }
+}
diff --git a/src/debug/di/amd64/floatconversion.S b/src/debug/di/amd64/floatconversion.S
new file mode 100644
index 0000000000..70698d26cc
--- /dev/null
+++ b/src/debug/di/amd64/floatconversion.S
@@ -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.
+
+.intel_syntax noprefix
+#include <unixasmmacros.inc>
+
+LEAF_ENTRY FPFillR8, _TEXT
+ movdqa xmm0, xmmword ptr [rdi]
+ ret
+LEAF_END FPFillR8, _TEXT
diff --git a/src/debug/di/amd64/primitives.cpp b/src/debug/di/amd64/primitives.cpp
new file mode 100644
index 0000000000..33717cf1d0
--- /dev/null
+++ b/src/debug/di/amd64/primitives.cpp
@@ -0,0 +1,12 @@
+// 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 "../../shared/amd64/primitives.cpp"
+
+
diff --git a/src/debug/di/arm/.gitmirror b/src/debug/di/arm/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/di/arm/.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/debug/di/arm/cordbregisterset.cpp b/src/debug/di/arm/cordbregisterset.cpp
new file mode 100644
index 0000000000..092fd0c6b9
--- /dev/null
+++ b/src/debug/di/arm/cordbregisterset.cpp
@@ -0,0 +1,150 @@
+// 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.
+//*****************************************************************************
+// File: CordbRegisterSet.cpp
+//
+
+//
+//*****************************************************************************
+#include "primitives.h"
+
+HRESULT CordbRegisterSet::GetRegistersAvailable(ULONG64 *pAvailable)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pAvailable, ULONG64 *);
+
+ *pAvailable = SETBITULONG64(REGISTER_INSTRUCTION_POINTER)
+ | SETBITULONG64(REGISTER_STACK_POINTER)
+ | SETBITULONG64(REGISTER_ARM_R0)
+ | SETBITULONG64(REGISTER_ARM_R1)
+ | SETBITULONG64(REGISTER_ARM_R2)
+ | SETBITULONG64(REGISTER_ARM_R3)
+ | SETBITULONG64(REGISTER_ARM_R4)
+ | SETBITULONG64(REGISTER_ARM_R5)
+ | SETBITULONG64(REGISTER_ARM_R6)
+ | SETBITULONG64(REGISTER_ARM_R7)
+ | SETBITULONG64(REGISTER_ARM_R8)
+ | SETBITULONG64(REGISTER_ARM_R9)
+ | SETBITULONG64(REGISTER_ARM_R10)
+ | SETBITULONG64(REGISTER_ARM_R11)
+ | SETBITULONG64(REGISTER_ARM_R12)
+ | SETBITULONG64(REGISTER_ARM_LR);
+
+ return S_OK;
+}
+
+HRESULT CordbRegisterSet::GetRegisters(ULONG64 mask, ULONG32 regCount, CORDB_REGISTER regBuffer[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ UINT iRegister = 0;
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(regBuffer, CORDB_REGISTER, regCount, true, true);
+
+ // @ARMTODO: floating point support
+
+ for (int i = REGISTER_INSTRUCTION_POINTER;
+ i <= REGISTER_ARM_LR && iRegister < regCount;
+ i++)
+ {
+ if (mask & SETBITULONG64(i))
+ {
+ switch (i)
+ {
+ case REGISTER_INSTRUCTION_POINTER:
+ regBuffer[iRegister++] = m_rd->PC; break;
+ case REGISTER_STACK_POINTER:
+ regBuffer[iRegister++] = m_rd->SP; break;
+ case REGISTER_ARM_R0:
+ regBuffer[iRegister++] = m_rd->R0; break;
+ case REGISTER_ARM_R1:
+ regBuffer[iRegister++] = m_rd->R1; break;
+ case REGISTER_ARM_R2:
+ regBuffer[iRegister++] = m_rd->R2; break;
+ case REGISTER_ARM_R3:
+ regBuffer[iRegister++] = m_rd->R3; break;
+ case REGISTER_ARM_R4:
+ regBuffer[iRegister++] = m_rd->R4; break;
+ case REGISTER_ARM_R5:
+ regBuffer[iRegister++] = m_rd->R5; break;
+ case REGISTER_ARM_R6:
+ regBuffer[iRegister++] = m_rd->R6; break;
+ case REGISTER_ARM_R7:
+ regBuffer[iRegister++] = m_rd->R7; break;
+ case REGISTER_ARM_R8:
+ regBuffer[iRegister++] = m_rd->R8; break;
+ case REGISTER_ARM_R9:
+ regBuffer[iRegister++] = m_rd->R9; break;
+ case REGISTER_ARM_R10:
+ regBuffer[iRegister++] = m_rd->R10; break;
+ case REGISTER_ARM_R11:
+ regBuffer[iRegister++] = m_rd->R11; break;
+ case REGISTER_ARM_R12:
+ regBuffer[iRegister++] = m_rd->R12; break;
+ case REGISTER_ARM_LR:
+ regBuffer[iRegister++] = m_rd->LR; break;
+ }
+ }
+ }
+
+ _ASSERTE (iRegister <= regCount);
+ return S_OK;
+}
+
+HRESULT CordbRegisterSet::GetRegistersAvailable(ULONG32 regCount,
+ BYTE pAvailable[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pAvailable, CORDB_REGISTER, regCount, true, true);
+
+ // Defer to adapter for v1.0 interface
+ return GetRegistersAvailableAdapter(regCount, pAvailable);
+}
+
+
+HRESULT CordbRegisterSet::GetRegisters(ULONG32 maskCount, BYTE mask[],
+ ULONG32 regCount, CORDB_REGISTER regBuffer[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(regBuffer, CORDB_REGISTER, regCount, true, true);
+
+ // Defer to adapter for v1.0 interface
+ return GetRegistersAdapter(maskCount, mask, regCount, regBuffer);
+}
+
+// This is just a convenience function to convert a regdisplay into a Context.
+// Since a context has more info than a regdisplay, the conversion isn't perfect
+// and the context can't be fully accurate.
+void CordbRegisterSet::InternalCopyRDToContext(DT_CONTEXT * pInputContext)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+ _ASSERTE(pInputContext);
+
+ if ((pInputContext->ContextFlags & DT_CONTEXT_INTEGER) == DT_CONTEXT_INTEGER)
+ {
+ pInputContext->R0 = m_rd->R0;
+ pInputContext->R1 = m_rd->R1;
+ pInputContext->R2 = m_rd->R2;
+ pInputContext->R3 = m_rd->R3;
+ pInputContext->R4 = m_rd->R4;
+ pInputContext->R5 = m_rd->R5;
+ pInputContext->R6 = m_rd->R6;
+ pInputContext->R7 = m_rd->R7;
+ pInputContext->R8 = m_rd->R8;
+ pInputContext->R9 = m_rd->R9;
+ pInputContext->R10 = m_rd->R10;
+ pInputContext->R11 = m_rd->R11;
+ }
+
+ if ((pInputContext->ContextFlags & DT_CONTEXT_CONTROL) == DT_CONTEXT_CONTROL)
+ {
+ pInputContext->Sp = m_rd->SP;
+ pInputContext->Lr = m_rd->LR;
+ pInputContext->Pc = m_rd->PC;
+ }
+}
diff --git a/src/debug/di/arm/primitives.cpp b/src/debug/di/arm/primitives.cpp
new file mode 100644
index 0000000000..52e3451cb1
--- /dev/null
+++ b/src/debug/di/arm/primitives.cpp
@@ -0,0 +1,7 @@
+// 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 "../../shared/arm/primitives.cpp"
diff --git a/src/debug/di/arm64/.gitmirror b/src/debug/di/arm64/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/di/arm64/.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/debug/di/arm64/cordbregisterset.cpp b/src/debug/di/arm64/cordbregisterset.cpp
new file mode 100644
index 0000000000..eab5ba403c
--- /dev/null
+++ b/src/debug/di/arm64/cordbregisterset.cpp
@@ -0,0 +1,145 @@
+// 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.
+//*****************************************************************************
+// File: CordbRegisterSet.cpp
+//
+
+//
+//*****************************************************************************
+#include "primitives.h"
+
+
+HRESULT CordbRegisterSet::GetRegistersAvailable(ULONG64* pAvailable)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pAvailable, ULONG64 *);
+
+ *pAvailable = SETBITULONG64(REGISTER_ARM64_PC)
+ | SETBITULONG64(REGISTER_ARM64_SP)
+ | SETBITULONG64(REGISTER_ARM64_X0)
+ | SETBITULONG64(REGISTER_ARM64_X1)
+ | SETBITULONG64(REGISTER_ARM64_X2)
+ | SETBITULONG64(REGISTER_ARM64_X3)
+ | SETBITULONG64(REGISTER_ARM64_X4)
+ | SETBITULONG64(REGISTER_ARM64_X5)
+ | SETBITULONG64(REGISTER_ARM64_X6)
+ | SETBITULONG64(REGISTER_ARM64_X7)
+ | SETBITULONG64(REGISTER_ARM64_X8)
+ | SETBITULONG64(REGISTER_ARM64_X9)
+ | SETBITULONG64(REGISTER_ARM64_X10)
+ | SETBITULONG64(REGISTER_ARM64_X11)
+ | SETBITULONG64(REGISTER_ARM64_X12)
+ | SETBITULONG64(REGISTER_ARM64_X13)
+ | SETBITULONG64(REGISTER_ARM64_X14)
+ | SETBITULONG64(REGISTER_ARM64_X15)
+ | SETBITULONG64(REGISTER_ARM64_X16)
+ | SETBITULONG64(REGISTER_ARM64_X17)
+ | SETBITULONG64(REGISTER_ARM64_X18)
+ | SETBITULONG64(REGISTER_ARM64_X19)
+ | SETBITULONG64(REGISTER_ARM64_X20)
+ | SETBITULONG64(REGISTER_ARM64_X21)
+ | SETBITULONG64(REGISTER_ARM64_X22)
+ | SETBITULONG64(REGISTER_ARM64_X23)
+ | SETBITULONG64(REGISTER_ARM64_X24)
+ | SETBITULONG64(REGISTER_ARM64_X25)
+ | SETBITULONG64(REGISTER_ARM64_X26)
+ | SETBITULONG64(REGISTER_ARM64_X27)
+ | SETBITULONG64(REGISTER_ARM64_X28)
+ | SETBITULONG64(REGISTER_ARM64_FP)
+ | SETBITULONG64(REGISTER_ARM64_LR);
+
+ return S_OK;
+}
+
+HRESULT CordbRegisterSet::GetRegisters(ULONG64 mask, ULONG32 regCount,
+ CORDB_REGISTER regBuffer[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ UINT iRegister = 0;
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(regBuffer, CORDB_REGISTER, regCount, true, true);
+
+ // @ARM64TODO: floating point support
+
+ for (int i = REGISTER_ARM64_PC;
+ i <= REGISTER_ARM64_LR && iRegister < regCount;
+ i++)
+ {
+ if (mask & SETBITULONG64(i))
+ {
+ if ((i >= REGISTER_ARM64_X0) && (i <= REGISTER_ARM64_X28))
+ {
+ regBuffer[iRegister++] = m_rd->X[i - REGISTER_ARM64_X0];
+ continue;
+ }
+
+ switch (i)
+ {
+ case REGISTER_ARM64_PC:
+ regBuffer[iRegister++] = m_rd->PC; break;
+ case REGISTER_ARM64_SP:
+ regBuffer[iRegister++] = m_rd->SP; break;
+ case REGISTER_ARM64_FP:
+ regBuffer[iRegister++] = m_rd->FP; break;
+ case REGISTER_ARM64_LR:
+ regBuffer[iRegister++] = m_rd->LR; break;
+ default:
+ _ASSERTE(false); break;
+ }
+ }
+ }
+
+ _ASSERTE (iRegister <= regCount);
+ return S_OK;
+}
+
+
+HRESULT CordbRegisterSet::GetRegistersAvailable(ULONG32 regCount,
+ BYTE pAvailable[])
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pAvailable, CORDB_REGISTER, regCount, true, true);
+
+ // Defer to adapter for v1.0 interface
+ return GetRegistersAvailableAdapter(regCount, pAvailable);
+}
+
+
+HRESULT CordbRegisterSet::GetRegisters(ULONG32 maskCount, BYTE mask[],
+ ULONG32 regCount, CORDB_REGISTER regBuffer[])
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(regBuffer, CORDB_REGISTER, regCount, true, true);
+
+ // Defer to adapter for v1.0 interface
+ return GetRegistersAdapter(maskCount, mask, regCount, regBuffer);
+}
+
+
+// This is just a convenience function to convert a regdisplay into a Context.
+// Since a context has more info than a regdisplay, the conversion isn't perfect
+// and the context can't be fully accurate.
+void CordbRegisterSet::InternalCopyRDToContext(DT_CONTEXT *pInputContext)
+{ INTERNAL_SYNC_API_ENTRY(GetProcess());
+ _ASSERTE(pInputContext);
+
+ if ((pInputContext->ContextFlags & DT_CONTEXT_INTEGER) == DT_CONTEXT_INTEGER)
+ {
+ for (int i = 0 ; i < 29 ; ++i)
+ {
+ pInputContext->X[i] = m_rd->X[i];
+ }
+ }
+
+ if ((pInputContext->ContextFlags & DT_CONTEXT_CONTROL) == DT_CONTEXT_CONTROL)
+ {
+ pInputContext->Sp = m_rd->SP;
+ pInputContext->Lr = m_rd->LR;
+ pInputContext->Pc = m_rd->PC;
+ pInputContext->Fp = m_rd->FP;
+ }
+}
diff --git a/src/debug/di/arm64/floatconversion.asm b/src/debug/di/arm64/floatconversion.asm
new file mode 100644
index 0000000000..e478fd10fd
--- /dev/null
+++ b/src/debug/di/arm64/floatconversion.asm
@@ -0,0 +1,22 @@
+; 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 "ksarm64.h"
+
+;; Arguments
+;; input: (in X0) the _NEON128 value to be converted to a double
+;; output: the double corresponding to the _NEON128 input value
+
+ LEAF_ENTRY FPFillR8
+ LDR Q0, [X0]
+ ret lr
+ LEAF_END
+
+;; Must be at very end of file
+ END
diff --git a/src/debug/di/arm64/primitives.cpp b/src/debug/di/arm64/primitives.cpp
new file mode 100644
index 0000000000..b802d8c468
--- /dev/null
+++ b/src/debug/di/arm64/primitives.cpp
@@ -0,0 +1,7 @@
+// 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 "../../shared/arm64/primitives.cpp"
diff --git a/src/debug/di/breakpoint.cpp b/src/debug/di/breakpoint.cpp
new file mode 100644
index 0000000000..1e381a5f83
--- /dev/null
+++ b/src/debug/di/breakpoint.cpp
@@ -0,0 +1,722 @@
+// 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.
+//*****************************************************************************
+// File: breakpoint.cpp
+//
+
+//
+//*****************************************************************************
+#include "stdafx.h"
+
+/* ------------------------------------------------------------------------- *
+ * Breakpoint class
+ * ------------------------------------------------------------------------- */
+
+CordbBreakpoint::CordbBreakpoint(CordbProcess * pProcess, CordbBreakpointType bpType)
+ : CordbBase(pProcess, 0, enumCordbBreakpoint),
+ m_active(false), m_type(bpType)
+{
+}
+
+// Neutered by CordbAppDomain
+void CordbBreakpoint::Neuter()
+{
+ m_pAppDomain = NULL; // clear ref
+ CordbBase::Neuter();
+}
+
+HRESULT CordbBreakpoint::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugBreakpoint)
+ {
+ *pInterface = static_cast<ICorDebugBreakpoint*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugBreakpoint*>(this));
+ }
+ else
+ {
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbBreakpoint::BaseIsActive(BOOL *pbActive)
+{
+ *pbActive = m_active ? TRUE : FALSE;
+
+ return S_OK;
+}
+
+/* ------------------------------------------------------------------------- *
+ * Function Breakpoint class
+ * ------------------------------------------------------------------------- */
+
+CordbFunctionBreakpoint::CordbFunctionBreakpoint(CordbCode *code,
+ SIZE_T offset)
+ : CordbBreakpoint(code->GetProcess(), CBT_FUNCTION),
+ m_code(code), m_offset(offset)
+{
+ // Remember the app domain we came from so that breakpoints can be
+ // deactivated from within the ExitAppdomain callback.
+ m_pAppDomain = m_code->GetAppDomain();
+ _ASSERTE(m_pAppDomain != NULL);
+}
+
+CordbFunctionBreakpoint::~CordbFunctionBreakpoint()
+{
+ // @todo- eventually get CordbFunctionBreakpoint rooted and enable this.
+ //_ASSERTE(this->IsNeutered());
+ //_ASSERTE(m_code == NULL);
+}
+
+void CordbFunctionBreakpoint::Neuter()
+{
+ Disconnect();
+ CordbBreakpoint::Neuter();
+}
+
+HRESULT CordbFunctionBreakpoint::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugFunctionBreakpoint)
+ {
+ *pInterface = static_cast<ICorDebugFunctionBreakpoint*>(this);
+ }
+ else
+ {
+ // Not looking for a function breakpoint? See if the base class handles
+ // this interface. (issue 143976)
+ return CordbBreakpoint::QueryInterface(id, pInterface);
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbFunctionBreakpoint::GetFunction(ICorDebugFunction **ppFunction)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppFunction, ICorDebugFunction **);
+
+ if (m_code == NULL)
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+ if (m_code->IsNeutered())
+ {
+ return CORDBG_E_CODE_NOT_AVAILABLE;
+ }
+
+ *ppFunction = static_cast<ICorDebugFunction *> (m_code->GetFunction());
+ (*ppFunction)->AddRef();
+
+ return S_OK;
+}
+
+// m_id is actually a LSPTR_BREAKPOINT. Get it as a type-safe member.
+LSPTR_BREAKPOINT CordbFunctionBreakpoint::GetLsPtrBP()
+{
+ LSPTR_BREAKPOINT p;
+ p.Set((void*) m_id);
+ return p;
+}
+
+HRESULT CordbFunctionBreakpoint::GetOffset(ULONG32 *pnOffset)
+{
+ //REVISIT_TODO: is this casting correct for ia64?
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pnOffset, SIZE_T *);
+
+ *pnOffset = (ULONG32)m_offset;
+
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Activates or removes a breakpoint
+//
+// Arguments:
+// fActivate - TRUE if to activate the breakpoint, else FALSE.
+//
+// Return Value:
+// S_OK if successful, else a specific error code detailing the type of failure.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbFunctionBreakpoint::Activate(BOOL fActivate)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ OK_IF_NEUTERED(this); // we'll check again later
+
+ if (fActivate == (m_active == true) )
+ {
+ return S_OK;
+ }
+
+ // For backwards compat w/ everett, we let the other error codes
+ // take precedence over neutering error codes.
+ if ((m_code == NULL) || this->IsNeutered())
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ HRESULT hr;
+ ATT_ALLOW_LIVE_DO_STOPGO(GetProcess());
+
+ // For legacy, check this error condition. We must do this under the stop-go lock to ensure
+ // that the m_code object was not deleted out from underneath us.
+ //
+ // 6/23/09 - This isn't just for legacy anymore, collectible types should be able to hit this
+ // by unloading the module containing the code this breakpoint is bound to.
+ if (m_code->IsNeutered())
+ {
+ return CORDBG_E_CODE_NOT_AVAILABLE;
+ }
+
+
+ //
+ // <REVISIT_TODO>@todo: when we implement module and value breakpoints, then
+ // we'll want to factor some of this code out.</REVISIT_TODO>
+ //
+ CordbProcess * pProcess = GetProcess();
+
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ pProcess->ClearPatchTable(); // if we add something, then the right side
+ // view of the patch table is no longer valid
+
+ DebuggerIPCEvent * pEvent = (DebuggerIPCEvent *) _alloca(CorDBIPC_BUFFER_SIZE);
+
+ CordbAppDomain * pAppDomain = GetAppDomain();
+ _ASSERTE (pAppDomain != NULL);
+
+ if (fActivate)
+ {
+ pProcess->InitIPCEvent(pEvent, DB_IPCE_BREAKPOINT_ADD, true, pAppDomain->GetADToken());
+
+ pEvent->BreakpointData.funcMetadataToken = m_code->GetMetadataToken();
+ pEvent->BreakpointData.vmDomainFile = m_code->GetModule()->GetRuntimeDomainFile();
+ pEvent->BreakpointData.encVersion = m_code->GetVersion();
+
+ BOOL fIsIL = m_code->IsIL();
+
+ pEvent->BreakpointData.isIL = fIsIL ? true : false;
+ pEvent->BreakpointData.offset = m_offset;
+ if (fIsIL)
+ {
+ pEvent->BreakpointData.nativeCodeMethodDescToken = pEvent->BreakpointData.nativeCodeMethodDescToken.NullPtr();
+ }
+ else
+ {
+ pEvent->BreakpointData.nativeCodeMethodDescToken =
+ (m_code.GetValue()->AsNativeCode())->GetVMNativeCodeMethodDescToken().ToLsPtr();
+ }
+
+ // Note: we're sending a two-way event, so it blocks here
+ // until the breakpoint is really added and the reply event is
+ // copied over the event we sent.
+ lockHolder.Release();
+ hr = pProcess->SendIPCEvent(pEvent, CorDBIPC_BUFFER_SIZE);
+ lockHolder.Acquire();
+
+ hr = WORST_HR(hr, pEvent->hr);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+
+ m_id = LsPtrToCookie(pEvent->BreakpointData.breakpointToken);
+
+ // If we weren't able to allocate the BP, we should have set the
+ // hr on the left side.
+ _ASSERTE(m_id != 0);
+
+
+ pAppDomain->m_breakpoints.AddBase(this);
+ m_active = true;
+
+ // Continue called automatically by StopContinueHolder
+ }
+ else
+ {
+ _ASSERTE (pAppDomain != NULL);
+
+ if (pProcess->IsSafeToSendEvents())
+ {
+ pProcess->InitIPCEvent(pEvent, DB_IPCE_BREAKPOINT_REMOVE, false, pAppDomain->GetADToken());
+
+ pEvent->BreakpointData.breakpointToken = GetLsPtrBP();
+
+ lockHolder.Release();
+ hr = pProcess->SendIPCEvent(pEvent, CorDBIPC_BUFFER_SIZE);
+ lockHolder.Acquire();
+
+ hr = WORST_HR(hr, pEvent->hr);
+ }
+ else
+ {
+ hr = CORDBHRFromProcessState(pProcess, pAppDomain);
+ }
+
+ pAppDomain->m_breakpoints.RemoveBase(LsPtrToCookie(GetLsPtrBP()));
+ m_active = false;
+ }
+
+ return hr;
+}
+
+void CordbFunctionBreakpoint::Disconnect()
+{
+ m_code.Clear();
+}
+
+/* ------------------------------------------------------------------------- *
+ * Stepper class
+ * ------------------------------------------------------------------------- */
+
+CordbStepper::CordbStepper(CordbThread *thread, CordbFrame *frame)
+ : CordbBase(thread->GetProcess(), 0, enumCordbStepper),
+ m_thread(thread), m_frame(frame),
+ m_stepperToken(0), m_active(false),
+ m_rangeIL(TRUE),
+ m_fIsJMCStepper(false),
+ m_rgfMappingStop(STOP_OTHER_UNMAPPED),
+ m_rgfInterceptStop(INTERCEPT_NONE)
+{
+}
+
+HRESULT CordbStepper::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugStepper)
+ *pInterface = static_cast<ICorDebugStepper *>(this);
+ else if (id == IID_ICorDebugStepper2)
+ *pInterface = static_cast<ICorDebugStepper2 *>(this);
+ else if (id == IID_IUnknown)
+ *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugStepper *>(this));
+ else
+ return E_NOINTERFACE;
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbStepper::SetRangeIL(BOOL bIL)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ m_rangeIL = (bIL != FALSE);
+
+ return S_OK;
+}
+
+HRESULT CordbStepper::SetJMC(BOOL fIsJMCStepper)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ // Can't have JMC and stopping with anything else.
+ if (m_rgfMappingStop & STOP_ALL)
+ return E_INVALIDARG;
+
+ m_fIsJMCStepper = (fIsJMCStepper != FALSE);
+ return S_OK;
+}
+
+HRESULT CordbStepper::IsActive(BOOL *pbActive)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pbActive, BOOL *);
+
+ *pbActive = m_active;
+
+ return S_OK;
+}
+
+// M_id is a ptr to the stepper in the LS process.
+LSPTR_STEPPER CordbStepper::GetLsPtrStepper()
+{
+ LSPTR_STEPPER p;
+ p.Set((void*) m_id);
+ return p;
+}
+
+HRESULT CordbStepper::Deactivate()
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ if (!m_active)
+ return S_OK;
+
+ FAIL_IF_NEUTERED(this);
+
+ if (m_thread == NULL)
+ return CORDBG_E_PROCESS_TERMINATED;
+
+ HRESULT hr;
+ CordbProcess *process = GetProcess();
+ ATT_ALLOW_LIVE_DO_STOPGO(process);
+
+ process->Lock();
+
+ if (!m_active) // another thread may be deactivating (e.g. step complete event)
+ {
+ process->Unlock();
+ return S_OK;
+ }
+
+ CordbAppDomain *pAppDomain = GetAppDomain();
+ _ASSERTE (pAppDomain != NULL);
+
+ DebuggerIPCEvent event;
+ process->InitIPCEvent(&event,
+ DB_IPCE_STEP_CANCEL,
+ false,
+ pAppDomain->GetADToken());
+
+ event.StepData.stepperToken = GetLsPtrStepper();
+
+ process->Unlock();
+ hr = process->SendIPCEvent(&event, sizeof(DebuggerIPCEvent));
+ hr = WORST_HR(hr, event.hr);
+ process->Lock();
+
+
+ process->m_steppers.RemoveBase((ULONG_PTR)m_id);
+ m_active = false;
+
+ process->Unlock();
+
+ return hr;
+}
+
+HRESULT CordbStepper::SetInterceptMask(CorDebugIntercept mask)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ m_rgfInterceptStop = mask;
+ return S_OK;
+}
+
+HRESULT CordbStepper::SetUnmappedStopMask(CorDebugUnmappedStop mask)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ // You must be Win32 attached to stop in unmanaged code.
+ if ((mask & STOP_UNMANAGED) && !GetProcess()->IsInteropDebugging())
+ return E_INVALIDARG;
+
+ // Limitations on JMC Stepping - if JMC stepping is active,
+ // all other stop masks must be disabled.
+ // The jit can't place JMC probes before the prolog, so if we're
+ // we're JMC stepping, we'll stop after the prolog.
+ // The implementation for JMC stepping also doesn't let us stop in
+ // unmanaged code. (because there are no probes there).
+ // So enforce those implementation limitations here.
+ if (m_fIsJMCStepper)
+ {
+ if (mask & STOP_ALL)
+ return E_INVALIDARG;
+ }
+
+ // @todo- Ensure that we only set valid bits.
+
+
+ m_rgfMappingStop = mask;
+ return S_OK;
+}
+
+HRESULT CordbStepper::Step(BOOL bStepIn)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (m_thread == NULL)
+ return CORDBG_E_PROCESS_TERMINATED;
+
+ return StepRange(bStepIn, NULL, 0);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Ships off a step-range command to the left-side. On the next continue the LS will
+// step across one range at a time.
+//
+// Arguments:
+// fStepIn - TRUE if this stepper should execute a step-in, else FALSE
+// rgRanges - Array of ranges that define a single step.
+// cRanges - Count of number of elements in rgRanges.
+//
+// Returns:
+// S_OK if the stepper is successfully set-up, else an appropriate error code.
+//
+HRESULT CordbStepper::StepRange(BOOL fStepIn,
+ COR_DEBUG_STEP_RANGE rgRanges[],
+ ULONG32 cRanges)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(rgRanges, COR_DEBUG_STEP_RANGE, cRanges, true, true);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (m_thread == NULL)
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ HRESULT hr = S_OK;
+
+ if (m_active)
+ {
+ //
+ // Deactivate the current stepping.
+ // or return an error???
+ //
+ hr = Deactivate();
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ }
+
+ // Validate step-ranges. Ranges are exclusive, so end offset
+ // should always be greater than start offset.
+ // Ranges don't have to be sorted.
+ // Zero ranges is ok; though they ought to just call Step() in that case.
+ for (ULONG32 i = 0; i < cRanges; i++)
+ {
+ if (rgRanges[i].startOffset >= rgRanges[i].endOffset)
+ {
+ STRESS_LOG2(LF_CORDB, LL_INFO10, "Illegal step range. 0x%x-0x%x\n", rgRanges[i].startOffset, rgRanges[i].endOffset);
+ return ErrWrapper(E_INVALIDARG);
+ }
+ }
+
+ CordbProcess * pProcess = GetProcess();
+
+ //
+ // Build step event
+ //
+
+ DebuggerIPCEvent * pEvent = reinterpret_cast<DebuggerIPCEvent *>(_alloca(CorDBIPC_BUFFER_SIZE));
+
+ pProcess->InitIPCEvent(pEvent, DB_IPCE_STEP, true, GetAppDomain()->GetADToken());
+
+ pEvent->StepData.vmThreadToken = m_thread->m_vmThreadToken;
+ pEvent->StepData.rgfMappingStop = m_rgfMappingStop;
+ pEvent->StepData.rgfInterceptStop = m_rgfInterceptStop;
+ pEvent->StepData.IsJMCStop = !!m_fIsJMCStepper;
+
+
+ if (m_frame == NULL)
+ {
+ pEvent->StepData.frameToken = LEAF_MOST_FRAME;
+ }
+ else
+ {
+ pEvent->StepData.frameToken = m_frame->GetFramePointer();
+ }
+
+ pEvent->StepData.stepIn = (fStepIn != 0);
+ pEvent->StepData.totalRangeCount = cRanges;
+ pEvent->StepData.rangeIL = m_rangeIL;
+
+ //
+ // Send ranges. We may have to send > 1 message.
+ //
+
+ COR_DEBUG_STEP_RANGE * pRangeStart = &(pEvent->StepData.range);
+ COR_DEBUG_STEP_RANGE * pRangeEnd = (reinterpret_cast<COR_DEBUG_STEP_RANGE *> (((BYTE *)pEvent) + CorDBIPC_BUFFER_SIZE)) - 1;
+
+ int cRangesToGo = cRanges;
+
+ if (cRangesToGo > 0)
+ {
+ while (cRangesToGo > 0)
+ {
+ //
+ // Find the number of ranges we can copy this time thru the loop
+ //
+ int cRangesToCopy;
+
+ if (cRangesToGo < (pRangeEnd - pRangeStart))
+ {
+ cRangesToCopy = cRangesToGo;
+ }
+ else
+ {
+ cRangesToCopy = (unsigned int)(pRangeEnd - pRangeStart);
+ }
+
+ //
+ // Copy the ranges into the IPC block now, 1-by-1
+ //
+ int cRangesCopied = 0;
+
+ while (cRangesCopied != cRangesToCopy)
+ {
+ pRangeStart[cRangesCopied] = rgRanges[cRanges - cRangesToGo + cRangesCopied];
+ cRangesCopied++;
+ }
+
+ pEvent->StepData.rangeCount = cRangesCopied;
+
+ cRangesToGo -= cRangesCopied;
+
+ //
+ // Send step event (two-way event here...)
+ //
+
+ hr = pProcess->SendIPCEvent(pEvent, CorDBIPC_BUFFER_SIZE);
+
+ hr = WORST_HR(hr, pEvent->hr);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ }
+ }
+ else
+ {
+ //
+ // Send step event without any ranges (two-way event here...)
+ //
+
+ hr = pProcess->SendIPCEvent(pEvent, CorDBIPC_BUFFER_SIZE);
+
+ hr = WORST_HR(hr, pEvent->hr);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ }
+
+ m_id = LsPtrToCookie(pEvent->StepData.stepperToken);
+
+ LOG((LF_CORDB,LL_INFO10000, "CS::SR: m_id:0x%x | 0x%x \n",
+ m_id,
+ LsPtrToCookie(pEvent->StepData.stepperToken)));
+
+#ifdef _DEBUG
+ CordbAppDomain *pAppDomain = GetAppDomain();
+#endif
+ _ASSERTE (pAppDomain != NULL);
+
+ pProcess->Lock();
+
+ pProcess->m_steppers.AddBase(this);
+ m_active = true;
+
+ pProcess->Unlock();
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Ships off a step-out command to the left-side. On the next continue the LS will
+// execute a step-out
+//
+// Returns:
+// S_OK if the stepper is successfully set-up, else an appropriate error code.
+//
+HRESULT CordbStepper::StepOut()
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (m_thread == NULL)
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ HRESULT hr;
+
+ if (m_active)
+ {
+ //
+ // Deactivate the current stepping.
+ // or return an error???
+ //
+
+ hr = Deactivate();
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ }
+
+ CordbProcess * pProcess = GetProcess();
+
+ // We don't do native step-out.
+ if (pProcess->SupportsVersion(ver_ICorDebugProcess2))
+ {
+ if ((m_rgfMappingStop & STOP_UNMANAGED) != 0)
+ {
+ return ErrWrapper(CORDBG_E_CANT_INTEROP_STEP_OUT);
+ }
+ }
+
+ //
+ // Build step event
+ //
+
+ DebuggerIPCEvent * pEvent = (DebuggerIPCEvent *) _alloca(CorDBIPC_BUFFER_SIZE);
+
+ pProcess->InitIPCEvent(pEvent, DB_IPCE_STEP_OUT, true, GetAppDomain()->GetADToken());
+
+ pEvent->StepData.vmThreadToken = m_thread->m_vmThreadToken;
+ pEvent->StepData.rgfMappingStop = m_rgfMappingStop;
+ pEvent->StepData.rgfInterceptStop = m_rgfInterceptStop;
+ pEvent->StepData.IsJMCStop = !!m_fIsJMCStepper;
+
+ if (m_frame == NULL)
+ {
+ pEvent->StepData.frameToken = LEAF_MOST_FRAME;
+ }
+ else
+ {
+ pEvent->StepData.frameToken = m_frame->GetFramePointer();
+ }
+
+ pEvent->StepData.totalRangeCount = 0;
+
+ // Note: two-way event here...
+ hr = pProcess->SendIPCEvent(pEvent, CorDBIPC_BUFFER_SIZE);
+
+ hr = WORST_HR(hr, pEvent->hr);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ m_id = LsPtrToCookie(pEvent->StepData.stepperToken);
+
+#ifdef _DEBUG
+ CordbAppDomain * pAppDomain = GetAppDomain();
+#endif
+ _ASSERTE (pAppDomain != NULL);
+
+ pProcess->Lock();
+
+ pProcess->m_steppers.AddBase(this);
+ m_active = true;
+
+ pProcess->Unlock();
+
+ return S_OK;
+}
diff --git a/src/debug/di/classfactory.h b/src/debug/di/classfactory.h
new file mode 100644
index 0000000000..5109f3a637
--- /dev/null
+++ b/src/debug/di/classfactory.h
@@ -0,0 +1,82 @@
+// 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.
+//*****************************************************************************
+// ClassFactory.h
+//
+
+//
+// Class factories are used by the pluming in COM to activate new objects.
+// This module contains the class factory code to instantiate the debugger
+// objects described in RSPriv.h.
+//
+//*****************************************************************************
+#ifndef __ClassFactory__h__
+#define __ClassFactory__h__
+
+#include "rspriv.h"
+
+
+// This typedef is for a function which will create a new instance of an object.
+typedef HRESULT (__stdcall * PFN_CREATE_OBJ)(REFIID riid, void **ppvObject);
+
+
+//*****************************************************************************
+// One class factory object satifies all of our clsid's, to reduce overall
+// code bloat.
+//*****************************************************************************
+class CClassFactory :
+ public IClassFactory
+{
+ CClassFactory() { } // Can't use without data.
+
+public:
+ CClassFactory(PFN_CREATE_OBJ pfnCreateObject)
+ : m_cRef(1), m_pfnCreateObject(pfnCreateObject)
+ { }
+
+ virtual ~CClassFactory() {}
+
+ //
+ // IUnknown methods.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(
+ REFIID riid,
+ void **ppvObject);
+
+ virtual ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (InterlockedIncrement(&m_cRef));
+ }
+
+ virtual ULONG STDMETHODCALLTYPE Release()
+ {
+ LONG cRef = InterlockedDecrement(&m_cRef);
+ if (cRef <= 0)
+ delete this;
+ return (cRef);
+ }
+
+
+ //
+ // IClassFactory methods.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE CreateInstance(
+ IUnknown *pUnkOuter,
+ REFIID riid,
+ void **ppvObject);
+
+ virtual HRESULT STDMETHODCALLTYPE LockServer(
+ BOOL fLock);
+
+
+private:
+ LONG m_cRef; // Reference count.
+ PFN_CREATE_OBJ m_pfnCreateObject; // Creation function for an instance.
+};
+
+
+
+#endif // __ClassFactory__h__
diff --git a/src/debug/di/cordb.cpp b/src/debug/di/cordb.cpp
new file mode 100644
index 0000000000..497225fd67
--- /dev/null
+++ b/src/debug/di/cordb.cpp
@@ -0,0 +1,572 @@
+// 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.
+//*****************************************************************************
+// CorDB.cpp
+//
+
+//
+// Dll* routines for entry points, and support for COM framework. The class
+// factory and other routines live in this module.
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "classfactory.h"
+#include "corsym.h"
+#include "contract.h"
+#include "metadataexports.h"
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+#include "dbgtransportsession.h"
+#include "dbgtransportmanager.h"
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+//********** Globals. *********************************************************
+#ifndef FEATURE_PAL
+HINSTANCE g_hInst; // Instance handle to this piece of code.
+#endif
+
+//-----------------------------------------------------------------------------
+// SxS Versioning story for Mscordbi (ICorDebug + friends)
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// In v1.0, we declared that mscordbi was a "shared" component, which means
+// that we promised to provide it from now until the end of time. So every CLR implementation
+// needs an Mscordbi that implements the everett guids for CorDebug + CorPublish.
+//
+// This works fine for CorPublish, which is truly shared.
+// CorDebug however is "versioned" not "shared" - each version of the CLR has its own disjoint copy.
+//
+// Thus creating a CorDebug object requires a version parameter.
+// CoCreateInstance doesn't have a the version param, so we use the new (v2.0+)
+// shim interface CreateDebuggingInterfaceFromVersion.
+//
+// ** So in summary: **
+// - Dlls don't do self-registration; they're registered by setup using .vrg files.
+// - All CLR versions (past + future) must have the same registry footprint w.r.t mscordbi.
+// This just means that all CLRs have the same mscordbi.vrg file.
+// - CorDebug is in fact versioned and each CLR version has its own copy.
+// - In v1.0/1.1, CorDebug was a CoClass. In v2.0+, it is not a CoClass and is created via the
+// CreateDebuggingInterfaceFromVersion shim API, which takes a version parameter.
+// - CorDebug must be SxS. V1.1 must only get the V1.1 version, and V2.0 must only get the V2.0 version.
+// V1.1: Clients will cocreate to get CorDebug. v1.1 will be the only mscordbi!DllGetClassObject
+// that provides a CorDebug, so CoCreateInstance will guarantee getting a v1.1 object.
+// V2.0: Clients use the new version-aware shim API, so it's not an issue.
+//
+// ** Preparing for Life in a Single-CLR world: **
+// In Orcas (v3), we expect to run on single-CLR. There will only be 1 mscordbi, and it will service all versions.
+// For whidbey (v2), we want to be able to flip a knob and pretend to be orcas (for testing purposes).
+//
+// Here's how to do that:
+// - copy whidbey mscordbi & dac over the everett mscordbi.
+// - When VS cocreates w/ the everett-guid, it will load the mscordbi on the everett path (
+// which will be whidbey dll), and ask for the everett guid.
+// - re-add CorDebug to the g_CoClasses list.
+
+
+//********** Locals. **********************************************************
+
+
+//********** Code. ************************************************************
+
+
+//*****************************************************************************
+// Standard public helper to create a Cordb object (ICorDebug instance).
+// This is used by the shim to get the Cordb object out of this module.
+// This is the creation path for V2.0+ for CorDebug using the in-process debugging
+// architecture (ICorDebug). In CLR v4+ debugger may choose to use the out-of-process
+// architecture to get an ICorDebugProcess directly (IClrDebugging::OpenVirtualProcess).
+//
+// This was used by the Mix07 release of Silverlight, but it didn't properly support versioning
+// and we no longer support it's debugger protocol so we require callers to use
+// code:CoreCLRCreateCordbObject instead.
+//
+// This is also still used on Mac - multi-instance debugging and debugger
+// versioning isn't really implemented there yet. This probably needs to change.
+//*****************************************************************************
+STDAPI CreateCordbObject(int iDebuggerVersion, IUnknown ** ppCordb)
+{
+#if defined(FEATURE_CORECLR) && !defined(FEATURE_DBGIPC_TRANSPORT_DI) && !defined(FEATURE_CORESYSTEM)
+ // This API should not be called for Windows CoreCLR unless we are doing interop-debugging
+ // (which is only supported internally). Use code:CoreCLRCreateCordbObject instead.
+ if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgEnableMixedModeDebugging) == 0)
+ {
+ _ASSERTE(!"Deprecated entry point CreateCordbObject() is called on Windows CoreCLR\n");
+ return E_NOTIMPL;
+ }
+#endif // FEATURE_CORECLR && !FEATURE_DBGIPC_TRANSPORT_DI
+
+ if (ppCordb == NULL)
+ {
+ return E_INVALIDARG;
+ }
+ if (iDebuggerVersion != CorDebugVersion_2_0 && iDebuggerVersion != CorDebugVersion_4_0)
+ {
+ return E_INVALIDARG;
+ }
+
+ return Cordb::CreateObject((CorDebugInterfaceVersion)iDebuggerVersion, IID_ICorDebug, (void **) ppCordb);
+}
+
+#if defined(FEATURE_CORECLR)
+//
+// Public API.
+// Telesto Creation path - only way to debug multi-instance.
+// This supercedes code:CreateCordbObject
+//
+// Arguments:
+// iDebuggerVersion - version of ICorDebug interfaces that the debugger is requesting
+// pid - pid of debuggee that we're attaching to.
+// hmodTargetCLR - module handle to clr in target pid that we're attaching to.
+// ppCordb - (out) the resulting ICorDebug object.
+//
+// Notes:
+// It's inconsistent that this takes a (handle, pid) but hands back an ICorDebug instead of an ICorDebugProcess.
+// Callers will need to call *ppCordb->DebugActiveProcess(pid).
+STDAPI CoreCLRCreateCordbObject(int iDebuggerVersion, DWORD pid, HMODULE hmodTargetCLR, IUnknown ** ppCordb)
+{
+ if (ppCordb == NULL)
+ {
+ return E_INVALIDARG;
+ }
+ if ((iDebuggerVersion < CorDebugVersion_2_0) ||
+ (iDebuggerVersion > CorDebugLatestVersion))
+ {
+ return E_INVALIDARG;
+ }
+
+ //
+ // Create the ICorDebug object
+ //
+ RSExtSmartPtr<ICorDebug> pCordb;
+ Cordb::CreateObject((CorDebugInterfaceVersion)iDebuggerVersion, IID_ICorDebug, (void **) &pCordb);
+
+ // @dbgtodo - we should stash the pid and validate that it's the same pid we're attaching to in ICorDebug::DebugActiveProcess.
+
+ //
+ // Associate it with the target instance
+ //
+ HRESULT hr = static_cast<Cordb*>(pCordb.GetValue())->SetTargetCLR(hmodTargetCLR);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ //
+ // Assign to out parameter.
+ //
+ hr = pCordb->QueryInterface(IID_IUnknown, (void**) ppCordb);
+
+ // Implicit release of pUnk, pCordb
+ return hr;
+}
+
+#endif // FEATURE_CORECLR
+
+
+
+
+//*****************************************************************************
+// The main dll entry point for this module. This routine is called by the
+// OS when the dll gets loaded. Control is simply deferred to the main code.
+//*****************************************************************************
+BOOL WINAPI DbgDllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
+{
+ // Save off the instance handle for later use.
+ switch (dwReason)
+ {
+
+ case DLL_PROCESS_ATTACH:
+ {
+#ifndef FEATURE_PAL
+ g_hInst = hInstance;
+#else
+ int err = PAL_InitializeDLL();
+ if(err != 0)
+ {
+ return FALSE;
+ }
+#endif
+
+#if defined(_DEBUG)
+ static int BreakOnDILoad = -1;
+ if (BreakOnDILoad == -1)
+ BreakOnDILoad = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnDILoad);
+
+ if (BreakOnDILoad)
+ {
+ _ASSERTE(!"DI Loaded");
+ }
+#endif
+
+#if defined(LOGGING)
+ {
+ PathString rcFile;
+ WszGetModuleFileName(hInstance, rcFile);
+ LOG((LF_CORDB, LL_INFO10000,
+ "DI::DbgDllMain: load right side support from file '%s'\n",
+ rcFile.GetUnicode()));
+ }
+#endif
+
+#ifdef RSCONTRACTS
+ // alloc a TLS slot
+ DbgRSThread::s_TlsSlot = TlsAlloc();
+ _ASSERTE(DbgRSThread::s_TlsSlot != TLS_OUT_OF_INDEXES);
+#endif
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ g_pDbgTransportTarget = new (nothrow) DbgTransportTarget();
+ if (g_pDbgTransportTarget == NULL)
+ return FALSE;
+
+ if (FAILED(g_pDbgTransportTarget->Init()))
+ return FALSE;
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+ }
+ break;
+
+ case DLL_THREAD_DETACH:
+ {
+#ifdef STRESS_LOG
+ StressLog::ThreadDetach((ThreadStressLog*) ClrFlsGetValue(TlsIdx_StressLog));
+#endif
+
+#ifdef RSCONTRACTS
+ // DbgRSThread are lazily created when we call GetThread(),
+ // So we don't need to do anything in DLL_THREAD_ATTACH,
+ // But this is our only chance to destroy the thread object.
+ DbgRSThread * p = DbgRSThread::GetThread();
+
+ p->Destroy();
+#endif
+ }
+ break;
+
+ case DLL_PROCESS_DETACH:
+ {
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ if (g_pDbgTransportTarget != NULL)
+ {
+ g_pDbgTransportTarget->Shutdown();
+ delete g_pDbgTransportTarget;
+ g_pDbgTransportTarget = NULL;
+ }
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+#ifdef RSCONTRACTS
+ TlsFree(DbgRSThread::s_TlsSlot);
+ DbgRSThread::s_TlsSlot = TLS_OUT_OF_INDEXES;
+#endif
+ }
+ break;
+ }
+
+ return TRUE;
+}
+
+
+// The obsolete v1 CLSID - see comment above for details.
+static const GUID CLSID_CorDebug_V1 = {0x6fef44d0,0x39e7,0x4c77, { 0xbe,0x8e,0xc9,0xf8,0xcf,0x98,0x86,0x30}};
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+
+// GUID for pipe-based debugging (Unix platforms)
+const GUID CLSID_CorDebug_Telesto = {0x8bd1daae, 0x188e, 0x42f4, {0xb0, 0x09, 0x08, 0xfa, 0xfd, 0x17, 0x81, 0x3b}};
+
+// The debug engine needs to implement an internal Visual Studio debugger interface (defined by the CPDE)
+// which augments launch and attach requests so that we can obtain information from the port supplier (the
+// network address of the target in our case). See RSPriv.h for the definition of the interface. (We have to
+// hard code the IID and interface definition because VS does not export it, but it's not much of an issue
+// since COM interfaces are completely immutable).
+const GUID IID_IDebugRemoteCorDebug = {0x83C91210, 0xA34F, 0x427c, {0xB3, 0x5F, 0x79, 0xC3, 0x99, 0x5B, 0x3C, 0x14}};
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+//*****************************************************************************
+// Called by COM to get a class factory for a given CLSID. If it is one we
+// support, instantiate a class factory object and prepare for create instance.
+//*****************************************************************************
+STDAPI DllGetClassObjectInternal( // Return code.
+ REFCLSID rclsid, // The class to desired.
+ REFIID riid, // Interface wanted on class factory.
+ LPVOID FAR *ppv) // Return interface pointer here.
+{
+ HRESULT hr;
+ CClassFactory *pClassFactory; // To create class factory object.
+ PFN_CREATE_OBJ pfnCreateObject = NULL;
+
+
+#if defined(FEATURE_DBG_PUBLISH)
+ if (rclsid == CLSID_CorpubPublish)
+ {
+ pfnCreateObject = CorpubPublish::CreateObject;
+ }
+ else
+#endif
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ if (rclsid == CLSID_CorDebug_Telesto)
+ {
+ pfnCreateObject = Cordb::CreateObjectTelesto;
+ }
+#else // !FEATURE_DBGIPC_TRANSPORT_DI
+ if(rclsid == CLSID_CorDebug_V1)
+ {
+ if (0) // if (IsSingleCLR())
+ {
+ // Don't allow creating backwards objects until we ensure that the v2.0 Right-side
+ // is backwards compat. This may involve using CordbProcess::SupportsVersion to conditionally
+ // emulate old behavior.
+ // If emulating V1.0, QIs for V2.0 interfaces should fail.
+ _ASSERTE(!"Ensure that V2.0 RS is backwards compat");
+ pfnCreateObject = Cordb::CreateObjectV1;
+ }
+ }
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+ if (pfnCreateObject == NULL)
+ return (CLASS_E_CLASSNOTAVAILABLE);
+
+ // Allocate the new factory object. The ref count is set to 1 in the constructor.
+ pClassFactory = new (nothrow) CClassFactory(pfnCreateObject);
+ if (!pClassFactory)
+ return (E_OUTOFMEMORY);
+
+ // Pick the v-table based on the caller's request.
+ hr = pClassFactory->QueryInterface(riid, ppv);
+
+ // Always release the local reference, if QI failed it will be
+ // the only one and the object gets freed.
+ pClassFactory->Release();
+
+ return hr;
+}
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+// In V2 we started hiding DllGetClassObject because activation was no longer performed through COM directly
+// (we went through the shim). CoreCLR doesn't have a shim and we go back to the COM model so we re-expose
+// DllGetClassObject to make that work.
+
+STDAPI DllGetClassObject( // Return code.
+ REFCLSID rclsid, // The class to desired.
+ REFIID riid, // Interface wanted on class factory.
+ LPVOID FAR *ppv) // Return interface pointer here.
+{
+ return DllGetClassObjectInternal(rclsid, riid, ppv);
+}
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+
+//*****************************************************************************
+//
+//********** Class factory code.
+//
+//*****************************************************************************
+
+
+//*****************************************************************************
+// QueryInterface is called to pick a v-table on the co-class.
+//*****************************************************************************
+HRESULT STDMETHODCALLTYPE CClassFactory::QueryInterface(
+ REFIID riid,
+ void **ppvObject)
+{
+ HRESULT hr;
+
+ // Avoid confusion.
+ *ppvObject = NULL;
+
+ // Pick the right v-table based on the IID passed in.
+ if (riid == IID_IUnknown)
+ *ppvObject = (IUnknown *) this;
+ else if (riid == IID_IClassFactory)
+ *ppvObject = (IClassFactory *) this;
+
+ // If successful, add a reference for out pointer and return.
+ if (*ppvObject)
+ {
+ hr = S_OK;
+ AddRef();
+ }
+ else
+ hr = E_NOINTERFACE;
+ return (hr);
+}
+
+
+//*****************************************************************************
+// CreateInstance is called to create a new instance of the coclass for which
+// this class was created in the first place. The returned pointer is the
+// v-table matching the IID if there.
+//*****************************************************************************
+HRESULT STDMETHODCALLTYPE CClassFactory::CreateInstance(
+ IUnknown *pUnkOuter,
+ REFIID riid,
+ void **ppvObject)
+{
+ HRESULT hr;
+
+ // Avoid confusion.
+ *ppvObject = NULL;
+ _ASSERTE(m_pfnCreateObject);
+
+ // Aggregation is not supported by these objects.
+ if (pUnkOuter)
+ return (CLASS_E_NOAGGREGATION);
+
+ // Ask the object to create an instance of itself, and check the iid.
+ hr = (*m_pfnCreateObject)(riid, ppvObject);
+ return (hr);
+}
+
+
+HRESULT STDMETHODCALLTYPE CClassFactory::LockServer(
+ BOOL fLock)
+{
+//<TODO>@todo: hook up lock server logic.</TODO>
+ return (S_OK);
+}
+
+
+//*****************************************************************************
+// This helper provides access to the instance handle of the loaded image.
+//*****************************************************************************
+#ifndef FEATURE_PAL
+HINSTANCE GetModuleInst()
+{
+ return g_hInst;
+}
+#endif
+
+
+//-----------------------------------------------------------------------------
+// Substitute for mscoree
+//
+// Notes:
+// Mscordbi does not link with mscoree, provide a stub implementation.
+// Callers are in dead-code paths, but we still need to provide a stub. Ideally, we'd factor
+// out the callers too and then we wouldn't need an E_NOTIMPL stub.
+STDAPI GetRequestedRuntimeInfo(LPCWSTR pExe,
+ LPCWSTR pwszVersion,
+ LPCWSTR pConfigurationFile,
+ DWORD startupFlags,
+ DWORD runtimeInfoFlags,
+ __out_ecount_opt(dwDirectory) LPWSTR pDirectory,
+ DWORD dwDirectory,
+ DWORD *dwDirectoryLength,
+ __out_ecount_opt(cchBuffer) LPWSTR pVersion,
+ DWORD cchBuffer,
+ DWORD* dwlength)
+{
+ _ASSERTE(!"GetRequestedRuntimeInfo not impl");
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+// Replacement for legacy shim API GetCORRequiredVersion(...) used in linked libraries.
+// Used in code:TiggerStorage::GetDefaultVersion#CallTo_CLRRuntimeHostInternal_GetImageVersionString.
+//
+// Notes:
+// Mscordbi does not statically link to mscoree.dll.
+// This is used in EnC for IMetadataEmit2::GetSaveSize to computer size of header.
+// see code:TiggerStorage::GetDefaultVersion.
+//
+// Implemented by returning the version we're built for. Mscordbi.dll has a tight coupling with
+// the CLR version, so this will match exactly the build version we're debugging.
+// One potential caveat is that the build version doesn't necessarily match the install string
+// (eg. we may install as "v4.0.x86chk" but that's not captured in the build version). But this should
+// be internal scenarios only, and shouldn't actually matter here. If it did, we could instead get
+// the last components of the directory name the current mscordbi.dll is located in.
+//
+HRESULT
+CLRRuntimeHostInternal_GetImageVersionString(
+ __out_ecount_part(*pcchBuffer, *pcchBuffer) LPWSTR wszBuffer,
+ DWORD *pcchBuffer)
+{
+ // Construct the cannoncial version string we're built as - eg. "v4.0.1234"
+ const WCHAR k_wszBuiltFor[] = W("v") VER_PRODUCTVERSION_NO_QFE_STR_L;
+
+ // Copy our buffer in
+ HRESULT hr = HRESULT_FROM_WIN32(wcscpy_s(wszBuffer, *pcchBuffer, k_wszBuiltFor));
+
+ // Hand out length regardless of success - like GetCORRequiredVersion
+ *pcchBuffer = _countof(k_wszBuiltFor);
+
+ return hr;
+} // CLRRuntimeHostInternal_GetImageVersionString
+
+
+#ifdef _TARGET_ARM_
+BOOL
+DbiGetThreadContext(HANDLE hThread,
+ DT_CONTEXT *lpContext)
+{
+ // if we aren't local debugging this isn't going to work
+#if !defined(_ARM_) || defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ _ASSERTE(!"Can't use local GetThreadContext remotely, this needed to go to datatarget");
+ return FALSE;
+#else
+ BOOL res = FALSE;
+ if (((ULONG)lpContext) & ~0x10)
+ {
+ CONTEXT *ctx = (CONTEXT*)_aligned_malloc(sizeof(CONTEXT), 16);
+ if (ctx)
+ {
+ ctx->ContextFlags = lpContext->ContextFlags;
+ if (::GetThreadContext(hThread, ctx))
+ {
+ *lpContext = *(DT_CONTEXT*)ctx;
+ res = TRUE;
+ }
+
+ _aligned_free(ctx);
+ }
+ else
+ {
+ // malloc does not set the last error, but the caller of GetThreadContext
+ // will expect it to be set on failure.
+ SetLastError(ERROR_OUTOFMEMORY);
+ }
+ }
+ else
+ {
+ res = ::GetThreadContext(hThread, (CONTEXT*)lpContext);
+ }
+
+ return res;
+#endif
+}
+
+BOOL
+DbiSetThreadContext(HANDLE hThread,
+ const DT_CONTEXT *lpContext)
+{
+#if !defined(_ARM_) || defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ _ASSERTE(!"Can't use local GetThreadContext remotely, this needed to go to datatarget");
+ return FALSE;
+#else
+ BOOL res = FALSE;
+ if (((ULONG)lpContext) & ~0x10)
+ {
+ CONTEXT *ctx = (CONTEXT*)_aligned_malloc(sizeof(CONTEXT), 16);
+ if (ctx)
+ {
+ *ctx = *(CONTEXT*)lpContext;
+ res = ::SetThreadContext(hThread, ctx);
+ _aligned_free(ctx);
+ }
+ else
+ {
+ // malloc does not set the last error, but the caller of SetThreadContext
+ // will expect it to be set on failure.
+ SetLastError(ERROR_OUTOFMEMORY);
+ }
+ }
+ else
+ {
+ res = ::SetThreadContext(hThread, (CONTEXT*)lpContext);
+ }
+
+ return res;
+#endif
+}
+#endif
diff --git a/src/debug/di/dbgtransportmanager.cpp b/src/debug/di/dbgtransportmanager.cpp
new file mode 100644
index 0000000000..77a3548ea5
--- /dev/null
+++ b/src/debug/di/dbgtransportmanager.cpp
@@ -0,0 +1,231 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include "stdafx.h"
+#include "dbgtransportsession.h"
+#include "dbgtransportmanager.h"
+#include "coreclrremotedebugginginterfaces.h"
+
+
+
+#ifdef FEATURE_DBGIPC_TRANSPORT_DI
+
+DbgTransportTarget *g_pDbgTransportTarget = NULL;
+
+DbgTransportTarget::DbgTransportTarget()
+{
+ memset(this, 0, sizeof(*this));
+}
+
+// Initialization routine called only by the DbgTransportManager.
+HRESULT DbgTransportTarget::Init()
+{
+ m_sLock.Init("DbgTransportTarget Lock", RSLock::cLockFlat, RSLock::LL_DBG_TRANSPORT_TARGET_LOCK);
+
+ return S_OK;
+}
+
+// Shutdown routine called only by the DbgTransportManager.
+void DbgTransportTarget::Shutdown()
+{
+ DbgTransportLog(LC_Always, "DbgTransportTarget shutting down");
+
+ {
+ RSLockHolder lock(&m_sLock);
+ while (m_pProcessList)
+ {
+ ProcessEntry *pDelProcess = m_pProcessList;
+ m_pProcessList = m_pProcessList->m_pNext;
+ delete pDelProcess;
+ }
+ }
+ m_sLock.Destroy();
+}
+
+
+// Given a PID attempt to find or create a DbgTransportSession instance to manage a connection to a runtime in
+// that process. Returns E_UNEXPECTED if the process can't be found. Also returns a handle that can be waited
+// on for process termination.
+HRESULT DbgTransportTarget::GetTransportForProcess(DWORD dwPID,
+ DbgTransportSession **ppTransport,
+ HANDLE *phProcessHandle)
+{
+ RSLockHolder lock(&m_sLock);
+ HRESULT hr = S_OK;
+
+ ProcessEntry *entry = LocateProcessByPID(dwPID);
+
+ if (entry == NULL)
+ {
+
+ NewHolder<ProcessEntry> newEntry = new(nothrow) ProcessEntry();
+ if (newEntry == NULL)
+ return E_OUTOFMEMORY;
+
+ NewHolder<DbgTransportSession> transport = new(nothrow) DbgTransportSession();
+ if (transport == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+
+ HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
+ if (hProcess == NULL)
+ {
+ transport->Shutdown();
+ return HRESULT_FROM_GetLastError();
+ }
+
+ // Initialize it (this immediately starts the remote connection process).
+ hr = transport->Init(dwPID, hProcess);
+ if (FAILED(hr))
+ {
+ transport->Shutdown();
+ CloseHandle(hProcess);
+ return hr;
+ }
+
+ entry = newEntry;
+ newEntry.SuppressRelease();
+ entry->m_dwPID = dwPID;
+ entry->m_hProcess = hProcess;
+ entry->m_transport = transport;
+ transport.SuppressRelease();
+ entry->m_cProcessRef = 0;
+
+ // Adding new entry to the list.
+ entry->m_pNext = m_pProcessList;
+ m_pProcessList = entry;
+ }
+
+ entry->m_cProcessRef++;
+ _ASSERTE(entry->m_cProcessRef > 0);
+ _ASSERTE(entry->m_transport != NULL);
+ _ASSERTE(entry->m_hProcess > 0);
+
+ *ppTransport = entry->m_transport;
+ if (!DuplicateHandle(GetCurrentProcess(),
+ entry->m_hProcess,
+ GetCurrentProcess(),
+ phProcessHandle,
+ 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS
+ FALSE,
+ DUPLICATE_SAME_ACCESS))
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ return hr;
+}
+
+
+// Release another reference to the transport associated with dwPID. Once all references are gone (modulo the
+// manager's own weak reference) clean up the transport and deallocate it.
+void DbgTransportTarget::ReleaseTransport(DbgTransportSession *pTransport)
+{
+ RSLockHolder lock(&m_sLock);
+
+ ProcessEntry *entry = m_pProcessList;
+
+ // Pointer to the pointer that points to *entry.
+ // It either points to m_pProcessList or m_pNext of some entry.
+ // It is used to fix the linked list after deletion of an entry.
+ ProcessEntry **prevPtr = &m_pProcessList;
+
+ // Looking for ProcessEntry with a given transport
+ while (entry)
+ {
+
+ _ASSERTE(entry->m_cProcessRef > 0);
+ _ASSERTE(entry->m_transport != NULL);
+ _ASSERTE(entry->m_hProcess > 0);
+
+ if (entry->m_transport == pTransport)
+ {
+ // Mark that it has one less holder now
+ entry->m_cProcessRef--;
+
+ // If no more holders remove the entry from the list and free resources
+ if (entry->m_cProcessRef == 0)
+ {
+ *prevPtr = entry->m_pNext;
+ delete entry;
+ }
+ return;
+ }
+ prevPtr = &entry->m_pNext;
+ entry = entry->m_pNext;
+ }
+
+ _ASSERTE(!"Trying to release transport that doesn't belong to this DbgTransportTarget");
+ pTransport->Shutdown();
+}
+
+HRESULT DbgTransportTarget::CreateProcess(LPCWSTR lpApplicationName,
+ LPCWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation)
+{
+
+ BOOL result = WszCreateProcess(lpApplicationName,
+ lpCommandLine,
+ lpProcessAttributes,
+ lpThreadAttributes,
+ bInheritHandles,
+ dwCreationFlags,
+ lpEnvironment,
+ lpCurrentDirectory,
+ lpStartupInfo,
+ lpProcessInformation);
+
+ if (!result)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ return S_OK;
+}
+
+// Kill the process identified by PID.
+void DbgTransportTarget::KillProcess(DWORD dwPID)
+{
+ HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, dwPID);
+ if (hProcess != NULL)
+ {
+ TerminateProcess(hProcess, 0);
+ CloseHandle(hProcess);
+ }
+}
+
+DbgTransportTarget::ProcessEntry::~ProcessEntry()
+{
+ CloseHandle(m_hProcess);
+ m_hProcess = NULL;
+
+ m_transport->Shutdown();
+ m_transport = NULL;
+}
+
+// Locate a process entry by PID. Assumes the lock is already held.
+DbgTransportTarget::ProcessEntry *DbgTransportTarget::LocateProcessByPID(DWORD dwPID)
+{
+ _ASSERTE(m_sLock.HasLock());
+
+ ProcessEntry *pProcess = m_pProcessList;
+ while (pProcess)
+ {
+ if (pProcess->m_dwPID == dwPID)
+ return pProcess;
+ pProcess = pProcess->m_pNext;
+ }
+ return NULL;
+}
+
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
diff --git a/src/debug/di/dbgtransportmanager.h b/src/debug/di/dbgtransportmanager.h
new file mode 100644
index 0000000000..3a8013eae8
--- /dev/null
+++ b/src/debug/di/dbgtransportmanager.h
@@ -0,0 +1,87 @@
+// 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 __DBG_TRANSPORT_MANAGER_INCLUDED
+#define __DBG_TRANSPORT_MANAGER_INCLUDED
+
+#ifdef FEATURE_DBGIPC_TRANSPORT_DI
+
+#include "coreclrremotedebugginginterfaces.h"
+
+
+// TODO: Ideally we'd like to remove this class and don't do any process related book keeping in DBI.
+
+// This is a registry of all the processes a debugger knows about, different components call it in order to
+// obtain right instance of DbgTransportSession for a given PID. It keeps list of processes and transports for them.
+// It also handles things like creating and killing a process.
+
+// Usual lifecycle looks like this:
+// Debug a new process:
+// * CreateProcess(&pid)
+// * GetTransportForProcess(pid, &transport)
+// * ReleaseTransport(transport)
+// * KillProcess(pid)
+
+// Attach to an existing process:
+// * Obtain pid from a user
+// * GetTransportForProcess(pid, &transport)
+// * ReleaseTransport(transport)
+
+class DbgTransportTarget
+{
+public:
+ DbgTransportTarget();
+
+ // Given a PID attempt to find or create a DbgTransportSession instance to manage a connection to a
+ // runtime in that process. Returns E_UNEXPECTED if the process can't be found. Also returns a handle that
+ // can be waited on for process termination.
+ HRESULT GetTransportForProcess(DWORD dwPID, DbgTransportSession **ppTransport, HANDLE *phProcessHandle);
+
+ // Give back a previously acquired transport (if nobody else is using the transport it will close down the
+ // connection at this point).
+ void ReleaseTransport(DbgTransportSession *pTransport);
+
+ // When and if the process starts the runtime will be told to halt and wait for a debugger attach.
+ HRESULT CreateProcess(LPCWSTR lpApplicationName,
+ LPCWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation);
+
+ // Kill the process identified by PID.
+ void KillProcess(DWORD dwPID);
+
+ HRESULT Init();
+ void Shutdown();
+
+private:
+ struct ProcessEntry
+ {
+ ProcessEntry *m_pNext; // Next entry in the list
+ DWORD m_dwPID; // Process ID for this entry
+ HANDLE m_hProcess; // Process handle
+ DbgTransportSession *m_transport; // Debugger's connection to the process
+ DWORD m_cProcessRef; // Ref count
+
+ ~ProcessEntry();
+ };
+
+ ProcessEntry *m_pProcessList; // Head of list of currently alive processes (unsorted)
+ RSLock m_sLock; // Lock protecting read and write access to the target list
+
+ // Locate a process entry by PID. Assumes the lock is already held.
+ ProcessEntry *LocateProcessByPID(DWORD dwPID);
+};
+
+extern DbgTransportTarget *g_pDbgTransportTarget;
+
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+#endif // __DBG_TRANSPORT_MANAGER_INCLUDED
diff --git a/src/debug/di/dbgtransportpipeline.cpp b/src/debug/di/dbgtransportpipeline.cpp
new file mode 100644
index 0000000000..e3a3a8a54d
--- /dev/null
+++ b/src/debug/di/dbgtransportpipeline.cpp
@@ -0,0 +1,457 @@
+// 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.
+//*****************************************************************************
+// File: DbgTransportPipeline.cpp
+//
+
+//
+// Implements the native pipeline for Mac debugging.
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "nativepipeline.h"
+#include "dbgtransportsession.h"
+#include "dbgtransportmanager.h"
+
+
+DWORD GetProcessId(const DEBUG_EVENT * pEvent)
+{
+ return pEvent->dwProcessId;
+}
+DWORD GetThreadId(const DEBUG_EVENT * pEvent)
+{
+ return pEvent->dwThreadId;
+}
+
+// Get exception event
+BOOL IsExceptionEvent(const DEBUG_EVENT * pEvent, BOOL * pfFirstChance, const EXCEPTION_RECORD ** ppRecord)
+{
+ if (pEvent->dwDebugEventCode != EXCEPTION_DEBUG_EVENT)
+ {
+ *pfFirstChance = FALSE;
+ *ppRecord = NULL;
+ return FALSE;
+ }
+ *pfFirstChance = pEvent->u.Exception.dwFirstChance;
+ *ppRecord = &(pEvent->u.Exception.ExceptionRecord);
+ return TRUE;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// INativeEventPipeline is an abstraction over the Windows native debugging pipeline. This class is an
+// implementation which works over an SSL connection for debugging a target process on a Mac remotely.
+// It builds on top of code:DbgTransportTarget (which is a connection to the debugger proxy on the Mac) and
+// code:DbgTransportSession (which is a connection to the target process on the Mac). See
+// code:IEventChannel for more information.
+//
+// Assumptions:
+// This class is NOT thread-safe. Caller is assumed to have taken the appropriate measures for
+// synchronization.
+//
+
+class DbgTransportPipeline :
+ public INativeEventPipeline
+{
+public:
+ DbgTransportPipeline()
+ {
+ m_fRunning = FALSE;
+ m_hProcess = NULL;
+ m_pIPCEvent = reinterpret_cast<DebuggerIPCEvent * >(m_rgbIPCEventBuffer);
+ m_pProxy = NULL;
+ m_pTransport = NULL;
+ _ASSERTE(!IsTransportRunning());
+ }
+
+ virtual ~DbgTransportPipeline()
+ {
+ Dispose();
+ }
+
+ // Call to free up the pipeline.
+ virtual void Delete();
+
+ virtual BOOL DebugSetProcessKillOnExit(bool fKillOnExit);
+
+ // Create
+ virtual HRESULT CreateProcessUnderDebugger(
+ MachineInfo machineInfo,
+ LPCWSTR lpApplicationName,
+ LPCWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation);
+
+ // Attach
+ virtual HRESULT DebugActiveProcess(MachineInfo machineInfo, DWORD processId);
+
+ // Detach
+ virtual HRESULT DebugActiveProcessStop(DWORD processId);
+
+ // Block and wait for the next debug event from the debuggee process.
+ virtual BOOL WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess);
+
+ virtual BOOL ContinueDebugEvent(
+ DWORD dwProcessId,
+ DWORD dwThreadId,
+ DWORD dwContinueStatus
+ );
+
+ // Return a handle which will be signaled when the debuggee process terminates.
+ virtual HANDLE GetProcessHandle();
+
+ // Terminate the debuggee process.
+ virtual BOOL TerminateProcess(UINT32 exitCode);
+
+#ifdef FEATURE_PAL
+ virtual void CleanupTargetProcess()
+ {
+ m_pTransport->CleanupTargetProcess();
+ }
+#endif
+
+private:
+ // Return TRUE if the transport is up and runnning
+ BOOL IsTransportRunning()
+ {
+ return m_fRunning;
+ };
+
+ // clean up all resources
+ void Dispose()
+ {
+ if (m_hProcess != NULL)
+ {
+ CloseHandle(m_hProcess);
+ }
+ m_hProcess = NULL;
+
+ if (m_pTransport)
+ {
+ if (m_ticket.IsValid())
+ {
+ m_pTransport->StopUsingAsDebugger(&m_ticket);
+ }
+ m_pProxy->ReleaseTransport(m_pTransport);
+ }
+ m_pTransport = NULL;
+ m_pProxy = NULL;
+ }
+
+ BOOL m_fRunning;
+
+ DWORD m_dwProcessId;
+ // This is actually a handle to an event. This is only valid for waiting on process termination.
+ HANDLE m_hProcess;
+
+ DbgTransportTarget * m_pProxy;
+ DbgTransportSession * m_pTransport;
+
+ // Any buffer for storing a DebuggerIPCEvent must be at least CorDBIPC_BUFFER_SIZE big. For simplicity
+ // sake I have added an extra field member which points to the buffer.
+ DebuggerIPCEvent * m_pIPCEvent;
+ BYTE m_rgbIPCEventBuffer[CorDBIPC_BUFFER_SIZE];
+ DebugTicket m_ticket;
+};
+
+// Allocate and return a pipeline object for this platform
+INativeEventPipeline * NewPipelineForThisPlatform()
+{
+ return new (nothrow) DbgTransportPipeline();
+}
+
+// Call to free up the lpProcessInformationpeline.
+void DbgTransportPipeline::Delete()
+{
+ delete this;
+}
+
+// set whether to kill outstanding debuggees when the debugger exits.
+BOOL DbgTransportPipeline::DebugSetProcessKillOnExit(bool fKillOnExit)
+{
+ // This is not supported or necessary for Mac debugging. The only reason we need this on Windows is to
+ // ask the OS not to terminate the debuggee when the debugger exits. The Mac debugging pipeline doesn't
+ // automatically kill the debuggee when the debugger exits.
+ return TRUE;
+}
+
+// Create an process under the debugger.
+HRESULT DbgTransportPipeline::CreateProcessUnderDebugger(
+ MachineInfo machineInfo,
+ LPCWSTR lpApplicationName,
+ LPCWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation)
+{
+ // INativeEventPipeline has a 1:1 relationship with CordbProcess.
+ _ASSERTE(!IsTransportRunning());
+
+ // We don't support interop-debugging on the Mac.
+ _ASSERTE(!(dwCreationFlags & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS)));
+
+ // When we're using a transport we can't deal with creating a suspended process (we need the process to
+ // startup in order that it can start up a transport thread and reply to our messages).
+ _ASSERTE(!(dwCreationFlags & CREATE_SUSPENDED));
+
+ // Connect to the debugger proxy on the remote machine and ask it to create a process for us.
+ HRESULT hr = E_FAIL;
+
+ m_pProxy = g_pDbgTransportTarget;
+ hr = m_pProxy->CreateProcess(lpApplicationName,
+ lpCommandLine,
+ lpProcessAttributes,
+ lpThreadAttributes,
+ bInheritHandles,
+ dwCreationFlags,
+ lpEnvironment,
+ lpCurrentDirectory,
+ lpStartupInfo,
+ lpProcessInformation);
+
+ if (SUCCEEDED(hr))
+ {
+ // Establish a connection to the actual runtime to be debugged.
+ hr = m_pProxy->GetTransportForProcess(lpProcessInformation->dwProcessId,
+ &m_pTransport,
+ &m_hProcess);
+ if (SUCCEEDED(hr))
+ {
+ // Wait for the connection to become useable (or time out).
+ if (!m_pTransport->WaitForSessionToOpen(10000))
+ {
+ hr = CORDBG_E_TIMEOUT;
+ }
+ else
+ {
+ if (!m_pTransport->UseAsDebugger(&m_ticket))
+ {
+ hr = CORDBG_E_DEBUGGER_ALREADY_ATTACHED;
+ }
+ }
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ _ASSERTE((m_hProcess != NULL) && (m_hProcess != INVALID_HANDLE_VALUE));
+
+ m_dwProcessId = lpProcessInformation->dwProcessId;
+
+ // For Mac remote debugging, we don't actually have a process handle to hand back to the debugger.
+ // Instead, we return a handle to an event as the "process handle". The Win32 event thread also waits
+ // on this event handle, and the event will be signaled when the proxy notifies us that the process
+ // on the remote machine is terminated. However, normally the debugger calls CloseHandle() immediately
+ // on the "process handle" after CreateProcess() returns. Doing so causes the Win32 event thread to
+ // continue waiting on a closed event handle, and so it will never wake up.
+ // (In fact, in Whidbey, we also duplicate the process handle in code:CordbProcess::Init.)
+ if (!DuplicateHandle(GetCurrentProcess(),
+ m_hProcess,
+ GetCurrentProcess(),
+ &(lpProcessInformation->hProcess),
+ 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS
+ FALSE,
+ DUPLICATE_SAME_ACCESS))
+ {
+ hr = HRESULT_FROM_GetLastError();
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ m_fRunning = TRUE;
+ }
+ else
+ {
+ Dispose();
+ }
+
+ return hr;
+}
+
+// Attach the debugger to this process.
+HRESULT DbgTransportPipeline::DebugActiveProcess(MachineInfo machineInfo, DWORD processId)
+{
+ // INativeEventPipeline has a 1:1 relationship with CordbProcess.
+ _ASSERTE(!IsTransportRunning());
+
+ HRESULT hr = E_FAIL;
+
+ m_pProxy = g_pDbgTransportTarget;
+
+ // Establish a connection to the actual runtime to be debugged.
+ hr = m_pProxy->GetTransportForProcess(processId, &m_pTransport, &m_hProcess);
+ if (SUCCEEDED(hr))
+ {
+ // TODO: Pass this timeout as a parameter all the way from debugger
+ // Wait for the connection to become useable (or time out).
+ if (!m_pTransport->WaitForSessionToOpen(10000))
+ {
+ hr = CORDBG_E_TIMEOUT;
+ }
+ else
+ {
+ if (!m_pTransport->UseAsDebugger(&m_ticket))
+ {
+ hr = CORDBG_E_DEBUGGER_ALREADY_ATTACHED;
+ }
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ m_dwProcessId = processId;
+ m_fRunning = TRUE;
+ }
+ else
+ {
+ Dispose();
+ }
+
+ return hr;
+}
+
+// Detach
+HRESULT DbgTransportPipeline::DebugActiveProcessStop(DWORD processId)
+{
+ // The only way to tell the transport to detach from a process is by shutting it down.
+ // That will happen when we neuter the CordbProcess object.
+ return E_NOTIMPL;
+}
+
+// Block and wait for the next debug event from the debuggee process.
+BOOL DbgTransportPipeline::WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess)
+{
+ if (!IsTransportRunning())
+ {
+ return FALSE;
+ }
+
+ // We need to wait for a debug event from the transport and the process termination event.
+ // On Windows, process termination is communicated via a debug event as well, but that's not true for
+ // the Mac debugging transport.
+ DWORD cWaitSet = 2;
+ HANDLE rghWaitSet[2];
+ rghWaitSet[0] = m_pTransport->GetDebugEventReadyEvent();
+ rghWaitSet[1] = m_hProcess;
+
+ DWORD dwRet = ::WaitForMultipleObjectsEx(cWaitSet, rghWaitSet, FALSE, dwTimeout, FALSE);
+
+ if (dwRet == WAIT_OBJECT_0)
+ {
+ // The Mac debugging transport actually transmits IPC events and not debug events.
+ // We need to convert the IPC event to a debug event and pass it back to the caller.
+ m_pTransport->GetNextEvent(m_pIPCEvent, CorDBIPC_BUFFER_SIZE);
+
+ pEvent->dwProcessId = m_pIPCEvent->processId;
+ _ASSERTE(m_dwProcessId == m_pIPCEvent->processId);
+
+ // We are supposed to return a thread ID in the DEBUG_EVENT back to our caller.
+ // However, we don't actually store the thread ID in the DebuggerIPCEvent anymore. Instead,
+ // we just get a VMPTR_Thread, and so we need to find the thread ID associated with the VMPTR_Thread.
+ pEvent->dwThreadId = 0;
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (!m_pIPCEvent->vmThread.IsNull())
+ {
+ pEvent->dwThreadId = pProcess->GetDAC()->TryGetVolatileOSThreadID(m_pIPCEvent->vmThread);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ return FALSE;
+ }
+
+ // The Windows implementation stores the target address of the IPC event in the debug event.
+ // We can do that for Mac debugging, but that would require the caller to do another cross-machine
+ // ReadProcessMemory(). Since we have all the data in-proc already, we just store a local address.
+ //
+ // @dbgtodo Mac - We are using -1 as a dummy base address right now.
+ // Currently Mac remote debugging doesn't really support multi-instance.
+ InitEventForDebuggerNotification(pEvent, PTR_TO_CORDB_ADDRESS(reinterpret_cast<LPVOID>(-1)), m_pIPCEvent);
+
+ return TRUE;
+ }
+ else if (dwRet == (WAIT_OBJECT_0 + 1))
+ {
+ // The process has been terminated.
+
+ // We don't have a lot of information here.
+ pEvent->dwDebugEventCode = EXIT_PROCESS_DEBUG_EVENT;
+ pEvent->dwProcessId = m_dwProcessId;
+ pEvent->dwThreadId = 0; // On Windows this is the first thread created in the process.
+ pEvent->u.ExitProcess.dwExitCode = 0; // This is not passed back to us by the transport.
+
+ // Once the process termination event is signaled, we cannot send or receive any events.
+ // So we mark the transport as not running anymore.
+ m_fRunning = FALSE;
+ return TRUE;
+ }
+ else
+ {
+ // We may have timed out, or the actual wait operation may have failed.
+ // Either way, we don't have an event.
+ return FALSE;
+ }
+}
+
+BOOL DbgTransportPipeline::ContinueDebugEvent(
+ DWORD dwProcessId,
+ DWORD dwThreadId,
+ DWORD dwContinueStatus
+)
+{
+ if (!IsTransportRunning())
+ {
+ return FALSE;
+ }
+
+ // See code:INativeEventPipeline::ContinueDebugEvent.
+ return TRUE;
+}
+
+// Return a handle which will be signaled when the debuggee process terminates.
+HANDLE DbgTransportPipeline::GetProcessHandle()
+{
+ HANDLE hProcessTerminated;
+
+ if (!DuplicateHandle(GetCurrentProcess(),
+ m_hProcess,
+ GetCurrentProcess(),
+ &hProcessTerminated,
+ 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS
+ FALSE,
+ DUPLICATE_SAME_ACCESS))
+ {
+ return NULL;
+ }
+
+ // The handle returned here is only valid for waiting on process termination.
+ // See code:INativeEventPipeline::GetProcessHandle.
+ return hProcessTerminated;
+}
+
+// Terminate the debuggee process.
+BOOL DbgTransportPipeline::TerminateProcess(UINT32 exitCode)
+{
+ _ASSERTE(IsTransportRunning());
+
+ // The transport will still be running until the process termination handle is signaled.
+ m_pProxy->KillProcess(m_dwProcessId);
+ return TRUE;
+}
diff --git a/src/debug/di/dbi.sln b/src/debug/di/dbi.sln
new file mode 100644
index 0000000000..33cbec162c
--- /dev/null
+++ b/src/debug/di/dbi.sln
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dbi", "dbi.vcxproj", "{D8445C62-03DC-4D6A-A2F2-1AAF31577151}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {D8445C62-03DC-4D6A-A2F2-1AAF31577151}.Debug|Win32.ActiveCfg = Debug|Win32
+ {D8445C62-03DC-4D6A-A2F2-1AAF31577151}.Debug|Win32.Build.0 = Debug|Win32
+ {D8445C62-03DC-4D6A-A2F2-1AAF31577151}.Release|Win32.ActiveCfg = Release|Win32
+ {D8445C62-03DC-4D6A-A2F2-1AAF31577151}.Release|Win32.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/src/debug/di/dbi.vcxproj b/src/debug/di/dbi.vcxproj
new file mode 100644
index 0000000000..08533b7240
--- /dev/null
+++ b/src/debug/di/dbi.vcxproj
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{D8445C62-03DC-4D6A-A2F2-1AAF31577151}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>dbi</RootNamespace>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v110</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v110</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;DBI_EXPORTS;DBG_TARGET_X86;_TARGET_X86_;VS_COMPILE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\inc;..\..\inc;C:\clr_next\src\InternalApis\Sys_clr\inc;C:\CLR_Next\binaries\amd64chk\IntraPartitionAPIs\clr\inc;C:\CLR_Next\src\ndp\Common\Inc\version;C:\CLR_Next\binaries\amd64chk\SysBuild\Version;..\inc\i386;..\inc\dump</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;DBI_EXPORTS;DBG_TARGET_X86;_TARGET_X86_;VS_COMPILE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\inc;..\..\inc;C:\clr_next\src\InternalApis\Sys_clr\inc;C:\CLR_Next\binaries\amd64chk\IntraPartitionAPIs\clr\inc;C:\CLR_Next\src\ndp\Common\Inc\version;C:\CLR_Next\binaries\amd64chk\SysBuild\Version;;..\inc\i386</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClInclude Include="classfactory.h" />
+ <ClInclude Include="DbgTransportManager.h" />
+ <ClInclude Include="DDPack.h" />
+ <ClInclude Include="EventChannel.h" />
+ <ClInclude Include="EventRedirectionPipeline.h" />
+ <ClInclude Include="helpers.h" />
+ <ClInclude Include="NativePipeline.h" />
+ <ClInclude Include="RsEnumerator.hpp" />
+ <ClInclude Include="RSPriv.h" />
+ <ClInclude Include="ShimDataTarget.h" />
+ <ClInclude Include="shimpriv.h" />
+ <ClInclude Include="StdAfx.h" />
+ <ClInclude Include="symbolinfo.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="RSPriv.inl" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="breakpoint.cpp" />
+ <ClCompile Include="cordb.cpp" />
+ <ClCompile Include="DbgTransportManager.cpp" />
+ <ClCompile Include="DbgTransportPipeline.cpp" />
+ <ClCompile Include="DDPack.cpp" />
+ <ClCompile Include="DIValue.cpp" />
+ <ClCompile Include="EventRedirectionPipeline.cpp" />
+ <ClCompile Include="hash.cpp" />
+ <ClCompile Include="i386\CordbRegisterSet.cpp" />
+ <ClCompile Include="i386\primitives.cpp" />
+ <ClCompile Include="LocalEventChannel.cpp" />
+ <ClCompile Include="module.cpp" />
+ <ClCompile Include="NativePipeline.cpp" />
+ <ClCompile Include="PlatformSpecific.cpp" />
+ <ClCompile Include="process.cpp" />
+ <ClCompile Include="publish.cpp" />
+ <ClCompile Include="RemoteEventChannel.cpp" />
+ <ClCompile Include="RotorPipeline.cpp" />
+ <ClCompile Include="RsAppDomain.cpp" />
+ <ClCompile Include="RsAssembly.cpp" />
+ <ClCompile Include="rsclass.cpp" />
+ <ClCompile Include="rsfunction.cpp" />
+ <ClCompile Include="RsMain.cpp" />
+ <ClCompile Include="RsMda.cpp" />
+ <ClCompile Include="RSRegSetCommon.cpp" />
+ <ClCompile Include="RsStackWalk.cpp" />
+ <ClCompile Include="RsThread.cpp" />
+ <ClCompile Include="RsType.cpp" />
+ <ClCompile Include="shared.cpp" />
+ <ClCompile Include="shimcallback.cpp" />
+ <ClCompile Include="ShimDataTarget.cpp" />
+ <ClCompile Include="ShimEvents.cpp" />
+ <ClCompile Include="ShimLocalDataTarget.cpp" />
+ <ClCompile Include="ShimProcess.cpp" />
+ <ClCompile Include="ShimRemoteDataTarget.cpp" />
+ <ClCompile Include="ShimStackWalk.cpp" />
+ <ClCompile Include="StdAfx.cpp" />
+ <ClCompile Include="symbolinfo.cpp" />
+ <ClCompile Include="ValueHome.cpp" />
+ <ClCompile Include="WindowsPipeline.cpp" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/src/debug/di/dirs.proj b/src/debug/di/dirs.proj
new file mode 100644
index 0000000000..c5a98947e5
--- /dev/null
+++ b/src/debug/di/dirs.proj
@@ -0,0 +1,18 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <!--Import the settings-->
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" />
+
+ <PropertyGroup>
+ <BuildInPhase1>true</BuildInPhase1>
+ <BuildInPhaseDefault>false</BuildInPhaseDefault>
+ <BuildCoreBinaries>true</BuildCoreBinaries>
+ </PropertyGroup>
+
+ <!--The following projects will build during PHASE 1-->
+ <ItemGroup Condition="'$(BuildExePhase)' == '1'">
+ <ProjectFile Condition="'$(FeatureDbiDebugging)'=='true'" Include="hostlocal\di.nativeproj" />
+ </ItemGroup>
+
+ <!--Import the targets-->
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\tools\Microsoft.DevDiv.Traversal.targets" />
+</Project>
diff --git a/src/debug/di/divalue.cpp b/src/debug/di/divalue.cpp
new file mode 100644
index 0000000000..50ecd68aa9
--- /dev/null
+++ b/src/debug/di/divalue.cpp
@@ -0,0 +1,4564 @@
+// 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.
+//*****************************************************************************
+// File: DIValue.cpp
+//
+
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "primitives.h"
+
+// copy from a MemoryRange to dest
+// Arguments:
+// input: source - MemoryRange describing the start address and size of the source buffer
+// output: dest - address of the buffer to which the source buffer is copied
+// Note: the buffer for dest must be allocated by the caller and must be large enough to hold the
+// bytes from the source buffer.
+void localCopy(void * dest, MemoryRange source)
+{
+ _ASSERTE(dest != NULL);
+ _ASSERTE(source.StartAddress() != NULL);
+
+ memcpy(dest, source.StartAddress(), source.Size());
+}
+
+// for an inheritance graph of the ICDValue types, // See file:./ICorDebugValueTypes.vsd for a diagram of the types.
+
+/* ------------------------------------------------------------------------- *
+ * CordbValue class
+ * ------------------------------------------------------------------------- */
+
+CordbValue::CordbValue(CordbAppDomain * appdomain,
+ CordbType * type,
+ CORDB_ADDRESS id,
+ bool isLiteral,
+ NeuterList * pList)
+ : CordbBase(
+ ((appdomain != NULL) ? (appdomain->GetProcess()) : (type->GetProcess())),
+ (UINT_PTR)id, enumCordbValue),
+ m_appdomain(appdomain),
+ m_type(type), // implicit InternalAddRef
+ //m_sigCopied(false),
+ m_size(0),
+ m_isLiteral(isLiteral)
+{
+ HRESULT hr = S_OK;
+
+ _ASSERTE(GetProcess() != NULL);
+
+ // Add to a neuter list. If none is provided, use the ExitProcess list as a default.
+ // The main neuter lists of interest here are:
+ // - CordbProcess::GetContinueNeuterList() - Shortest. Neuter when the process continues.
+ // - CordbAppDomain::GetExitNeuterList() - Middle. Neuter when the AD exits. Since most Values (except globals) are in
+ // a specific AD, this almost catches all; and keeps us safe in AD-unload scenarios.
+ // - CordbProcess::GetExitNeuterList() - Worst. Doesn't neuter until the process exits (or we detach).
+ // This could be a long time.
+ if (pList == NULL)
+ {
+ pList = GetProcess()->GetExitNeuterList();
+ }
+
+
+ EX_TRY
+ {
+ pList->Add(GetProcess(), this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+} // CordbValue::CordbValue
+
+CordbValue::~CordbValue()
+{
+ DTOR_ENTRY(this);
+
+ _ASSERTE(this->IsNeutered());
+
+ _ASSERTE(m_type == NULL);
+} // CordbValue::~CordbValue
+
+void CordbValue::Neuter()
+{
+ m_appdomain = NULL;
+ m_type.Clear();
+
+ ValueHome * pValueHome = GetValueHome();
+ if (pValueHome != NULL)
+ {
+ pValueHome->Clear();
+ }
+ CordbBase::Neuter();
+} // CordbValue::Neuter
+
+// Helper for code:CordbValue::CreateValueByType. Create a new instance of CordbGenericValue
+// Arguments:
+// input: pAppdomain - appdomain to which the value belongs
+// pType - type of the value
+// remoteValue - remote address and size of the value
+// localValue - local address and size of the value
+// ppRemoteRegAddr - register address of the value
+// output: ppValue - the newly created instance of an ICDValue
+// Notes:
+// - only one of the three locations will be non-NULL
+// - Throws
+/* static */
+void CordbValue::CreateGenericValue(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ TargetBuffer remoteValue,
+ MemoryRange localValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr,
+ ICorDebugValue** ppValue)
+{
+ LOG((LF_CORDB,LL_INFO100000,"CV::CreateValueByType CreateGenericValue\n"));
+ RSSmartPtr<CordbGenericValue> pGenValue;
+ // A generic value
+ // By using a RSSmartPtr we ensure that in both success and failure cases,
+ // this object is cleaned up properly (deleted or not depending on ref counts).
+ // Specifically, the object has probably been placed on a neuter list so we
+ // can't delete it (but this is a detail we shouldn't rely on)
+ pGenValue.Assign(new CordbGenericValue(pAppdomain,
+ pType,
+ remoteValue,
+ ppRemoteRegAddr));
+
+ pGenValue->Init(localValue); // throws
+
+ pGenValue->AddRef();
+ *ppValue = (ICorDebugValue *)(ICorDebugGenericValue *)pGenValue;
+} // CordbValue::CreateGenericValue
+
+// create a new instance of CordbVCObjectValue or CordbReferenceValue
+// Arguments:
+// input: pAppdomain - appdomain to which the value belongs
+// pType - type of the value
+// boxed - indicates whether the value is boxed
+// remoteValue - remote address and size of the value
+// localValue - local address and size of the value
+// ppRemoteRegAddr - register address of the value
+// output: ppValue - the newly created instance of an ICDValue
+// Notes:
+// - only one of the three locations will be non-NULL
+// - Throws error codes from reading process memory
+/* static */
+void CordbValue::CreateVCObjOrRefValue(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ bool boxed,
+ TargetBuffer remoteValue,
+ MemoryRange localValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr,
+ ICorDebugValue** ppValue)
+
+{
+ HRESULT hr = S_OK;
+ LOG((LF_CORDB,LL_INFO1000000,"CV::CreateValueByType Creating ReferenceValue\n"));
+
+ // We either have a boxed or unboxed value type, or we have a value that's not a value type.
+ // For an unboxed value type, we'll create an instance of CordbVCObjectValue. Otherwise, we'll
+ // create an instance of CordbReferenceValue.
+
+ // do we have a value type?
+ bool isVCObject = pType->IsValueType(); // throws
+
+ if (!boxed && isVCObject)
+ {
+ RSSmartPtr<CordbVCObjectValue> pVCValue(new CordbVCObjectValue(pAppdomain,
+ pType,
+ remoteValue,
+ ppRemoteRegAddr));
+
+ IfFailThrow(pVCValue->Init(localValue));
+
+ pVCValue->AddRef();
+ *ppValue = (ICorDebugValue*)(ICorDebugObjectValue*)pVCValue;
+ }
+ else
+ {
+ // either the value is boxed or it's not a value type
+ RSSmartPtr<CordbReferenceValue> pRef;
+ hr = CordbReferenceValue::Build(pAppdomain,
+ pType,
+ remoteValue,
+ localValue,
+ VMPTR_OBJECTHANDLE::NullPtr(),
+ ppRemoteRegAddr, // Home
+ &pRef);
+ IfFailThrow(hr);
+ hr = pRef->QueryInterface(__uuidof(ICorDebugValue), (void**)ppValue);
+ _ASSERTE(SUCCEEDED(hr));
+ }
+} // CordbValue::CreateVCObjOrRefValue
+
+//
+// Create the proper ICDValue instance based on the given element type.
+// Arguments:
+// input: pAppdomain - appdomain to which the value belongs
+// pType - type of the value
+// boxed - indicates whether the value is boxed
+// remoteValue - remote address and size of the value
+// localValue - local address and size of the value
+// ppRemoteRegAddr - register address of the value
+// output: ppValue - the newly created instance of an ICDValue
+// Notes:
+// - Only one of the three locations, remoteValue, localValue or ppRemoteRegAddr, will be non-NULL.
+// - Throws.
+/*static*/ void CordbValue::CreateValueByType(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ bool boxed,
+ TargetBuffer remoteValue,
+ MemoryRange localValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr,
+ ICorDebugValue** ppValue)
+{
+ INTERNAL_SYNC_API_ENTRY(pAppdomain->GetProcess()); //
+
+ // We'd really hope that our callers give us a valid appdomain, but in case
+ // they don't, we'll fail gracefully.
+ if ((pAppdomain != NULL) && pAppdomain->IsNeutered())
+ {
+ STRESS_LOG1(LF_CORDB, LL_EVERYTHING, "CVBT using neutered AP, %p\n", pAppdomain);
+ ThrowHR(E_INVALIDARG);
+ }
+
+ LOG((LF_CORDB,LL_INFO100000,"CV::CreateValueByType\n"));
+
+ *ppValue = NULL;
+
+ switch(pType->m_elementType)
+ {
+ case ELEMENT_TYPE_BOOLEAN:
+ case ELEMENT_TYPE_CHAR:
+ case ELEMENT_TYPE_I1:
+ case ELEMENT_TYPE_U1:
+ case ELEMENT_TYPE_I2:
+ case ELEMENT_TYPE_U2:
+ case ELEMENT_TYPE_I4:
+ case ELEMENT_TYPE_U4:
+ case ELEMENT_TYPE_R4:
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_U8:
+ case ELEMENT_TYPE_R8:
+ case ELEMENT_TYPE_I:
+ case ELEMENT_TYPE_U:
+ {
+ CreateGenericValue(pAppdomain, pType, remoteValue, localValue, ppRemoteRegAddr, ppValue); // throws
+ break;
+ }
+
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_OBJECT:
+ case ELEMENT_TYPE_STRING:
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_BYREF:
+ case ELEMENT_TYPE_TYPEDBYREF:
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ case ELEMENT_TYPE_FNPTR:
+ {
+ CreateVCObjOrRefValue(pAppdomain, pType, boxed, remoteValue, localValue, ppRemoteRegAddr, ppValue); // throws
+ break;
+ }
+
+ default:
+ _ASSERTE(!"Bad value type!");
+ ThrowHR(E_FAIL);
+ }
+} // CordbValue::CreateValueByType
+
+// Create the proper ICDValue instance based on the given remote heap object
+// Arguments:
+// pAppDomain - the app domain the remote object is in
+// vmObj - the remote object to get an ICDValue for
+ICorDebugValue* CordbValue::CreateHeapValue(CordbAppDomain* pAppDomain, VMPTR_Object vmObj)
+{
+ IDacDbiInterface* pDac = pAppDomain->GetProcess()->GetDAC();
+
+ TargetBuffer objBuffer = pDac->GetObjectContents(vmObj);
+ VOID* pRemoteAddr = CORDB_ADDRESS_TO_PTR(objBuffer.pAddress);
+ // This creates a local reference that has a remote address in it. Ie &pRemoteAddr is an address
+ // in the host address space and pRemoteAddr is an address in the target.
+ MemoryRange localReferenceDescription(&pRemoteAddr, sizeof(pRemoteAddr));
+ RSSmartPtr<CordbReferenceValue> pRefValue;
+ IfFailThrow(CordbReferenceValue::Build(pAppDomain,
+ NULL,
+ EMPTY_BUFFER,
+ localReferenceDescription,
+ VMPTR_OBJECTHANDLE::NullPtr(),
+ NULL,
+ &pRefValue));
+
+ // Dereference our temporary reference value to construct the heap value we want
+ ICorDebugValue* pExtValue;
+ IfFailThrow(pRefValue->Dereference(&pExtValue));
+ return pExtValue;
+}
+
+// Gets the size om bytes of a value from its type. If the value is complex, we assume it is represented as
+// a reference, since this is called for values that have been found on the stack, as an element of an
+// array (represented as CordbArrayValue) or field of an object (CordbObjectValue) or the result of a
+// func eval. For unboxed value types, we get the size of the entire value (it is not represented as a
+// reference).
+// Examples:
+// - int on the stack
+// => sizeof(int)
+// - int as a field in an object on the heap
+// =>sizeof(int)
+// - Boxed int on the heap
+// => size of a pointer
+// - Class Point { int x; int y}; // class will have a method table / object header which may increase size.
+// => size of a pointer
+// - Struct Point {int x; int y; }; // unboxed struct may not necessarily have the object header.
+// => 2 * sizeof(int)
+// - List<int>
+// => size of a pointer
+// Arguments: pType - the type of the value
+// boxing - indicates whether the value is boxed or not
+// Return Value: the size of the value
+// Notes: Throws
+// In general, this returns the unboxed size of the value, but if we have a type
+// that represents a non-generic and it's not an unboxed value type, we know that
+// it will be represented as a reference, so we return the size of a pointer instead.
+/* static */
+ULONG32 CordbValue::GetSizeForType(CordbType * pType, BoxedValue boxing)
+{
+ ULONG32 size = 0;
+
+ switch(pType->m_elementType)
+ {
+ case ELEMENT_TYPE_BOOLEAN:
+ case ELEMENT_TYPE_CHAR:
+ case ELEMENT_TYPE_I1:
+ case ELEMENT_TYPE_U1:
+ case ELEMENT_TYPE_I2:
+ case ELEMENT_TYPE_U2:
+ case ELEMENT_TYPE_I4:
+ case ELEMENT_TYPE_U4:
+ case ELEMENT_TYPE_R4:
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_U8:
+ case ELEMENT_TYPE_R8:
+ case ELEMENT_TYPE_I:
+ case ELEMENT_TYPE_U: pType->GetUnboxedObjectSize(&size); break;
+
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_OBJECT:
+ case ELEMENT_TYPE_STRING:
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_BYREF:
+ case ELEMENT_TYPE_TYPEDBYREF:
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ case ELEMENT_TYPE_FNPTR: {
+ bool isUnboxedVCObject = false;
+
+ if (boxing == kUnboxed)
+ {
+ isUnboxedVCObject = pType->IsValueType(); // throws
+ }
+ if (!isUnboxedVCObject)
+ {
+ // if it's not an unboxed value type (we're in the case
+ // for compound types), then it's a reference
+ // and we just want to return the size of a pointer
+ size = sizeof(void *);
+ }
+ else
+ {
+ pType->GetUnboxedObjectSize(&size);
+ }
+ } break;
+
+ default:
+ _ASSERTE(!"Bad value type!");
+}
+ return size;
+} // CordbValue::GetSizeForType
+
+
+HRESULT CordbValue::CreateBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint)
+{
+ VALIDATE_POINTER_TO_OBJECT(ppBreakpoint, ICorDebugValueBreakpoint **);
+
+ return E_NOTIMPL;
+} // CordbValue::CreateBreakpoint
+
+// gets the exact type of a value
+// Arguments:
+// input: none (uses m_type field)
+// output: ppType - an instance of ICDType representing the exact type of the value
+// Return Value:
+HRESULT CordbValue::GetExactType(ICorDebugType **ppType)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppType, ICorDebugType **);
+ FAIL_IF_NEUTERED(this);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ *ppType = static_cast<ICorDebugType*> (m_type);
+
+ if (*ppType != NULL)
+ (*ppType)->AddRef();
+
+ return S_OK;
+} // CordbValue::GetExactType
+
+// CreateHandle for a heap object.
+// @todo: How to prevent this being called by non-heap object?
+// Arguments:
+// input: handleType - type of the handle to be created
+// output: ppHandle - on success, the newly created handle
+// Return Value: S_OK on success or E_INVALIDARG, E_OUTOFMEMORY, or CORDB_E_HELPER_MAY_DEADLOCK
+HRESULT CordbValue::InternalCreateHandle(CorDebugHandleType handleType,
+ ICorDebugHandleValue ** ppHandle)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+ LOG((LF_CORDB,LL_INFO1000,"CV::CreateHandle\n"));
+
+ DebuggerIPCEvent event;
+ CordbProcess *process;
+ BOOL fStrong = FALSE;
+
+ // @dbgtodo- , as part of inspection, convert this path to throwing.
+ if (ppHandle == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ *ppHandle = NULL;
+
+ if (handleType == HANDLE_STRONG)
+ {
+ fStrong = TRUE;
+ }
+ else
+ {
+ _ASSERTE(handleType == HANDLE_WEAK_TRACK_RESURRECTION);
+ }
+
+
+ // Create the ICorDebugHandleValue object
+ RSInitHolder<CordbHandleValue> pHandle(new (nothrow) CordbHandleValue(m_appdomain, m_type, handleType) );
+
+ if (pHandle == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ // Send the event to create the handle.
+ process = m_appdomain->GetProcess();
+ _ASSERTE(process != NULL);
+
+ process->InitIPCEvent(&event,
+ DB_IPCE_CREATE_HANDLE,
+ true,
+ m_appdomain->GetADToken());
+
+ CORDB_ADDRESS addr = GetValueHome() != NULL ? GetValueHome()->GetAddress() : NULL;
+ event.CreateHandle.objectToken = CORDB_ADDRESS_TO_PTR(addr);
+ event.CreateHandle.fStrong = fStrong;
+
+ // Note: two-way event here...
+ HRESULT hr = process->SendIPCEvent(&event, sizeof(DebuggerIPCEvent));
+ hr = WORST_HR(hr, event.hr);
+
+ if (SUCCEEDED(hr))
+ {
+ _ASSERTE(event.type == DB_IPCE_CREATE_HANDLE_RESULT);
+
+ // Initialize the handle value object.
+ hr = pHandle->Init(event.CreateHandleResult.vmObjectHandle);
+ }
+
+ if (!SUCCEEDED(hr))
+ {
+ // Free the handle from the left-side.
+ pHandle->Dispose();
+
+ // The RSInitHolder will neuter and delete it.
+ return hr;
+ }
+
+ // Pass out the new handle value object.
+ pHandle.TransferOwnershipExternal(ppHandle);
+
+ return S_OK;
+} // CordbValue::InternalCreateHandle
+
+/* ------------------------------------------------------------------------- *
+ * Generic Value class
+ * ------------------------------------------------------------------------- */
+
+//
+// CordbGenericValue constructor that builds a generic value from
+// a remote address or register.
+// Arguments:
+// input: pAppdomain - the app domain to which the value belongs
+// pType - the type of the value
+// remoteValue - buffer (and size) of the remote location where
+// the value resides. This may be NULL if the value
+// is enregistered.
+// ppRemoteRegAddr - information describing the register in which the
+// value resides. This may be NULL--only one of
+// ppRemoteRegAddr and remoteValue will be non-NULL,
+// depending on whether the value is in a register or
+// memory.
+CordbGenericValue::CordbGenericValue(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ TargetBuffer remoteValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr)
+ : CordbValue(pAppdomain, pType, remoteValue.pAddress, false),
+ m_pValueHome(NULL)
+{
+ _ASSERTE(pType->m_elementType != ELEMENT_TYPE_END);
+ _ASSERTE(pType->m_elementType != ELEMENT_TYPE_VOID);
+ _ASSERTE(pType->m_elementType < ELEMENT_TYPE_MAX);
+
+ // We can fill in the size now for generic values.
+ ULONG32 size;
+ HRESULT hr;
+ hr = pType->GetUnboxedObjectSize(&size);
+ _ASSERTE (!FAILED(hr));
+ m_size = size;
+
+ // now instantiate the value home
+ NewHolder<ValueHome> pHome(NULL);
+ if (remoteValue.IsEmpty())
+ {
+ pHome = (new RegisterValueHome(pAppdomain->GetProcess(), ppRemoteRegAddr));
+ }
+ else
+ {
+ pHome = (new RemoteValueHome(pAppdomain->GetProcess(), remoteValue));
+ }
+ m_pValueHome = pHome.GetValue(); // throws
+ pHome.SuppressRelease();
+} // CordbGenericValue::CordbGenericValue
+
+//
+// CordbGenericValue constructor that builds an empty generic value
+// from just an element type. Used for literal values for func evals
+// only.
+// Arguments:
+// input: pType - the type of the value
+CordbGenericValue::CordbGenericValue(CordbType * pType)
+ : CordbValue(NULL, pType, NULL, true),
+ m_pValueHome(NULL)
+{
+ // The only purpose of a literal value is to hold a RS literal value.
+ ULONG32 size;
+ HRESULT hr;
+ hr = pType->GetUnboxedObjectSize(&size);
+ _ASSERTE (!FAILED(hr));
+ m_size = size;
+
+ memset(m_pCopyOfData, 0, m_size);
+
+ // there is no value home for a literal so we leave it as NULL
+} // CordbGenericValue::CordbGenericValue
+
+// destructor
+CordbGenericValue::~CordbGenericValue()
+{
+ if (m_pValueHome != NULL)
+ {
+ delete m_pValueHome;
+ m_pValueHome = NULL;
+}
+} // CordbGenericValue::~CordbGenericValue
+
+HRESULT CordbGenericValue::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugValue)
+ {
+ *pInterface = static_cast<ICorDebugValue*>(static_cast<ICorDebugGenericValue*>(this));
+ }
+ else if (id == IID_ICorDebugValue2)
+ {
+ *pInterface = static_cast<ICorDebugValue2*>(this);
+ }
+ else if (id == IID_ICorDebugValue3)
+ {
+ *pInterface = static_cast<ICorDebugValue3*>(this);
+ }
+ else if (id == IID_ICorDebugGenericValue)
+ {
+ *pInterface = static_cast<ICorDebugGenericValue*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugGenericValue*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+} // CordbGenericValue::QueryInterface
+
+//
+// initialize a generic value by copying the necessary data, either
+// from the remote process or from another value in this process.
+// Argument:
+// input: localValue - RS location of value to be copied. This could be NULL or it
+// could be a field from the cached copy of a CordbVCObjectValue or CordbObjectValue
+// instance or an element from the cached copy of a CordbArrayValue instance
+// Note: Throws error codes from reading process memory
+void CordbGenericValue::Init(MemoryRange localValue)
+{
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess());
+
+ if(!m_isLiteral)
+ {
+ // If neither localValue.StartAddress nor m_remoteValue.pAddress are set, then all that means
+ // is that we've got a pre-initialized 64-bit value.
+ if (localValue.StartAddress() != NULL)
+ {
+ // Copy the data out of the local address space.
+ localCopy(m_pCopyOfData, localValue);
+ }
+ else
+ {
+ m_pValueHome->GetValue(MemoryRange(m_pCopyOfData, m_size)); // throws
+ }
+ }
+} // CordbGenericValue::Init
+
+// gets the value (i.e., number, boolean or pointer value) for this instance of CordbGenericValue
+// Arguments:
+// output: pTo - the starting address of a buffer in which the value will be written. This buffer must
+// be guaranteed by the caller to be large enough to hold the value. There is no way for
+// us to check here if it is. This must be non-NULL.
+// Return Value: S_OK on success or E_INVALIDARG if the pTo is NULL
+HRESULT CordbGenericValue::GetValue(void *pTo)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pTo, BYTE, m_size, false, true);
+
+ _ASSERTE(m_pCopyOfData != NULL);
+ // Copy out the value
+ memcpy(pTo, m_pCopyOfData, m_size);
+
+ return S_OK;
+} // CordbGenericValue::GetValue
+
+// Sets the value of this instance of CordbGenericValue
+// Arguments:
+// input: pFrom - pointer to a buffer holding the new value. We assume this is the same size as the
+// original value; we have no way to check. This must be non-NULL.
+// Return Value: S_OK on success or E_INVALIDARG if the pFrom is NULL
+HRESULT CordbGenericValue::SetValue(void *pFrom)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pFrom, BYTE, m_size, true, false);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // We only need to send to the left side to update values that are
+ // object references. For generic values, we can simply do a write
+ // memory.
+
+ EX_TRY
+ {
+ if(!m_isLiteral)
+ {
+ m_pValueHome->SetValue(MemoryRange(pFrom, m_size), m_type); // throws
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+
+ // That worked, so update the copy of the value we have in
+ // m_copyOfData.
+ memcpy(m_pCopyOfData, pFrom, m_size);
+
+ return hr;
+} // CordbGenericValue::SetValue
+
+// copies the value from this instance of CordbGenericValue iff the value represents a literal
+// Arguments:
+// output: pBuffer - pointer to the beginning of a caller-allocated buffer.This buffer must
+// be guaranteed by the caller to be large enough to hol
+// d the value. There is no way for
+// us to check here if it is. This must be non-NULL.
+// Return Value: true iff this is a literal value and pBuffer is a valid writeable address
+bool CordbGenericValue::CopyLiteralData(BYTE *pBuffer)
+{
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess());
+ _ASSERTE(pBuffer != NULL);
+
+ // If this is a RS fabrication, copy the literal data into the
+ // given buffer and return true.
+ if (m_isLiteral)
+ {
+ _ASSERTE(m_size <= 8);
+ memcpy(pBuffer, m_pCopyOfData, m_size);
+ return true;
+ }
+ else
+ return false;
+} // CordbGenericValue::CopyLiteralData
+
+/* ------------------------------------------------------------------------- *
+ * Reference Value class
+ * ------------------------------------------------------------------------- */
+
+// constructor
+// Arguments:
+// input: pAppdomain - appdomain to which the value belongs
+// pType - the type of the referent (the object pointed to)
+// localValue - the RS address and size of the buffer from which the reference
+// will be copied. This will be NULL if either remoteValue,
+// ppRemoteRegAddr or vmObjectHandle is non-NULL. Otherwise, it will
+// point into the local cached copy of another instance of ICDValue
+// remoteValue - the LS address and size of the buffer from which the reference
+// will be copied. This will be NULL if either localValue,
+// ppRemoteRegAddr, or vmObjectHandle is non-NULL.
+// ppRemoteRegAddr - information about the register location of the buffer from which
+// the reference will be copied. This will be NULL if either localValue,
+// remoteValue, or vmObjectHandle is non-NULL.
+// vmObjectHandle - a LS object handle holding the reference. This will be NULL if either
+// localValue, remoteValue, or ppRemoteRegAddr is non-NULL.
+// Note: this may throw OOM
+CordbReferenceValue::CordbReferenceValue(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ MemoryRange localValue,
+ TargetBuffer remoteValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr,
+ VMPTR_OBJECTHANDLE vmObjectHandle)
+ : CordbValue(pAppdomain, pType, remoteValue.pAddress, false,
+ // We'd like to change this to be a ContinueList so it gets neutered earlier,
+ // but it may be a breaking change
+ pAppdomain->GetSweepableExitNeuterList()),
+
+ m_realTypeOfTypedByref(NULL)
+{
+ memset(&m_info, 0, sizeof(m_info));
+
+ LOG((LF_CORDB,LL_EVERYTHING,"CRV::CRV: this:0x%x\n",this));
+ m_size = sizeof(void *);
+
+ // now instantiate the value home
+ NewHolder<ValueHome> pHome(NULL);
+
+ if (!vmObjectHandle.IsNull())
+ {
+ pHome = (new HandleValueHome(pAppdomain->GetProcess(), vmObjectHandle));
+ m_valueHome.SetObjHandleFlag(false);
+ }
+
+ else if (remoteValue.IsEmpty())
+ {
+ pHome = (new RegisterValueHome(pAppdomain->GetProcess(), ppRemoteRegAddr));
+ m_valueHome.SetObjHandleFlag(true);
+
+ }
+ else
+ {
+ pHome = (new RefRemoteValueHome(pAppdomain->GetProcess(), remoteValue));
+}
+ m_valueHome.m_pHome = pHome.GetValue(); // throws
+ pHome.SuppressRelease();
+} // CordbReferenceValue::CordbReferenceValue
+
+// CordbReferenceValue constructor that builds an empty reference value
+// from just an element type. Used for literal values for func evals
+// only.
+// Arguments:
+// input: pType - the type of the value
+CordbReferenceValue::CordbReferenceValue(CordbType * pType)
+ : CordbValue(NULL, pType, NULL, true, pType->GetAppDomain()->GetSweepableExitNeuterList())
+{
+ memset(&m_info, 0, sizeof(m_info));
+
+ // The only purpose of a literal value is to hold a RS literal value.
+ m_size = sizeof(void*);
+
+ // there is no value home for a literal
+ m_valueHome.m_pHome = NULL;
+} // CordbReferenceValue::CordbReferenceValue
+
+// copies the value from this instance of CordbReferenceValue iff the value represents a literal
+// Arguments:
+// output: pBuffer - pointer to the beginning of a caller-allocated buffer.This buffer must
+// be guaranteed by the caller to be large enough to hold the value.
+// There is no way for us to check here if it is. This must be non-NULL.
+// Return Value: true iff this is a literal value and pBuffer is a valid writeable address
+bool CordbReferenceValue::CopyLiteralData(BYTE *pBuffer)
+{
+ _ASSERTE(pBuffer != NULL);
+
+ // If this is a RS fabrication, then its a null reference.
+ if (m_isLiteral)
+ {
+ void *n = NULL;
+ memcpy(pBuffer, &n, sizeof(n));
+ return true;
+ }
+ else
+ return false;
+} // CordbReferenceValue::CopyLiteralData
+
+// destructor
+CordbReferenceValue::~CordbReferenceValue()
+{
+ DTOR_ENTRY(this);
+
+ LOG((LF_CORDB,LL_EVERYTHING,"CRV::~CRV: this:0x%x\n",this));
+
+ _ASSERTE(IsNeutered());
+} // CordbReferenceValue::~CordbReferenceValue
+
+void CordbReferenceValue::Neuter()
+{
+ if (m_valueHome.m_pHome != NULL)
+ {
+ m_valueHome.m_pHome->Clear();
+ delete m_valueHome.m_pHome;
+ m_valueHome.m_pHome = NULL;
+ }
+
+ m_realTypeOfTypedByref = NULL;
+ CordbValue::Neuter();
+} // CordbReferenceValue::Neuter
+
+
+HRESULT CordbReferenceValue::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugValue)
+ {
+ *pInterface = static_cast<ICorDebugValue*>(static_cast<ICorDebugReferenceValue*>(this));
+ }
+ else if (id == IID_ICorDebugValue2)
+ {
+ *pInterface = static_cast<ICorDebugValue2*>(this);
+ }
+ else if (id == IID_ICorDebugValue3)
+ {
+ *pInterface = static_cast<ICorDebugValue3*>(this);
+ }
+ else if (id == IID_ICorDebugReferenceValue)
+ {
+ *pInterface = static_cast<ICorDebugReferenceValue*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugReferenceValue*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+} // CordbReferenceValue::QueryInterface
+
+// gets the type of the referent of the object ref
+// Arguments:
+// output: pType - the type of the value. The caller must guarantee that pType is non-null.
+// Return Value: S_OK on success, E_INVALIDARG on failure
+HRESULT CordbReferenceValue::GetType(CorElementType *pType)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pType, CorElementType *);
+
+ if( m_type == NULL )
+ {
+ // We may not have a CordbType if we were created from a GC handle to NULL
+ _ASSERTE( m_info.objTypeData.elementType == ELEMENT_TYPE_CLASS );
+ _ASSERTE(!m_valueHome.ObjHandleIsNull());
+ _ASSERTE( m_info.objRef == NULL );
+ *pType = m_info.objTypeData.elementType;
+ }
+ else
+ {
+ // The element type stored in both places should match
+ _ASSERTE( m_info.objTypeData.elementType == m_type->m_elementType );
+ *pType = m_type->m_elementType;
+ }
+
+ return S_OK;
+} // CordbReferenceValue::GetType
+
+// gets the remote (LS) address of the reference. This may return NULL if the
+// reference is a literal or resides in a register.
+// Arguments:
+// output: pAddress - the LS location of the reference. The caller must guarantee pAddress is non-null,
+// but the contents may be null after the call if the reference is enregistered or is
+// the value of a field or element of some other Cordb*Value instance.
+// Return Value: S_OK on success or E_INVALIDARG if pAddress is null
+HRESULT CordbReferenceValue::GetAddress(CORDB_ADDRESS *pAddress)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pAddress, CORDB_ADDRESS *);
+
+ *pAddress = m_valueHome.m_pHome ? m_valueHome.m_pHome->GetAddress() : NULL;
+ return (S_OK);
+}
+
+// Determines whether the reference is null
+// Arguments:
+// output - pfIsNull - pointer to a BOOL that will be set to true iff this represents a
+// null reference
+// Return Value: S_OK on success or E_INVALIDARG if pfIsNull is null
+HRESULT CordbReferenceValue::IsNull(BOOL * pfIsNull)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pfIsNull, BOOL *);
+
+ if (m_isLiteral || (m_info.objRef == NULL))
+ *pfIsNull = TRUE;
+ else
+ *pfIsNull = FALSE;
+
+ return S_OK;
+}
+
+// gets the value (object address) of this CordbReferenceValue
+// Arguments:
+// output: pTo - reference value
+// Return Value: S_OK on success or E_INVALIDARG if pAddress is null
+HRESULT CordbReferenceValue::GetValue(CORDB_ADDRESS *pAddress)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pAddress, CORDB_ADDRESS *);
+ FAIL_IF_NEUTERED(this);
+
+ // Copy out the value, which is simply the value the object reference.
+ if (m_isLiteral)
+ *pAddress = NULL;
+ else
+ *pAddress = PTR_TO_CORDB_ADDRESS(m_info.objRef);
+
+ return S_OK;
+}
+
+// sets the value of the reference
+// Arguments:
+// input: address - the new reference--this must be a LS address
+// Return Value: S_OK on success or E_INVALIDARG or write process memory errors
+// Note: We make no effort to ensure that the new reference is of the same type as the old one.
+// We simply assume it is. As long as this assumption is correct, we only need to update information about
+// the referent if it's a string (its length can change).
+
+// @dbgtodo Microsoft inspection: consider whether it's worthwhile to verify that the type of the new referent is
+// the same as the type of the existing one. We'd have to do most of the work for a call to InitRef to do
+// this, since we need to know the type of the new referent.
+HRESULT CordbReferenceValue::SetValue(CORDB_ADDRESS address)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ HRESULT hr = S_OK;
+
+ // If this is a heap object, ideally we'd prevent violations of AppDomain isolation
+ // here. However, we have no reliable way of determining what AppDomain the address is in.
+
+ // Can't change literal refs.
+ if (m_isLiteral)
+ {
+ return E_INVALIDARG;
+ }
+
+ // Either we know the type, or it's a handle to a null value
+ _ASSERTE((m_type != NULL) ||
+ (!m_valueHome.ObjHandleIsNull() && (m_info.objRef == NULL)));
+
+ EX_TRY
+ {
+ m_valueHome.m_pHome->SetValue(MemoryRange(&address, sizeof(void *)), m_type); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (SUCCEEDED(hr))
+ {
+ // That worked, so update the copy of the value we have in
+ // our local cache.
+ m_info.objRef = CORDB_ADDRESS_TO_PTR(address);
+
+
+ if (m_info.objTypeData.elementType == ELEMENT_TYPE_STRING)
+ {
+ // update information about the string
+ InitRef(MemoryRange(&m_info.objRef, sizeof (void *)));
+ }
+
+ // All other data in m_info is no longer valid, and we may have invalidated other
+ // ICDRVs at this address. We have to invalidate all cached debuggee data.
+ m_appdomain->GetProcess()->m_continueCounter++;
+ }
+
+ return hr;
+} // CordbReferenceValue::SetValue
+
+HRESULT CordbReferenceValue::DereferenceStrong(ICorDebugValue **ppValue)
+{
+ return E_NOTIMPL;
+}
+
+// Get a new ICDValue instance to represent the referent of this object ref.
+// Arguments:
+// output: ppValue - the new ICDValue instance
+// Return Value: S_OK on success or E_INVALIDARG
+HRESULT CordbReferenceValue::Dereference(ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ // Can't dereference literal refs.
+ if (m_isLiteral)
+ return E_INVALIDARG;
+
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+
+ if (m_continueCounterLastSync != m_appdomain->GetProcess()->m_continueCounter)
+ {
+ IfFailRet(InitRef(MemoryRange(NULL, 0)));
+ }
+
+ EX_TRY
+ {
+ // We may know ahead of time (depending on the reference type) if
+ // the reference is bad.
+ if ((m_info.objRefBad) || (m_info.objRef == NULL))
+ {
+ ThrowHR(CORDBG_E_BAD_REFERENCE_VALUE);
+ }
+
+ hr = DereferenceCommon(m_appdomain, m_type, m_realTypeOfTypedByref, &m_info, ppValue);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+
+}
+
+//-----------------------------------------------------------------------------
+// Common helper to dereferefence.
+// Parameters:
+// pAppDomain, pType, pInfo - necessary paramters to create the value
+// pRealTypeOfTypedByref - type for a potential TypedByRef. Can be NULL if we know
+// that we're not a typed-byref (this is true if we're definitely an object handle)
+// ppValue - outparameter for newly created value. This will get an Ext AddRef.
+//-----------------------------------------------------------------------------
+HRESULT CordbReferenceValue::DereferenceCommon(
+ CordbAppDomain * pAppDomain,
+ CordbType * pType,
+ CordbType * pRealTypeOfTypedByref,
+ DebuggerIPCE_ObjectData * pInfo,
+ ICorDebugValue **ppValue
+)
+{
+ INTERNAL_SYNC_API_ENTRY(pAppDomain->GetProcess());
+
+ // pCachedObject may be NULL if we're not caching.
+ _ASSERTE(pType != NULL);
+ _ASSERTE(pAppDomain != NULL);
+ _ASSERTE(pInfo != NULL);
+ _ASSERTE(ppValue != NULL);
+
+ HRESULT hr = S_OK;
+ *ppValue = NULL; // just to be safe.
+
+ switch(pType->m_elementType)
+ {
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_OBJECT:
+ case ELEMENT_TYPE_STRING:
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DereferenceInternal: type class/object/string\n"));
+ // An object value (possibly a string value, too.) If the class of this object is a value class,
+ // then we have a reference to a boxed object. So we create a box instead of an object value.
+ bool isBoxedVCObject = false;
+
+ if ((pType->m_pClass != NULL) && (pType->m_elementType != ELEMENT_TYPE_STRING))
+ {
+ EX_TRY
+ {
+ isBoxedVCObject = pType->m_pClass->IsValueClass();
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ }
+
+ if (isBoxedVCObject)
+ {
+ TargetBuffer remoteValue(PTR_TO_CORDB_ADDRESS(pInfo->objRef), (ULONG)pInfo->objSize);
+ EX_TRY
+ {
+ RSSmartPtr<CordbBoxValue> pBoxValue(new CordbBoxValue(
+ pAppDomain,
+ pType,
+ remoteValue,
+ (ULONG32)pInfo->objSize,
+ pInfo->objOffsetToVars));
+ pBoxValue->ExternalAddRef();
+ *ppValue = (ICorDebugValue*)(ICorDebugBoxValue*)pBoxValue;
+ }
+ EX_CATCH_HRESULT(hr);
+ }
+ else
+ {
+ RSSmartPtr<CordbObjectValue> pObj;
+ TargetBuffer remoteValue(PTR_TO_CORDB_ADDRESS(pInfo->objRef), (ULONG)pInfo->objSize);
+ // Note: we call Init() by default when we create (or refresh) a reference value, so we
+ // never have to do it again.
+ EX_TRY
+ {
+ pObj.Assign(new CordbObjectValue(pAppDomain, pType, remoteValue, pInfo));
+ IfFailThrow(pObj->Init());
+
+ pObj->ExternalAddRef();
+ *ppValue = static_cast<ICorDebugValue*>( static_cast<ICorDebugObjectValue*>(pObj) );
+ }
+ EX_CATCH_HRESULT(hr);
+ } // boxed?
+
+ break;
+ }
+
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DereferenceInternal: type array/szarray\n"));
+ TargetBuffer remoteValue(PTR_TO_CORDB_ADDRESS(pInfo->objRef), (ULONG)pInfo->objSize); // sizeof(void *)?
+ EX_TRY
+ {
+ RSSmartPtr<CordbArrayValue> pArrayValue(new CordbArrayValue(
+ pAppDomain,
+ pType,
+ pInfo,
+ remoteValue));
+
+ IfFailThrow(pArrayValue->Init());
+
+ pArrayValue->ExternalAddRef();
+ *ppValue = (ICorDebugValue*)(ICorDebugArrayValue*)pArrayValue;
+ }
+ EX_CATCH_HRESULT(hr);
+
+ break;
+ }
+
+ case ELEMENT_TYPE_BYREF:
+ case ELEMENT_TYPE_PTR:
+ {
+ //_ASSERTE(pInfo->objToken.IsNull()); // can't get this type w/ an object handle
+
+ LOG((LF_CORDB, LL_INFO1000, "DereferenceInternal: type byref/ptr\n"));
+ CordbType *ptrType;
+ pType->DestUnaryType(&ptrType);
+
+ CorElementType et = ptrType->m_elementType;
+
+ if (et == ELEMENT_TYPE_VOID)
+ {
+ *ppValue = NULL;
+ return CORDBG_S_VALUE_POINTS_TO_VOID;
+ }
+
+ TargetBuffer remoteValue(pInfo->objRef, GetSizeForType(ptrType, kUnboxed));
+ // Create a value for what this reference points to. Note:
+ // this could be almost any type of value.
+ EX_TRY
+ {
+ CordbValue::CreateValueByType(
+ pAppDomain,
+ ptrType,
+ false,
+ remoteValue,
+ MemoryRange(NULL, 0), // local value
+ NULL,
+ ppValue); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+
+ break;
+ }
+
+ case ELEMENT_TYPE_TYPEDBYREF:
+ {
+ //_ASSERTE(pInfo->objToken.IsNull()); // can't get this type w/ an object handle
+ _ASSERTE(pRealTypeOfTypedByref != NULL);
+
+ LOG((LF_CORDB, LL_INFO1000, "DereferenceInternal: type typedbyref\n"));
+
+ TargetBuffer remoteValue(pInfo->objRef, sizeof(void *));
+ // Create the value for what this reference points
+ // to.
+ EX_TRY
+ {
+ CordbValue::CreateValueByType(
+ pAppDomain,
+ pRealTypeOfTypedByref,
+ false,
+ remoteValue,
+ MemoryRange(NULL, 0), // local value
+ NULL,
+ ppValue); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+
+ break;
+ }
+
+ case ELEMENT_TYPE_FNPTR:
+ // Function pointers cannot be dereferenced; only the pointer value itself
+ // may be inspected--not what it points to.
+ *ppValue = NULL;
+ return CORDBG_E_VALUE_POINTS_TO_FUNCTION;
+
+ default:
+ LOG((LF_CORDB, LL_INFO1000, "DereferenceInternal: Fail!\n"));
+ _ASSERTE(!"Bad reference type!");
+ hr = E_FAIL;
+ break;
+ }
+
+ return hr;
+}
+
+// static helper to build a CordbReferenceValue from a general variable home.
+// We can find the CordbType from the object instance.
+HRESULT CordbReferenceValue::Build(CordbAppDomain * appdomain,
+ CordbType * type,
+ TargetBuffer remoteValue,
+ MemoryRange localValue,
+ VMPTR_OBJECTHANDLE vmObjectHandle,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr,
+ CordbReferenceValue** ppValue)
+{
+ HRESULT hr = S_OK;
+
+ // We can find the AD from an object handle (but not a normal object), so the AppDomain may
+ // be NULL if if it's an OH.
+ //_ASSERTE((appdomain != NULL) || objectRefsInHandles);
+
+ // A reference, possibly to an object or value class
+ // Weak by default
+ EX_TRY
+ {
+ RSSmartPtr<CordbReferenceValue> pRefValue(new CordbReferenceValue(appdomain,
+ type,
+ localValue,
+ remoteValue,
+ ppRemoteRegAddr,
+ vmObjectHandle));
+ IfFailThrow(pRefValue->InitRef(localValue));
+
+ pRefValue->InternalAddRef();
+ *ppValue = pRefValue;
+ }
+ EX_CATCH_HRESULT(hr)
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Static helper to build a CordbReferenceValue from a GCHandle
+// The LS can actually determine an AppDomain from an OBJECTHandles, however, the RS
+// should already have this infromation too, so we pass it in.
+// We also supply the AppDomain here because it provides the CordbValue with
+// process affinity.
+// Note that the GC handle may point to a NULL reference, in which case we should still create
+// an appropriate ICorDebugReferenceValue for which IsNull returns TRUE.
+//-----------------------------------------------------------------------------
+HRESULT CordbReferenceValue::BuildFromGCHandle(
+ CordbAppDomain *pAppDomain,
+ VMPTR_OBJECTHANDLE gcHandle,
+ ICorDebugReferenceValue ** pOutRef
+)
+{
+ _ASSERTE(pAppDomain != NULL);
+ _ASSERTE(pOutRef != NULL);
+
+ CordbProcess * pProc;
+ pProc = pAppDomain->GetProcess();
+ INTERNAL_SYNC_API_ENTRY(pProc);
+
+ HRESULT hr = S_OK;
+
+ *pOutRef = NULL;
+
+ // Make sure we even have a GC handle.
+ // Also, We may have a handle, but its contents may be null.
+ if (gcHandle.IsNull())
+ {
+ // We've seen this assert fire in the wild, but have never gotten a repro.
+ // so we'll include a runtime check to avoid the AV.
+ _ASSERTE(false || !"We got a bad reference value.");
+ return CORDBG_E_BAD_REFERENCE_VALUE;
+ }
+
+ // Now that we've got an AppDomain, we can go ahead and create the reference value normally.
+
+ RSSmartPtr<CordbReferenceValue> pRefValue;
+ TargetBuffer remoteValue;
+ EX_TRY
+ {
+ remoteValue.Init(pProc->GetDAC()->GetHandleAddressFromVmHandle(gcHandle), sizeof(void *));
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+
+ hr = CordbReferenceValue::Build(
+ pAppDomain,
+ NULL, // unknown type
+ remoteValue, // CORDB_ADDRESS remoteAddress,
+ MemoryRange(NULL, 0),
+ gcHandle, // objectRefsInHandles,
+ NULL, // EnregisteredValueHome * pRemoteRegAddr,
+ &pRefValue);
+
+ if (SUCCEEDED(hr))
+ {
+ pRefValue->QueryInterface(__uuidof(ICorDebugReferenceValue), (void**)pOutRef);
+ }
+
+ return hr;
+}
+
+// Helper function for SanityCheckPointer. Make an attempt to read memory at the address which is the value
+// of the reference.
+// Arguments: none
+// Notes:
+// - Throws
+// - m_info.objRefBad must be set to true before calling this function. If we throw, we'll
+// never end up setting m_info.objRefBad, but throwing indicates that the reference is
+// indeed bad. Only if we exit normally will we end up setting m_info.objRefBad to false.
+void CordbReferenceValue::TryDereferencingTarget()
+{
+ _ASSERTE(!!m_info.objRefBad == true);
+ // First get the referent type
+ CordbType * pReferentType;
+ m_type->DestUnaryType(&pReferentType);
+
+ // Next get the size
+ ULONG32 dataSize, sizeToRead;
+ IfFailThrow(pReferentType->GetUnboxedObjectSize(&dataSize));
+ if (dataSize <= 0)
+ sizeToRead = 1; // Read at least one byte.
+ else if (dataSize >= 8)
+ sizeToRead = 8; // Read at most eight bytes--this is just a perf improvement. Even if we read
+ // all the bytes, we are only able to determine that we can read those bytes,
+ // we can't really tell if the data we are reading is actually the data we
+ // want.
+ else sizeToRead = dataSize;
+
+ // Now see if we can read from the address where the object is supposed to be
+ BYTE dummy[8];
+
+ // Get a target buffer with the remote address and size of the object--since we don't know if the
+ // address if valid, this could throw or return a size that's complete garbage
+ TargetBuffer object(m_info.objRef, sizeToRead);
+
+ // now read target memory. This may throw ...
+ GetProcess()->SafeReadBuffer(object, dummy);
+
+} // CordbReferenceValue::TryDereferencingTarget
+
+// Do a sanity check on the pointer which is the value of the object reference. We can't efficiently ensure that
+// the pointer is really good, so we settle for a quick check just to make sure the memory at the address is
+// readable. We're actually just checking that we can dereference the pointer.
+// Arguments:
+// input: type - the type of the pointer to which the object reference points.
+// output: none, but fills in m_info.objRefBad
+// Note: Throws
+void CordbReferenceValue::SanityCheckPointer (CorElementType type)
+{
+ m_info.objRefBad = TRUE;
+ if (type != ELEMENT_TYPE_FNPTR)
+ {
+ // We should never dereference a function pointer, so all references
+ // are considered "bad."
+ if (m_info.objRef != NULL)
+ {
+ if (type == ELEMENT_TYPE_PTR)
+ {
+ // The only way to tell if the reference in PTR is bad or
+ // not is to try to deref the thing.
+ TryDereferencingTarget();
+ }
+ } // !m_info.m_basicData.m_vmObject.IsNull()
+ // else Null refs are considered "bad".
+ } // type != ELEMENT_TYPE_FNPTR
+
+ // we made it without throwing, so we'll assume (perhaps wrongly) that the ref is good
+ m_info.objRefBad = FALSE;
+
+} // CordbReferenceValue::SanityCheckPointer
+
+// get information about the reference when it's not an object address but another kind of pointer type:
+// ELEMENT_TYPE_BYREF, ELEMENT_TYPE_PTR or ELEMENT_TYPE_FNPTR
+// Arguments:
+// input: type - type of the referent
+// localValue - starting address and length of a local buffer containing the object ref
+// Notes:
+// - fills in the m_info field of "this"
+// - Throws (errors from reading process memory)
+void CordbReferenceValue::GetPointerData(CorElementType type, MemoryRange localValue)
+{
+ HRESULT hr = S_OK;
+ // Fill in the type since we will not be getting it from the DAC
+ m_info.objTypeData.elementType = type;
+
+ // First get the objRef
+ if (localValue.StartAddress() != NULL)
+ {
+ // localValue represents a buffer containing a copy of the objectRef that exists locally. It could be a
+ // component of a container type residing within a local cached copy belonging to some other
+ // Cordb*Value instance representing the container type. In this case it will be a field, array
+ // element, or referent of a different object reference for that other Cordb*Value instance. It
+ // could also be a pointer to the value of a local register display of the frame from which this object
+ // ref comes.
+
+ // For example, if we have a value class (represented by a CordbVCObject instance) with a field
+ // that is an object pointer, localValue will contain a pointer to that field in the local
+ // cache of the CordbVCObjectValue instance (CordbVCObjectValue::m_pObjectCopy).
+
+ // Note, though, that pLocalValue holds the address of a target object. We will cache
+ // the contents of pLocalValue (the object ref) here for efficiency of read access, but if we
+ // want to set the reference later (e.g., we want the object ref to point to NULL instead of an
+ // object), we'll have to set the object ref in the target, not our local copy.
+ // Host memory Target memory
+ // --------------- |
+ // CordbVCObjectValue::m_copyOfObject ----> | |
+ // | ... | |
+ // | |
+ // |---------------| | Object
+ // localAddress ---> | object addr |-------------> --------------
+ // |---------------| | ---> | |
+ // | ... | | | |
+ // --------------- | | --------------
+ // |
+ // CordbReferenceValue::m_info.objRef ---> --------------- | |
+ // | object addr |---------
+ // --------------- |
+
+ _ASSERTE(localValue.Size() == sizeof(void *));
+ localCopy(&(m_info.objRef), localValue);
+ }
+ else
+ {
+ // we have a non-local location, so we'll get the value of the ref from its home
+
+ // do some preinitialization in case we get an exception
+ EX_TRY
+ {
+ m_valueHome.m_pHome->GetValue(MemoryRange(&(m_info.objRef), sizeof(void*))); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ m_info.objRef = NULL;
+ m_info.objRefBad = TRUE;
+ ThrowHR(hr);
+ }
+ }
+
+ EX_TRY
+ {
+ // If we made it this far, we need to sanity check the pointer--we'll just see if we can
+ // read at that address
+ SanityCheckPointer(type);
+ }
+ EX_CATCH_HRESULT(hr); // we don't need to do anything here, m_info.objRefBad will have been set to true
+
+} // CordbReferenceValue::GetPointerData
+
+// Helper function for CordbReferenceValue::GetObjectData: Sets default values for the fields in pObjectData
+// before processing begins. Not all will necessarily be initialized during processing.
+// Arguments:
+// input: objectType - type of the referent of the objRef being examined
+// output: pObjectData - information about the reference to be initialized
+void PreInitObjectData(DebuggerIPCE_ObjectData * pObjectData, void * objAddress, CorElementType objectType)
+{
+ _ASSERTE(pObjectData != NULL);
+
+ memset(pObjectData, 0, sizeof(DebuggerIPCE_ObjectData));
+ pObjectData->objRef = objAddress;
+ pObjectData->objTypeData.elementType = objectType;
+
+} // PreInitObjectData
+
+// get basic object specific data when a reference points to an object, plus extra data if the object is an
+// array or string
+// Arguments:
+// input: pProcess - process to which the object belongs
+// objectAddress - pointer to the TypedByRef object (this is the value of the object reference
+// or handle.
+// type - the type of the object referenced
+// vmAppDomain - appdomain to which the object belongs
+// output: pInfo - filled with information about the object to which the TypedByRef refers.
+// Note: Throws
+/* static */
+void CordbReferenceValue::GetObjectData(CordbProcess * pProcess,
+ void * objectAddress,
+ CorElementType type,
+ VMPTR_AppDomain vmAppdomain,
+ DebuggerIPCE_ObjectData * pInfo)
+{
+ IDacDbiInterface *pInterface = pProcess->GetDAC();
+ CORDB_ADDRESS objTargetAddr = PTR_TO_CORDB_ADDRESS(objectAddress);
+
+ // make sure we don't end up with old garbage values in case the reference is bad
+ PreInitObjectData(pInfo, objectAddress, type);
+
+ pInterface->GetBasicObjectInfo(objTargetAddr, type, vmAppdomain, pInfo);
+
+ if (!pInfo->objRefBad)
+ {
+ // for certain referent types, we need a bit more information:
+ if (pInfo->objTypeData.elementType == ELEMENT_TYPE_STRING)
+ {
+ pInterface->GetStringData(objTargetAddr, pInfo);
+ }
+ else if ((pInfo->objTypeData.elementType == ELEMENT_TYPE_ARRAY) ||
+ (pInfo->objTypeData.elementType == ELEMENT_TYPE_SZARRAY))
+ {
+ pInterface->GetArrayData(objTargetAddr, pInfo);
+ }
+ }
+
+} // CordbReferenceValue::GetObjectData
+
+// get information about a TypedByRef object when the reference is the address of a TypedByRef structure.
+// Arguments:
+// input: pProcess - process to which the object belongs
+// pTypedByRef - pointer to the TypedByRef object (this is the value of the object reference or
+// handle.
+// type - the type of the object referenced
+// vmAppDomain - appdomain to which the object belongs
+// output: pInfo - filled with information about the object to which the TypedByRef refers.
+// Note: Throws
+/* static */
+void CordbReferenceValue::GetTypedByRefData(CordbProcess * pProcess,
+ CORDB_ADDRESS pTypedByRef,
+ CorElementType type,
+ VMPTR_AppDomain vmAppDomain,
+ DebuggerIPCE_ObjectData * pInfo)
+{
+
+ // make sure we don't end up with old garbage values since we don't set all the values for TypedByRef objects
+ PreInitObjectData(pInfo, CORDB_ADDRESS_TO_PTR(pTypedByRef), type);
+
+ // Though pTypedByRef is the value of the object ref represented by an instance of CordbReferenceValue,
+ // it is not the address of an object, as we would ordinarily expect. Instead, in the special case of
+ // TypedByref objects, it is actually the address of the TypedByRef struct which contains the
+ // type and the object address.
+
+ pProcess->GetDAC()->GetTypedByRefInfo(pTypedByRef, vmAppDomain, pInfo);
+} // CordbReferenceValue::GetTypedByRefData
+
+// get the address of the object referenced
+// Arguments: none
+// Return Value: the address of the object referenced (i.e., the value of the object ref)
+// Note: Throws
+void * CordbReferenceValue::GetObjectAddress(MemoryRange localValue)
+{
+ void * objectAddress;
+ if (localValue.StartAddress() != NULL)
+ {
+ // the object ref comes from a local cached copy
+ _ASSERTE(localValue.Size() == sizeof(void *));
+ memcpy(&objectAddress, localValue.StartAddress(), localValue.Size());
+ }
+ else
+ {
+ _ASSERTE(m_valueHome.m_pHome != NULL);
+ m_valueHome.m_pHome->GetValue(MemoryRange(&objectAddress, sizeof(void *))); // throws
+ }
+ return objectAddress;
+} // CordbReferenceValue::GetObjectAddress
+
+// update type information after initializing -- when we initialize, we may get more exact type information
+// than we previously had
+// Arguments: none--uses and updates data members
+// Note: Throws
+void CordbReferenceValue::UpdateTypeInfo()
+{
+ // If the object type that we got back is different than the one we sent, then it means that we
+ // orignally had a CLASS and now have something more specific, like a SDARRAY, MDARRAY, or STRING or
+ // a constructed type.
+ // Update our signature accordingly, which is okay since we always have a copy of our sig. This
+ // ensures that the reference's signature accurately reflects what the Runtime knows it's pointing
+ // to.
+ //
+ // GENERICS: do this for all types: for example, an array might have been discovered to be a more
+ // specific kind of array (String[] where an Object[] was expected).
+ CordbType *newtype;
+
+ IfFailThrow(CordbType::TypeDataToType(m_appdomain, &m_info.objTypeData, &newtype));
+
+ _ASSERTE(newtype->m_elementType != ELEMENT_TYPE_VALUETYPE);
+ m_type.Assign(newtype); // implicit Release + AddRef
+
+ // For typed-byref's the act of dereferencing the object also reveals to us
+ // what the "real" type of the object is...
+ if (m_info.objTypeData.elementType == ELEMENT_TYPE_TYPEDBYREF)
+{
+ IfFailThrow(CordbType::TypeDataToType(m_appdomain,
+ &m_info.typedByrefInfo.typedByrefType,
+ &m_realTypeOfTypedByref));
+ }
+} // CordbReferenceValue::UpdateTypeInfo
+
+// Initialize this CordbReferenceValue. This may involve inspecting the LS to get information about the
+// referent.
+// Arguments:
+// input: localValue - buffer address and size of the RS location of the reference. (This may be NULL
+// if the reference didn't come from a local cached copy. See
+// code:CordbReferenceValue::GetPointerData for further explanation of local locations.)
+// Return Value: S_OK on success or E_INVALIDARG or write process memory errors on failure
+
+HRESULT CordbReferenceValue::InitRef(MemoryRange localValue)
+{
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess());
+
+ HRESULT hr = S_OK;
+ CordbProcess * pProcess = GetProcess();
+
+ // Simple init needed for literal refs. Literals may have a null process / appdomain ptr.
+ if (m_isLiteral)
+ {
+ _ASSERTE(m_type != NULL);
+ m_info.objTypeData.elementType = m_type->m_elementType;
+ return hr;
+ }
+
+ _ASSERTE((pProcess->GetShim() == NULL) || pProcess->GetSynchronized());
+
+ // If the helper thread is dead, then pretend this is a bad reference.
+ if (GetProcess()->m_helperThreadDead)
+ {
+ m_info.objRef = NULL;
+ m_info.objRefBad = TRUE;
+ return hr;
+ }
+
+ m_continueCounterLastSync = pProcess->m_continueCounter;
+
+ // If no type provided, then it's b/c we're a class and we'll get the type when we get Created.
+ CorElementType type = (m_type != NULL) ? (m_type->m_elementType) : ELEMENT_TYPE_CLASS;
+ _ASSERTE (type != ELEMENT_TYPE_GENERICINST);
+ _ASSERTE (type != ELEMENT_TYPE_VAR);
+ _ASSERTE (type != ELEMENT_TYPE_MVAR);
+
+ EX_TRY
+ {
+ if ((type == ELEMENT_TYPE_BYREF) ||
+ (type == ELEMENT_TYPE_PTR) ||
+ (type == ELEMENT_TYPE_FNPTR))
+ {
+ // we know the size is just the size of a pointer, so we can just read process memory to get the
+ // information we need
+ GetPointerData(type, localValue);
+ }
+ else // we have to get more information about the object from the DAC
+ {
+ if (type == ELEMENT_TYPE_TYPEDBYREF)
+ {
+ _ASSERTE(m_valueHome.m_pHome != NULL);
+ GetTypedByRefData(pProcess,
+ m_valueHome.m_pHome->GetAddress(),
+ type,
+ m_appdomain->GetADToken(),
+ &m_info);
+ }
+ else
+ {
+ GetObjectData(pProcess, GetObjectAddress(localValue), type, m_appdomain->GetADToken(), &m_info);
+ }
+
+ // if we got (what we believe is probably) a good reference, we should update the type info
+ if (!m_info.objRefBad)
+ {
+ // we may have gotten back a more specific type than we had previously
+ UpdateTypeInfo();
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbReferenceValue::InitRef
+
+/* ------------------------------------------------------------------------- *
+ * Object Value class
+ * ------------------------------------------------------------------------- */
+
+
+// validate a CordbObjectValue to ensure it hasn't been neutered
+#define COV_VALIDATE_OBJECT() do { \
+ BOOL bValid; \
+ HRESULT hr; \
+ if (FAILED(hr = IsValid(&bValid))) \
+ return hr; \
+ \
+ if (!bValid) \
+ { \
+ return CORDBG_E_INVALID_OBJECT; \
+ } \
+ }while(0)
+
+// constructor
+// Arguments:
+// input: pAppDomain - the appdomain to which the object belongs
+// pType - the type of the object
+// remoteValue - the LS address and size of the object
+// pObjectData - other information about the object, most importantly, the offset to the
+// fields of the object
+CordbObjectValue::CordbObjectValue(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ TargetBuffer remoteValue,
+ DebuggerIPCE_ObjectData *pObjectData )
+ : CordbValue(pAppdomain, pType, remoteValue.pAddress,
+ false, pAppdomain->GetProcess()->GetContinueNeuterList()),
+ m_info(*pObjectData),
+ m_pObjectCopy(NULL), m_objectLocalVars(NULL), m_stringBuffer(NULL),
+ m_valueHome(pAppdomain->GetProcess(), remoteValue),
+ m_fIsExceptionObject(FALSE), m_fIsRcw(FALSE)
+{
+ _ASSERTE(pAppdomain != NULL);
+
+ m_size = m_info.objSize;
+
+ HRESULT hr = S_FALSE;
+
+ ALLOW_DATATARGET_MISSING_MEMORY
+ (
+ hr = IsExceptionObject();
+ );
+
+ if (hr == S_OK)
+ m_fIsExceptionObject = TRUE;
+
+ hr = S_FALSE;
+ ALLOW_DATATARGET_MISSING_MEMORY
+ (
+ hr = IsRcw();
+ );
+
+ if (hr == S_OK)
+ m_fIsRcw = TRUE;
+} // CordbObjectValue::CordbObjectValue
+
+// destructor
+CordbObjectValue::~CordbObjectValue()
+{
+ DTOR_ENTRY(this);
+
+ _ASSERTE(IsNeutered());
+} // CordbObjectValue::~CordbObjectValue
+
+void CordbObjectValue::Neuter()
+{
+ // Destroy the copy of the object.
+ if (m_pObjectCopy != NULL)
+ {
+ delete [] m_pObjectCopy;
+ m_pObjectCopy = NULL;
+ }
+
+ CordbValue::Neuter();
+} // CordbObjectValue::Neuter
+
+HRESULT CordbObjectValue::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugValue)
+ {
+ *pInterface = static_cast<ICorDebugValue*>(static_cast<ICorDebugObjectValue*>(this));
+ }
+ else if (id == IID_ICorDebugValue2)
+ {
+ *pInterface = static_cast<ICorDebugValue2*>(this);
+ }
+ else if (id == IID_ICorDebugValue3)
+ {
+ *pInterface = static_cast<ICorDebugValue3*>(this);
+ }
+ else if (id == IID_ICorDebugObjectValue)
+ {
+ *pInterface = static_cast<ICorDebugObjectValue*>(this);
+ }
+ else if (id == IID_ICorDebugObjectValue2)
+ {
+ *pInterface = static_cast<ICorDebugObjectValue2*>(this);
+ }
+ else if (id == IID_ICorDebugGenericValue)
+ {
+ *pInterface = static_cast<ICorDebugGenericValue*>(this);
+ }
+ else if (id == IID_ICorDebugHeapValue)
+ {
+ *pInterface = static_cast<ICorDebugHeapValue*>(this);
+ }
+ else if (id == IID_ICorDebugHeapValue2)
+ {
+ *pInterface = static_cast<ICorDebugHeapValue2*>(this);
+ }
+ else if (id == IID_ICorDebugHeapValue3)
+ {
+ *pInterface = static_cast<ICorDebugHeapValue3*>(this);
+ }
+ else if ((id == IID_ICorDebugStringValue) &&
+ (m_info.objTypeData.elementType == ELEMENT_TYPE_STRING))
+ {
+ *pInterface = static_cast<ICorDebugStringValue*>(this);
+ }
+ else if (id == IID_ICorDebugExceptionObjectValue && m_fIsExceptionObject)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugExceptionObjectValue*>(this));
+ }
+ else if (id == IID_ICorDebugComObjectValue && m_fIsRcw)
+ {
+ *pInterface = static_cast<ICorDebugComObjectValue*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugObjectValue*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+} // CordbObjectValue::QueryInterface
+
+// gets the type of the object
+// Arguments:
+// output: pType - the type of the value. The caller must guarantee that pType is non-null.
+// Return Value: S_OK on success, E_INVALIDARG on failure
+HRESULT CordbObjectValue::GetType(CorElementType *pType)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ return (CordbValue::GetType(pType));
+} // CordbObjectValue::GetType
+
+// gets the size of the object
+// Arguments:
+// output: pSize - the size of the value. The caller must guarantee that pSize is non-null.
+// Return Value: S_OK on success, E_INVALIDARG on failure
+HRESULT CordbObjectValue::GetSize(ULONG32 *pSize)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ return (CordbValue::GetSize(pSize));
+} // CordbObjectValue::GetSize
+
+// gets the size of the object
+// Arguments:
+// output: pSize - the size of the value. The caller must guarantee that pSize is non-null.
+// Return Value: S_OK on success, E_INVALIDARG on failure
+HRESULT CordbObjectValue::GetSize64(ULONG64 *pSize)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ return (CordbValue::GetSize64(pSize));
+} // CordbObjectValue::GetSize64
+
+
+// gets the remote (LS) address of the object. This may return NULL if the
+// object is a literal or resides in a register.
+// Arguments:
+// output: pAddress - the LS address (the contents should not be null since objects
+// aren't enregistered nor are they fields or elements of other
+// types). The caller must ensure that pAddress is not null.
+// Return Value: S_OK on success or E_INVALIDARG if pAddress is null
+HRESULT CordbObjectValue::GetAddress(CORDB_ADDRESS *pAddress)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ COV_VALIDATE_OBJECT();
+ VALIDATE_POINTER_TO_OBJECT(pAddress, CORDB_ADDRESS *);
+
+ *pAddress = m_valueHome.GetAddress();
+ return (S_OK);
+} // CordbObjectValue::GetAddress
+
+HRESULT CordbObjectValue::CreateBreakpoint(ICorDebugValueBreakpoint ** ppBreakpoint)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ COV_VALIDATE_OBJECT();
+
+ return (CordbValue::CreateBreakpoint(ppBreakpoint));
+}
+
+// determine if "this" is still valid (i.e., not neutered)
+// Arguments:
+// output: pfIsValid - true iff "this" is still not neutered
+// Return Value: S_OK or E_INVALIDARG
+HRESULT CordbObjectValue::IsValid(BOOL * pfIsValid)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pfIsValid, BOOL *);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // We're neutered on continue, so we're valid up until the time we're neutered
+ (*pfIsValid) = TRUE;
+ return S_OK;
+}
+
+HRESULT CordbObjectValue::CreateRelocBreakpoint(
+ ICorDebugValueBreakpoint **ppBreakpoint)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppBreakpoint, ICorDebugValueBreakpoint **);
+
+ COV_VALIDATE_OBJECT();
+
+ return E_NOTIMPL;
+}
+
+/*
+* Creates a handle of the given type for this heap value.
+*
+* Not Implemented In-Proc.
+*/
+HRESULT CordbObjectValue::CreateHandle(
+ CorDebugHandleType handleType,
+ ICorDebugHandleValue ** ppHandle)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return CordbValue::InternalCreateHandle(handleType, ppHandle);
+} // CreateHandle
+
+// Get class information for this object
+// Arguments:
+// output: ppClass - ICDClass instance for this object
+// Return Value: S_OK if success, CORDBG_E_CLASS_NOT_LOADED, E_INVALIDARG, OOM on failure
+HRESULT CordbObjectValue::GetClass(ICorDebugClass **ppClass)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppClass, ICorDebugClass **);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ if (m_type->m_pClass == NULL)
+ {
+ if (FAILED(hr = m_type->Init(FALSE)))
+ return hr;
+ }
+
+ _ASSERTE(m_type->m_pClass);
+ *ppClass = (ICorDebugClass*) m_type->m_pClass;
+
+ if (*ppClass != NULL)
+ (*ppClass)->AddRef();
+
+ return hr;
+} // CordbObjectValue::GetClass
+
+
+
+
+
+//-----------------------------------------------------------------------------
+//
+// Public API to get instance field of the given type in the object and returns an ICDValue for it.
+//
+// Arguments:
+// pType - The type containing the field token.
+// fieldDef - The field's metadata def.
+// ppValue - OUT: the ICDValue for the field.
+//
+// Returns:
+// S_OK on success. E_INVALIDARG, CORDBG_E_ENC_HANGING_FIELD, CORDBG_E_FIELD_NOT_INSTANCE or OOM on
+// failure
+//
+// Notes:
+// This is for instance fields only.
+// Lookup on code:CordbType::GetStaticFieldValue to get static fields.
+// This is generics aware.
+HRESULT CordbObjectValue::GetFieldValueForType(ICorDebugType * pType,
+ mdFieldDef fieldDef,
+ ICorDebugValue ** ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pType, ICorDebugType *);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ COV_VALIDATE_OBJECT();
+
+ CordbType * pCordbType = NULL;
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ BOOL fSyncBlockField = FALSE;
+ SIZE_T fldOffset;
+
+ //
+ // <TODO>@todo: need to ensure that pType is really on the class
+ // hierarchy of m_class!!!</TODO>
+ //
+ if (pType == NULL)
+ {
+ pCordbType = m_type;
+ }
+ else
+ {
+ pCordbType = static_cast<CordbType *>(pType);
+ }
+
+ // Validate the token.
+ if (pCordbType->m_pClass == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+ IMetaDataImport * pImport = pCordbType->m_pClass->GetModule()->GetMetaDataImporter();
+
+ if (!pImport->IsValidToken(fieldDef))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ FieldData * pFieldData;
+
+ #ifdef _DEBUG
+ pFieldData = NULL;
+ #endif
+
+ hr = pCordbType->GetFieldInfo(fieldDef, &pFieldData);
+
+ // If we couldn't get field info because the field was added with EnC
+ if (hr == CORDBG_E_ENC_HANGING_FIELD)
+ {
+ // The instance field hangs off the syncblock, get its address
+ hr = pCordbType->m_pClass->GetEnCHangingField(fieldDef, &pFieldData, this);
+
+ if (SUCCEEDED(hr))
+ {
+ fSyncBlockField = TRUE;
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ _ASSERTE(pFieldData != NULL);
+
+ if (pFieldData->m_fFldIsStatic)
+ {
+ ThrowHR(CORDBG_E_FIELD_NOT_INSTANCE);
+ }
+
+ // Compute the remote address, too, so that SetValue will work.
+ // Note that if pFieldData is a syncBlock field, fldOffset will have been cooked
+ // to produce the correct result here.
+ _ASSERTE(pFieldData->OkToGetOrSetInstanceOffset());
+ fldOffset = pFieldData->GetInstanceOffset();
+
+ CordbModule * pModule = pCordbType->m_pClass->GetModule();
+
+ SigParser sigParser;
+ IfFailThrow(pFieldData->GetFieldSignature(pModule, &sigParser));
+
+ CordbType * pFieldType;
+ IfFailThrow(CordbType::SigToType(pModule, &sigParser, &(pCordbType->m_inst), &pFieldType));
+
+ ULONG32 size = GetSizeForType(pFieldType, kUnboxed);
+
+ void * localAddr = NULL;
+ if (!fSyncBlockField)
+ {
+ // verify that the field starts and ends before the end of m_pObjectCopy
+ _ASSERTE(m_info.objOffsetToVars + fldOffset < m_size);
+ _ASSERTE(m_info.objOffsetToVars + fldOffset + size <= m_size);
+ localAddr = m_objectLocalVars + fldOffset;
+ }
+
+ // pass the computed local field address, but don't claim we have a local addr if the fldOffset
+ // has been cooked to point us to a sync block field.
+ m_valueHome.CreateInternalValue(pFieldType,
+ m_info.objOffsetToVars + fldOffset,
+ localAddr,
+ size,
+ ppValue); // throws
+ }
+
+ // If we can't get it b/c it's a constant, then say so.
+ hr = CordbClass::PostProcessUnavailableHRESULT(hr, pImport, fieldDef);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbObjectValue::GetFieldValueForType
+
+// Public implementation of ICorDebugObjectValue::GetFieldValue
+// Arguments:
+// input: pClass - class information for this object
+// fieldDef - the field token for the requested field
+// output: ppValue - instance of ICDValue created to represent the field
+// Return Value: S_OK on success, E_INVALIDARG, CORDBG_E_ENC_HANGING_FIELD, CORDBG_E_FIELD_NOT_INSTANCE
+// or OOM on failure
+HRESULT CordbObjectValue::GetFieldValue(ICorDebugClass *pClass,
+ mdFieldDef fieldDef,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ VALIDATE_POINTER_TO_OBJECT(pClass, ICorDebugClass *);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+
+ COV_VALIDATE_OBJECT();
+
+ HRESULT hr;
+ _ASSERTE(m_type);
+
+ if (m_type->m_elementType != ELEMENT_TYPE_CLASS &&
+ m_type->m_elementType != ELEMENT_TYPE_VALUETYPE)
+ {
+ return E_INVALIDARG;
+ }
+
+ // mdFieldDef may specify a field within a base class. mdFieldDef tokens are unique throughout a module.
+ // So we still need a metadata scope to resolve the mdFieldDef. We can infer the scope from pClass.
+ // Beware that this Type may be derived from a type in another module, and so the incoming
+ // fieldDef has to be resolved in the metadata scope of pClass.
+
+ RSExtSmartPtr<CordbType> relevantType;
+
+ // This object has an ICorDebugType which has the type-parameters for generics.
+ // ICorDebugClass provided by the caller does not have type-parameters. So we resolve that
+ // by using the provided ICDClass with the type parameters from this object's ICDType.
+ if (FAILED (hr= m_type->GetParentType((CordbClass *) pClass, &relevantType)))
+ {
+ return hr;
+ }
+ // Upon exit relevantType will either be the appropriate type for the
+ // class we're looking for.
+
+ hr = GetFieldValueForType(relevantType, fieldDef, ppValue);
+ // GetParentType adds one reference to relevantType., Holder dtor releases
+ return hr;
+
+} // CordbObjectValue::GetFieldValue
+
+HRESULT CordbObjectValue::GetVirtualMethod(mdMemberRef memberRef,
+ ICorDebugFunction **ppFunction)
+{
+ VALIDATE_POINTER_TO_OBJECT(ppFunction, ICorDebugFunction **);
+ FAIL_IF_NEUTERED(this);
+ COV_VALIDATE_OBJECT();
+
+ return E_NOTIMPL;
+} // CordbObjectValue::GetVirtualMethod
+
+HRESULT CordbObjectValue::GetVirtualMethodAndType(mdMemberRef memberRef,
+ ICorDebugFunction **ppFunction,
+ ICorDebugType **ppType)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppFunction, ICorDebugFunction **);
+ VALIDATE_POINTER_TO_OBJECT(ppFunction, ICorDebugType **);
+
+ COV_VALIDATE_OBJECT();
+
+ return E_NOTIMPL;
+} // CordbObjectValue::GetVirtualMethodAndType
+
+HRESULT CordbObjectValue::GetContext(ICorDebugContext **ppContext)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppContext, ICorDebugContext **);
+
+ COV_VALIDATE_OBJECT();
+
+ return E_NOTIMPL;
+} // CordbObjectValue::GetContext
+
+// determines whether this represents a value class-- always returns false
+// Arguments:
+// output: pfIsValueClass - always false; CordbVCObjectValue is used to represent
+// value classes, so by definition, a CordbObjectValue instance
+// does not represent a value class
+// Return Value: S_OK
+//
+HRESULT CordbObjectValue::IsValueClass(BOOL * pfIsValueClass)
+{
+ FAIL_IF_NEUTERED(this);
+ COV_VALIDATE_OBJECT();
+
+ if (pfIsValueClass) // don't assign to a null pointer!
+ *pfIsValueClass = FALSE;
+
+ return S_OK;
+} // CordbObjectValue::IsValueClass
+
+HRESULT CordbObjectValue::GetManagedCopy(IUnknown **ppObject)
+{
+ // GetManagedCopy() is deprecated. In the case where the version of
+ // the debugger doesn't match the version of the debuggee, the two processes
+ // might have dangerously different notions of the layout of an object.
+
+ // This function is deprecated
+ return E_NOTIMPL;
+} // CordbObjectValue::GetManagedCopy
+
+HRESULT CordbObjectValue::SetFromManagedCopy(IUnknown *pObject)
+{
+ // Deprecated for the same reason as GetManagedCopy()
+ return E_NOTIMPL;
+} // CordbObjectValue::SetFromManagedCopy
+
+// gets a copy of the value
+// Arguments:
+// output: pTo - buffer to hold the object copy. The caller must guarantee that this
+// is non-null and the buffer is large enough to hold the object
+// Return Value: S_OK or CORDBG_E_INVALID_OBJECT, CORDBG_E_OBJECT_NEUTERED, or E_INVALIDARG on failure
+//
+HRESULT CordbObjectValue::GetValue(void *pTo)
+{
+ FAIL_IF_NEUTERED(this);
+ COV_VALIDATE_OBJECT();
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pTo, BYTE, m_size, false, true);
+
+ // Copy out the value, which is the whole object.
+ memcpy(pTo, m_pObjectCopy, m_size);
+
+ return S_OK;
+} // CordbObjectValue::GetValue
+
+HRESULT CordbObjectValue::SetValue(void *pFrom)
+{
+ // You're not allowed to set a whole object at once.
+ return E_INVALIDARG;
+} // CordbObjectValue::SetValue
+
+// If this instance of CordbObjectValue is actually a string, get its length
+// Arguments:
+// output: pcchString - the count of characters in the string
+// Return Value: S_OK or CORDBG_E_INVALID_OBJECT, CORDBG_E_OBJECT_NEUTERED, or E_INVALIDARG on failure
+// Note: if the object is not really a string, the value in pcchString will be garbage on exit
+HRESULT CordbObjectValue::GetLength(ULONG32 *pcchString)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pcchString, SIZE_T *);
+ FAIL_IF_NEUTERED(this);
+
+ _ASSERTE(m_info.objTypeData.elementType == ELEMENT_TYPE_STRING);
+
+ COV_VALIDATE_OBJECT();
+
+ *pcchString = (ULONG32)m_info.stringInfo.length;
+ return S_OK;
+} // CordbObjectValue::GetLength
+
+// If this instance of CordbObjectValue represents a string, extract the string and its length.
+// If cchString is less than the length of the string, we'll return only the first cchString characters
+// but pcchString will still hold the full length. If cchString is more than the string length, we'll
+// return only string length characters.
+// Arguments:
+// input: cchString - the maximum number of characters to return, including NULL terminator
+// output: pcchString - the actual length of the string, excluding NULL terminator (this may be greater than cchString)
+// szString - a buffer holding the string. The memory for this must be allocated and
+// managed by the caller and must have space for at least cchString characters
+// Return Value: S_OK or CORDBG_E_INVALID_OBJECT, CORDBG_E_OBJECT_NEUTERED, or E_INVALIDARG on failure
+HRESULT CordbObjectValue::GetString(ULONG32 cchString,
+ ULONG32 *pcchString,
+ __out_ecount_opt(cchString) WCHAR szString[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(szString, WCHAR, cchString, true, true);
+ VALIDATE_POINTER_TO_OBJECT(pcchString, SIZE_T *);
+
+ _ASSERTE(m_info.objTypeData.elementType == ELEMENT_TYPE_STRING);
+
+ COV_VALIDATE_OBJECT();
+
+ if ((szString == NULL) || (cchString == 0))
+ return E_INVALIDARG;
+
+ // Add 1 to include null terminator
+ SIZE_T len = m_info.stringInfo.length + 1;
+
+ // adjust length to the size of the buffer
+ if (cchString < len)
+ len = cchString;
+
+ memcpy(szString, m_stringBuffer, len * 2);
+ *pcchString = (ULONG32)m_info.stringInfo.length;
+
+ return S_OK;
+} // CordbObjectValue::GetString
+
+// Initialize an instance of CordbObjectValue, filling in the m_pObjectCopy field and, if appropriate,
+// string information.
+// Arguments: none
+// ReturnValue: S_OK on success or E_OUTOFMEMORY or read process memory errors on failure
+HRESULT CordbObjectValue::Init()
+{
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess()); //
+ LOG((LF_CORDB,LL_INFO1000,"Invoking COV::Init\n"));
+
+ HRESULT hr = S_OK;
+
+ _ASSERTE (m_info.objTypeData.elementType != ELEMENT_TYPE_GENERICINST);
+ _ASSERTE (m_info.objTypeData.elementType != ELEMENT_TYPE_VAR);
+ _ASSERTE (m_info.objTypeData.elementType != ELEMENT_TYPE_MVAR);
+
+ // Copy the entire object over to this process.
+ m_pObjectCopy = new (nothrow) BYTE[m_size];
+
+ if (m_pObjectCopy == NULL)
+ return E_OUTOFMEMORY;
+
+ EX_TRY
+ {
+ m_valueHome.GetValue(MemoryRange(m_pObjectCopy, m_size)); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+
+ // Compute offsets in bytes to the locals and to a string if this is a
+ // string object.
+ m_objectLocalVars = m_pObjectCopy + m_info.objOffsetToVars;
+
+ if (m_info.objTypeData.elementType == ELEMENT_TYPE_STRING)
+ m_stringBuffer = m_pObjectCopy + m_info.stringInfo.offsetToStringBase;
+
+ return hr;
+} // CordbObjectValue::Init
+
+// CordbObjectValue::GetThreadOwningMonitorLock
+// If a managed thread owns the monitor lock on this object then *ppThread
+// will point to that thread and S_OK will be returned. The thread object is valid
+// until the thread exits. *pAcquisitionCount will indicate the number of times
+// this thread would need to release the lock before it returns to being
+// unowned.
+// If no managed thread owns the monitor lock on this object then *ppThread
+// and pAcquisitionCount will be unchanged and S_FALSE returned.
+// If ppThread or pAcquisitionCount is not a valid pointer the result is
+// undefined.
+// If any error occurs such that it cannot be determined which, if any, thread
+// owns the monitor lock on this object then a failing HRESULT will be returned
+HRESULT CordbObjectValue::GetThreadOwningMonitorLock(ICorDebugThread **ppThread, DWORD *pAcquisitionCount)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return CordbHeapValue3Impl::GetThreadOwningMonitorLock(GetProcess(),
+ GetValueHome()->GetAddress(),
+ ppThread,
+ pAcquisitionCount);
+}
+
+// CordbObjectValue::GetMonitorEventWaitList
+// Provides an ordered list of threads which are queued on the event associated
+// with a monitor lock. The first thread in the list is the first thread which
+// will be released by the next call to Monitor.Pulse, the next thread in the list
+// will be released on the following call, and so on.
+// If this list is non-empty S_OK will be returned, if it is empty S_FALSE
+// will be returned (the enumeration is still valid, just empty).
+// In either case the enumeration interface is only usable for the duration
+// of the current synchronized state, however the threads interfaces dispensed
+// from it are valid until the thread exits.
+// If ppThread is not a valid pointer the result is undefined.
+// If any error occurs such that it cannot be determined which, if any, threads
+// are waiting for the monitor then a failing HRESULT will be returned
+HRESULT CordbObjectValue::GetMonitorEventWaitList(ICorDebugThreadEnum **ppThreadEnum)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return CordbHeapValue3Impl::GetMonitorEventWaitList(GetProcess(),
+ GetValueHome()->GetAddress(),
+ ppThreadEnum);
+}
+
+HRESULT CordbObjectValue::EnumerateExceptionCallStack(ICorDebugExceptionObjectCallStackEnum** ppCallStackEnum)
+{
+ if (!ppCallStackEnum)
+ return E_INVALIDARG;
+
+ *ppCallStackEnum = NULL;
+
+ HRESULT hr = S_OK;
+ CorDebugExceptionObjectStackFrame* pStackFrames = NULL;
+
+ PUBLIC_API_BEGIN(this);
+
+ CORDB_ADDRESS objAddr = m_valueHome.GetAddress();
+
+ IDacDbiInterface* pDAC = GetProcess()->GetDAC();
+ VMPTR_Object vmObj = pDAC->GetObject(objAddr);
+
+ DacDbiArrayList<DacExceptionCallStackData> dacStackFrames;
+
+ pDAC->GetStackFramesFromException(vmObj, dacStackFrames);
+ int stackFramesLength = dacStackFrames.Count();
+
+ if (stackFramesLength > 0)
+ {
+ pStackFrames = new CorDebugExceptionObjectStackFrame[stackFramesLength];
+ for (int index = 0; index < stackFramesLength; ++index)
+ {
+ DacExceptionCallStackData& currentDacFrame = dacStackFrames[index];
+ CorDebugExceptionObjectStackFrame& currentStackFrame = pStackFrames[index];
+
+ CordbAppDomain* pAppDomain = GetProcess()->LookupOrCreateAppDomain(currentDacFrame.vmAppDomain);
+ CordbModule* pModule = pAppDomain->LookupOrCreateModule(currentDacFrame.vmDomainFile);
+
+ hr = pModule->QueryInterface(IID_ICorDebugModule, reinterpret_cast<void**>(&currentStackFrame.pModule));
+ _ASSERTE(SUCCEEDED(hr));
+
+ currentStackFrame.ip = currentDacFrame.ip;
+ currentStackFrame.methodDef = currentDacFrame.methodDef;
+ currentStackFrame.isLastForeignExceptionFrame = currentDacFrame.isLastForeignExceptionFrame;
+ }
+ }
+
+ CordbExceptionObjectCallStackEnumerator* callStackEnum = new CordbExceptionObjectCallStackEnumerator(GetProcess(), pStackFrames, stackFramesLength);
+ GetProcess()->GetContinueNeuterList()->Add(GetProcess(), callStackEnum);
+
+ hr = callStackEnum->QueryInterface(IID_ICorDebugExceptionObjectCallStackEnum, reinterpret_cast<void**>(ppCallStackEnum));
+ _ASSERTE(SUCCEEDED(hr));
+
+ PUBLIC_API_END(hr);
+
+ if (pStackFrames)
+ delete[] pStackFrames;
+
+ return hr;
+}
+
+HRESULT CordbObjectValue::IsExceptionObject()
+{
+ HRESULT hr = S_OK;
+
+ if (m_info.objTypeData.elementType != ELEMENT_TYPE_CLASS)
+ {
+ hr = S_FALSE;
+ }
+ else
+ {
+ CORDB_ADDRESS objAddr = m_valueHome.GetAddress();
+
+ if (objAddr == NULL)
+ {
+ // object is a literal
+ hr = S_FALSE;
+ }
+ else
+ {
+ IDacDbiInterface* pDAC = GetProcess()->GetDAC();
+
+ VMPTR_Object vmObj = pDAC->GetObject(objAddr);
+ BOOL fIsException = pDAC->IsExceptionObject(vmObj);
+
+ if (!fIsException)
+ hr = S_FALSE;
+ }
+ }
+
+ return hr;
+}
+
+HRESULT CordbObjectValue::IsRcw()
+{
+ HRESULT hr = S_OK;
+
+ if (m_info.objTypeData.elementType != ELEMENT_TYPE_CLASS)
+ {
+ hr = S_FALSE;
+ }
+ else
+ {
+ CORDB_ADDRESS objAddr = m_valueHome.GetAddress();
+
+ if (objAddr == NULL)
+ {
+ // object is a literal
+ hr = S_FALSE;
+ }
+ else
+ {
+ IDacDbiInterface* pDAC = GetProcess()->GetDAC();
+
+ VMPTR_Object vmObj = pDAC->GetObject(objAddr);
+ BOOL fIsRcw = pDAC->IsRcw(vmObj);
+
+ if (!fIsRcw)
+ hr = S_FALSE;
+ }
+ }
+
+ return hr;
+}
+
+HRESULT CordbObjectValue::GetCachedInterfaceTypes(
+ BOOL bIInspectableOnly,
+ ICorDebugTypeEnum * * ppInterfacesEnum)
+{
+#if !defined(FEATURE_COMINTEROP)
+
+ return E_NOTIMPL;
+
+#else
+
+ HRESULT hr = S_OK;
+
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ VALIDATE_POINTER_TO_OBJECT(ppInterfacesEnum, ICorDebugTypeEnum **);
+
+ _ASSERTE(m_fIsRcw);
+
+ EX_TRY
+ {
+ *ppInterfacesEnum = NULL;
+
+ NewArrayHolder<CordbType*> pItfs(NULL);
+
+ // retrieve interface types
+ DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> dacInterfaces;
+
+ IDacDbiInterface* pDAC = GetProcess()->GetDAC();
+
+ CORDB_ADDRESS objAddr = m_valueHome.GetAddress();
+ VMPTR_Object vmObj = pDAC->GetObject(objAddr);
+
+ // retrieve type info from LS
+ pDAC->GetRcwCachedInterfaceTypes(vmObj, m_appdomain->GetADToken(),
+ bIInspectableOnly, &dacInterfaces);
+
+ // synthesize CordbType instances
+ int cItfs = dacInterfaces.Count();
+ if (cItfs > 0)
+ {
+ pItfs = new CordbType*[cItfs];
+ for (int n = 0; n < cItfs; ++n)
+ {
+ hr = CordbType::TypeDataToType(m_appdomain,
+ &(dacInterfaces[n]),
+ &pItfs[n]);
+ }
+ }
+
+ // build a type enumerator
+ CordbTypeEnum* pTypeEnum = CordbTypeEnum::Build(m_appdomain, GetProcess()->GetContinueNeuterList(), cItfs, pItfs);
+ if ( pTypeEnum == NULL )
+ {
+ IfFailThrow(E_OUTOFMEMORY);
+ }
+
+ (*ppInterfacesEnum) = static_cast<ICorDebugTypeEnum*> (pTypeEnum);
+ pTypeEnum->ExternalAddRef();
+
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+
+#endif
+}
+
+HRESULT CordbObjectValue::GetCachedInterfacePointers(
+ BOOL bIInspectableOnly,
+ ULONG32 celt,
+ ULONG32 *pcEltFetched,
+ CORDB_ADDRESS * ptrs)
+{
+#if !defined(FEATURE_COMINTEROP)
+
+ return E_NOTIMPL;
+
+#else
+
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ _ASSERTE(m_fIsRcw);
+
+ if (pcEltFetched == NULL && (ptrs == NULL || celt == 0))
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+ ULONG32 cItfs = 0;
+
+ // retrieve interface types
+
+ CORDB_ADDRESS objAddr = m_valueHome.GetAddress();
+
+ DacDbiArrayList<CORDB_ADDRESS> dacItfPtrs;
+ EX_TRY
+ {
+ IDacDbiInterface* pDAC = GetProcess()->GetDAC();
+ VMPTR_Object vmObj = pDAC->GetObject(objAddr);
+
+ // retrieve type info from LS
+ pDAC->GetRcwCachedInterfacePointers(vmObj, bIInspectableOnly, &dacItfPtrs);
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+
+ // synthesize CordbType instances
+ cItfs = (ULONG32)dacItfPtrs.Count();
+
+ if (pcEltFetched != NULL && ptrs == NULL)
+ {
+ *pcEltFetched = cItfs;
+ return S_OK;
+ }
+
+ if (pcEltFetched != NULL)
+ {
+ *pcEltFetched = (cItfs <= celt ? cItfs : celt);
+ }
+
+ if (ptrs != NULL && *pcEltFetched > 0)
+ {
+ for (ULONG32 i = 0; i < *pcEltFetched; ++i)
+ ptrs[i] = dacItfPtrs[i];
+ }
+
+ return (*pcEltFetched == celt ? S_OK : S_FALSE);
+
+#endif
+}
+
+
+/* ------------------------------------------------------------------------- *
+ * Value Class Object
+ * ------------------------------------------------------------------------- */
+
+// constructor
+// Arguments:
+// input: pAppdomain - app domain to which the value belongs
+// pType - type information for the value
+// remoteValue - buffer describing the target location of the value
+// ppRemoteRegAddr - describes the register information if the value resides in a register
+// Note: May throw E_OUTOFMEMORY
+CordbVCObjectValue::CordbVCObjectValue(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ TargetBuffer remoteValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr)
+
+ // We'd like to neuter this on Continue (not just exit), but it may be a breaking change,
+ // especially for ValueTypes that don't have any GC refs in them.
+ : CordbValue(pAppdomain,
+ pType,
+ remoteValue.pAddress,
+ false,
+ pAppdomain->GetSweepableExitNeuterList()),
+ m_pObjectCopy(NULL),
+ m_pValueHome(NULL)
+{
+ // instantiate the value home
+ NewHolder<ValueHome> pHome(NULL);
+
+ if (remoteValue.IsEmpty())
+ {
+ pHome = (new RegisterValueHome(pAppdomain->GetProcess(), ppRemoteRegAddr));
+ }
+ else
+ {
+ pHome = (new VCRemoteValueHome(pAppdomain->GetProcess(), remoteValue));
+ }
+ m_pValueHome = pHome.GetValue(); // throws
+ pHome.SuppressRelease();
+} // CordbVCObjectValue::CordbVCObjectValue
+
+// destructor
+CordbVCObjectValue::~CordbVCObjectValue()
+{
+ DTOR_ENTRY(this);
+
+ _ASSERTE(IsNeutered());
+
+ // Destroy the copy of the object.
+ if (m_pObjectCopy != NULL)
+ {
+ delete [] m_pObjectCopy;
+ m_pObjectCopy = NULL;
+ }
+
+ // destroy the value home
+ if (m_pValueHome != NULL)
+ {
+ delete m_pValueHome;
+ m_pValueHome = NULL;
+}
+} // CordbVCObjectValue::~CordbVCObjectValue
+
+HRESULT CordbVCObjectValue::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugValue)
+ {
+ *pInterface = static_cast<ICorDebugValue*>(static_cast<ICorDebugObjectValue*>(this));
+ }
+ else if (id == IID_ICorDebugValue2)
+ {
+ *pInterface = static_cast<ICorDebugValue2*>(this);
+ }
+ else if (id == IID_ICorDebugValue3)
+ {
+ *pInterface = static_cast<ICorDebugValue3*>(this);
+ }
+ else if (id == IID_ICorDebugObjectValue)
+ {
+ *pInterface = static_cast<ICorDebugObjectValue*>(this);
+ }
+ else if (id == IID_ICorDebugObjectValue2)
+
+ {
+ *pInterface = static_cast<ICorDebugObjectValue2*>(this);
+ }
+ else if (id == IID_ICorDebugGenericValue)
+ {
+ *pInterface = static_cast<ICorDebugGenericValue*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugObjectValue*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+} // CordbVCObjectValue::QueryInterface
+
+// returns the basic type of the ICDValue
+// Arguments:
+// output: pType - the type of the ICDValue (always E_T_VALUETYPE)
+// ReturnValue: S_OK on success or E_INVALIDARG if pType is NULL
+HRESULT CordbVCObjectValue::GetType(CorElementType *pType)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pType, CorElementType *);
+
+ *pType = ELEMENT_TYPE_VALUETYPE;
+ return S_OK;
+} // CordbVCObjectValue::GetType
+
+// public API to get the CordbClass field
+// Arguments:
+// output: ppClass - holds a pointer to the ICDClass instance belonging to this
+// Return Value: S_OK on success, CORDBG_E_OBJECT_NEUTERED or synchronization errors on failure
+HRESULT CordbVCObjectValue::GetClass(ICorDebugClass **ppClass)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ *ppClass = (ICorDebugClass*) GetClass();
+
+ if (*ppClass != NULL)
+ (*ppClass)->AddRef();
+
+ return S_OK;
+} // CordbVCObjectValue::GetClass
+
+// internal method to get the CordbClass field
+// Arguments: none
+// ReturnValue: the instance of CordbClass belonging to this VC object
+CordbClass *CordbVCObjectValue::GetClass()
+{
+ CordbClass *tycon;
+ Instantiation inst;
+ m_type->DestConstructedType(&tycon, &inst);
+ return tycon;
+} // CordbVCObjectValue::GetClass
+
+//-----------------------------------------------------------------------------
+//
+// Finds the given field of the given type in the object and returns an ICDValue for it.
+//
+// Arguments:
+// pType - The type of the field
+// fieldDef - The field's metadata def.
+// ppValue - OUT: the ICDValue for the field.
+//
+// Returns:
+// S_OK on success, CORDBG_E_OBJECT_NEUTERED, E_INVALIDARG, CORDBG_E_ENC_HANGING_FIELD, or various other
+// failure codes
+HRESULT CordbVCObjectValue::GetFieldValueForType(ICorDebugType * pType,
+ mdFieldDef fieldDef,
+ ICorDebugValue ** ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Validate the token.
+ if ((m_type->m_pClass == NULL) || !m_type->m_pClass->GetModule()->GetMetaDataImporter()->IsValidToken(fieldDef))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+
+ CordbType * pCordbType;
+
+ //
+ // <TODO>@todo: need to ensure that pClass is really on the class
+ // hierarchy of m_class!!!</TODO>
+ //
+ if (pType == NULL)
+ {
+ pCordbType = m_type;
+ }
+ else
+ {
+ pCordbType = static_cast<CordbType *> (pType);
+ }
+
+ FieldData * pFieldData;
+
+ #ifdef _DEBUG
+ pFieldData = NULL;
+ #endif
+
+ hr = pCordbType->GetFieldInfo(fieldDef, &pFieldData);
+ _ASSERTE(hr != CORDBG_E_ENC_HANGING_FIELD);
+
+ // If we get back CORDBG_E_ENC_HANGING_FIELD we'll just fail -
+ // value classes should not be able to add fields once they're loaded,
+ // since the new fields _can't_ be contiguous with the old fields,
+ // and having all the fields contiguous is kinda the point of a V.C.
+ IfFailThrow(hr);
+
+ _ASSERTE(pFieldData != NULL);
+
+ CordbModule * pModule = pCordbType->m_pClass->GetModule();
+
+ SigParser sigParser;
+ IfFailThrow(pFieldData->GetFieldSignature(pModule, &sigParser));
+
+ // <TODO>
+ // How can I assert that I have exactly one field?
+ // </TODO>
+ CordbType * pFieldType;
+
+ IfFailThrow(CordbType::SigToType(pModule, &sigParser, &(pCordbType->m_inst), &pFieldType));
+
+ _ASSERTE(pFieldData->OkToGetOrSetInstanceOffset());
+ // Compute the address of the field contents in our local object cache
+ SIZE_T fieldOffset = pFieldData->GetInstanceOffset();
+ ULONG32 size = GetSizeForType(pFieldType, kUnboxed);
+
+ // verify that the field starts before the end of m_pObjectCopy
+ _ASSERTE(fieldOffset < m_size);
+ _ASSERTE(fieldOffset + size <= m_size);
+
+ m_pValueHome->CreateInternalValue(pFieldType,
+ fieldOffset,
+ m_pObjectCopy + fieldOffset,
+ size,
+ ppValue); // throws
+
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbVCObjectValue::GetFieldValueForType
+
+// gets an ICDValue to represent a field of the VC object
+// Arguments:
+// input: pClass - the class information for this object (needed to get the parent class information)
+// fieldDef - field token for the desired field
+// output: ppValue - on success, the ICDValue representing the desired field
+// Return Value: S_OK on success, CORDBG_E_OBJECT_NEUTERED, CORDBG_E_CLASS_NOT_LOADED, E_INVALIDARG, OOM,
+// CORDBG_E_ENC_HANGING_FIELD, or various other failure codes
+HRESULT CordbVCObjectValue::GetFieldValue(ICorDebugClass *pClass,
+ mdFieldDef fieldDef,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ VALIDATE_POINTER_TO_OBJECT(pClass, ICorDebugClass *);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+
+ HRESULT hr;
+ _ASSERTE(m_type);
+
+ if (m_type->m_elementType != ELEMENT_TYPE_CLASS &&
+ m_type->m_elementType != ELEMENT_TYPE_VALUETYPE)
+ {
+ return E_INVALIDARG;
+ }
+
+ RSExtSmartPtr<CordbType> relevantType;
+
+ if (FAILED (hr= m_type->GetParentType((CordbClass *) pClass, &relevantType)))
+ {
+ return hr;
+ }
+ // Upon exit relevantType will either be the appropriate type for the
+ // class we're looking for.
+
+ hr = GetFieldValueForType(relevantType, fieldDef, ppValue);
+ // GetParentType ands one reference to relevantType, holder dtor releases that.
+ return hr;
+
+} // CordbVCObjectValue::GetFieldValue
+
+// get a copy of the VC object
+// Arguments:
+// output: pTo - a caller-allocated buffer to hold the copy
+// Return Value: S_OK on success, CORDBG_E_OBJECT_NEUTERED on failure
+// Note: The caller must ensure the buffer is large enough to hold the value (by a previous call to GetSize)
+// and is responsible for allocation and deallocation.
+HRESULT CordbVCObjectValue::GetValue(void *pTo)
+{
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pTo, BYTE, m_size, false, true);
+ FAIL_IF_NEUTERED(this);
+
+ // Copy out the value, which is the whole object.
+ memcpy(pTo, m_pObjectCopy, m_size);
+
+ return S_OK;
+} // CordbVCObjectValue::GetValue
+
+// set the value of a VC object
+// Arguments:
+// input: pSrc - buffer containing the new value. Allocated and managed by the caller.
+// Return Value: S_OK on success, CORDBG_E_OBJECT_NEUTERED, synchronization errors, E_INVALIDARG, write
+// process memory errors, CORDBG_E_CLASS_NOT_LOADED or OOM on failure
+HRESULT CordbVCObjectValue::SetValue(void * pSrc)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pSrc, BYTE, m_size, true, false);
+
+ // Can't change literals...
+ if (m_isLiteral)
+ return E_INVALIDARG;
+
+ if (m_type)
+ {
+ IfFailRet(m_type->Init(FALSE));
+ }
+
+ EX_TRY
+ {
+ m_pValueHome->SetValue(MemoryRange(pSrc, m_size), m_type); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ if (SUCCEEDED(hr))
+ {
+ // That worked, so update the copy of the value we have over here.
+ memcpy(m_pObjectCopy, pSrc, m_size);
+ }
+
+ return hr;
+} // CordbVCObjectValue::SetValue
+
+HRESULT CordbVCObjectValue::GetVirtualMethod(mdMemberRef memberRef,
+ ICorDebugFunction **ppFunction)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbVCObjectValue::GetVirtualMethodAndType(mdMemberRef memberRef,
+ ICorDebugFunction **ppFunction,
+ ICorDebugType **ppType)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbVCObjectValue::GetContext(ICorDebugContext **ppContext)
+{
+ return E_NOTIMPL;
+}
+
+// self-identifier--always returns true as long as pbIsValueClass is non-Null
+HRESULT CordbVCObjectValue::IsValueClass(BOOL *pbIsValueClass)
+{
+ if (pbIsValueClass)
+ *pbIsValueClass = TRUE;
+
+ return S_OK;
+} // CordbVCObjectValue::IsValueClass
+
+HRESULT CordbVCObjectValue::GetManagedCopy(IUnknown **ppObject)
+{
+ // This function is deprecated
+ return E_NOTIMPL;
+}
+
+HRESULT CordbVCObjectValue::SetFromManagedCopy(IUnknown *pObject)
+{
+ // This function is deprecated
+ return E_NOTIMPL;
+}
+
+ //
+// CordbVCObjectValue::Init
+//
+// Description
+// Initializes the Right-Side's representation of a Value Class object.
+// Parameters
+// input: localValue - buffer containing the value if this instance of CordbObjectValue
+// was a field or array element of an existing value, otherwise this
+// will have a start address equal to NULL
+// Returns
+// HRESULT
+// S_OK if the function completed normally
+// failing HR otherwise
+// Exceptions
+// None
+//
+HRESULT CordbVCObjectValue::Init(MemoryRange localValue)
+{
+ HRESULT hr = S_OK;
+
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess()); //
+
+ // Get the object size from the class
+ ULONG32 size;
+ IfFailRet( m_type->GetUnboxedObjectSize(&size) );
+ m_size = size;
+
+ // Copy the entire object over to this process.
+ m_pObjectCopy = new (nothrow) BYTE[m_size];
+
+ if (m_pObjectCopy == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ if (localValue.StartAddress() != NULL)
+ {
+ // The data is already in the local address space. Go ahead and copy it
+ // from there.
+ // localValue.StartAddress points to:
+ // 1. A field from the local cached copy belonging to an instance of CordbVCObjectValue (different
+ // instance from "this") or CordbObjectValue
+ // 2. An element in the locally cached subrange of an array belonging to an instance of CordbArrayValue
+ // 3. The address of a particular register in the register display of an instance of CordbNativeFrame
+ // for an enregistered value type. In this case, it's possible that the size of the value is
+ // smaller than the size of a full register. For that reason, we can't just use localValue.Size()
+ // as the number of bytes to copy, because only enough space for the value has been allocated.
+ _ASSERTE(localValue.Size() >= m_size);
+ localCopy(m_pObjectCopy, MemoryRange(localValue.StartAddress(), m_size));
+ return S_OK;
+ }
+
+ EX_TRY
+ {
+ m_pValueHome->GetValue(MemoryRange(m_pObjectCopy, m_size)); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbVCObjectValue::Init
+
+/* ------------------------------------------------------------------------- *
+ * Box Value class
+ * ------------------------------------------------------------------------- */
+
+// constructor
+// Arguments:
+// input: appdomain - app domain to which the value belongs
+// type - type information for the boxed value
+// remoteValue - buffer describing the remote location of the value
+// size - size of the value
+// offsetToVars - offset from the beginning of the value to the first field of the value
+CordbBoxValue::CordbBoxValue(CordbAppDomain *appdomain,
+ CordbType *type,
+ TargetBuffer remoteValue,
+ ULONG32 size,
+ SIZE_T offsetToVars)
+ : CordbValue(appdomain, type, remoteValue.pAddress, false, appdomain->GetProcess()->GetContinueNeuterList()),
+ m_offsetToVars(offsetToVars),
+ m_valueHome(appdomain->GetProcess(), remoteValue)
+{
+ m_size = size;
+} // CordbBoxValue::CordbBoxValue
+
+// destructor
+CordbBoxValue::~CordbBoxValue()
+{
+ DTOR_ENTRY(this);
+ _ASSERTE(IsNeutered());
+} // CordbBoxValue::~CordbBoxValue
+
+HRESULT CordbBoxValue::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugValue)
+ {
+ *pInterface = static_cast<ICorDebugValue*>(static_cast<ICorDebugBoxValue*>(this));
+ }
+ else if (id == IID_ICorDebugValue2)
+ {
+ *pInterface = static_cast<ICorDebugValue2*>(this);
+ }
+ else if (id == IID_ICorDebugValue3)
+ {
+ *pInterface = static_cast<ICorDebugValue3*>(this);
+ }
+ else if (id == IID_ICorDebugBoxValue)
+ {
+ *pInterface = static_cast<ICorDebugBoxValue*>(this);
+ }
+ else if (id == IID_ICorDebugGenericValue)
+ {
+ *pInterface = static_cast<ICorDebugGenericValue*>(this);
+ }
+ else if (id == IID_ICorDebugHeapValue)
+ {
+ *pInterface = static_cast<ICorDebugHeapValue*>(this);
+ }
+ else if (id == IID_ICorDebugHeapValue2)
+ {
+ *pInterface = static_cast<ICorDebugHeapValue2*>(this);
+ }
+ else if (id == IID_ICorDebugHeapValue3)
+ {
+ *pInterface = static_cast<ICorDebugHeapValue3*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugBoxValue*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+} // CordbBoxValue::QueryInterface
+
+// returns the basic type of the ICDValue
+// Arguments:
+// output: pType - the type of the ICDValue (always E_T_CLASS)
+// ReturnValue: S_OK on success or E_INVALIDARG if pType is NULL
+HRESULT CordbBoxValue::GetType(CorElementType *pType)
+{
+ VALIDATE_POINTER_TO_OBJECT(pType, CorElementType *);
+
+ *pType = ELEMENT_TYPE_CLASS;
+
+ return (S_OK);
+} // CordbBoxValue::GetType
+
+HRESULT CordbBoxValue::IsValid(BOOL *pbValid)
+{
+ VALIDATE_POINTER_TO_OBJECT(pbValid, BOOL *);
+
+ // <TODO>@todo: implement tracking of objects across collections.</TODO>
+
+ return E_NOTIMPL;
+}
+
+HRESULT CordbBoxValue::CreateRelocBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint)
+{
+ VALIDATE_POINTER_TO_OBJECT(ppBreakpoint, ICorDebugValueBreakpoint **);
+
+ return E_NOTIMPL;
+}
+
+// Creates a handle of the given type for this heap value.
+// Not Implemented In-Proc.
+// Create a handle for a heap object.
+// @todo: How to prevent this being called by non-heap object?
+// Arguments:
+// input: handleType - type of the handle to be created
+// output: ppHandle - on success, the newly created handle
+// Return Value: S_OK on success or E_INVALIDARG, E_OUTOFMEMORY, or CORDB_E_HELPER_MAY_DEADLOCK
+HRESULT CordbBoxValue::CreateHandle(
+ CorDebugHandleType handleType,
+ ICorDebugHandleValue ** ppHandle)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return CordbValue::InternalCreateHandle(handleType, ppHandle);
+} // CordbBoxValue::CreateHandle
+
+HRESULT CordbBoxValue::GetValue(void *pTo)
+{
+ // Can't get a whole copy of a box.
+ return E_INVALIDARG;
+}
+
+HRESULT CordbBoxValue::SetValue(void *pFrom)
+{
+ // You're not allowed to set a box value.
+ return E_INVALIDARG;
+}
+
+// gets the unboxed value from this boxed value
+// Arguments:
+// output: ppObject - pointer to an instance of ICDValue representing the unboxed value, unless ppObject
+// is NULL
+// Return Value: S_OK on success or a variety of possible failures: OOM, E_FAIL, errors from
+// ReadProcessMemory.
+HRESULT CordbBoxValue::GetObject(ICorDebugObjectValue **ppObject)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppObject, ICorDebugObjectValue **);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ ULONG32 size;
+ m_type->GetUnboxedObjectSize(&size);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ m_valueHome.CreateInternalValue(m_type,
+ m_offsetToVars,
+ NULL,
+ size,
+ reinterpret_cast<ICorDebugValue **>(ppObject)); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbBoxValue::GetObject
+
+// If a managed thread owns the monitor lock on this object then *ppThread
+// will point to that thread and S_OK will be returned. The thread object is valid
+// until the thread exits. *pAcquisitionCount will indicate the number of times
+// this thread would need to release the lock before it returns to being
+// unowned.
+// If no managed thread owns the monitor lock on this object then *ppThread
+// and pAcquisitionCount will be unchanged and S_FALSE returned.
+// If ppThread or pAcquisitionCount is not a valid pointer the result is
+// undefined.
+// If any error occurs such that it cannot be determined which, if any, thread
+// owns the monitor lock on this object then a failing HRESULT will be returned
+HRESULT CordbBoxValue::GetThreadOwningMonitorLock(ICorDebugThread **ppThread, DWORD *pAcquisitionCount)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return CordbHeapValue3Impl::GetThreadOwningMonitorLock(GetProcess(),
+ GetValueHome()->GetAddress(),
+ ppThread,
+ pAcquisitionCount);
+}
+
+// Provides an ordered list of threads which are queued on the event associated
+// with a monitor lock. The first thread in the list is the first thread which
+// will be released by the next call to Monitor.Pulse, the next thread in the list
+// will be released on the following call, and so on.
+// If this list is non-empty S_OK will be returned, if it is empty S_FALSE
+// will be returned (the enumeration is still valid, just empty).
+// In either case the enumeration interface is only usable for the duration
+// of the current synchronized state, however the threads interfaces dispensed
+// from it are valid until the thread exits.
+// If ppThread is not a valid pointer the result is undefined.
+// If any error occurs such that it cannot be determined which, if any, threads
+// are waiting for the monitor then a failing HRESULT will be returned
+HRESULT CordbBoxValue::GetMonitorEventWaitList(ICorDebugThreadEnum **ppThreadEnum)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return CordbHeapValue3Impl::GetMonitorEventWaitList(GetProcess(),
+ GetValueHome()->GetAddress(),
+ ppThreadEnum);
+}
+
+
+/* ------------------------------------------------------------------------- *
+ * Array Value class
+ * ------------------------------------------------------------------------- */
+
+// The size of the buffer we allocate to hold array elements.
+// Note that since we must be able to hold at least one element, we may
+// allocate larger than the cache size here.
+// Also, this cache doesn't include a small header used to store the rank vectors
+#ifdef _DEBUG
+// For debug, use a small size to cause more churn
+ #define ARRAY_CACHE_SIZE (1000)
+#else
+// For release, guess 4 pages should be enough. Subtract some bytes to store
+// the header so that that doesn't push us onto another page. (We guess a reasonable
+// header size, but it's ok if it's larger).
+ #define ARRAY_CACHE_SIZE (4 * 4096 - 24)
+#endif
+
+// constructor
+// Arguments:
+// input:
+// pAppDomain - app domain to which the value belongs
+// pType - type information for the value
+// pObjectInfo - array specific type information
+// remoteValue - buffer describing the remote location of the value
+CordbArrayValue::CordbArrayValue(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ DebuggerIPCE_ObjectData * pObjectInfo,
+ TargetBuffer remoteValue)
+ : CordbValue(pAppdomain,
+ pType,
+ remoteValue.pAddress,
+ false,
+ pAppdomain->GetProcess()->GetContinueNeuterList()),
+ m_info(*pObjectInfo),
+ m_pObjectCopy(NULL),
+ m_valueHome(pAppdomain->GetProcess(), remoteValue)
+{
+ m_size = m_info.objSize;
+ pType->DestUnaryType(&m_elemtype);
+
+// Set range to illegal values to force a load on first access
+ m_idxLower = m_idxUpper = (SIZE_T) -1;
+} // CordbArrayValue::CordbArrayValue
+
+// destructor
+CordbArrayValue::~CordbArrayValue()
+{
+ DTOR_ENTRY(this);
+ _ASSERTE(IsNeutered());
+
+ // Destroy the copy of the object.
+ if (m_pObjectCopy != NULL)
+ delete [] m_pObjectCopy;
+} // CordbArrayValue::~CordbArrayValue
+
+HRESULT CordbArrayValue::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugValue)
+ {
+ *pInterface = static_cast<ICorDebugValue*>(static_cast<ICorDebugArrayValue*>(this));
+ }
+ else if (id == IID_ICorDebugValue2)
+ {
+ *pInterface = static_cast<ICorDebugValue2*>(this);
+ }
+ else if (id == IID_ICorDebugValue3)
+ {
+ *pInterface = static_cast<ICorDebugValue3*>(this);
+ }
+ else if (id == IID_ICorDebugArrayValue)
+ {
+ *pInterface = static_cast<ICorDebugArrayValue*>(this);
+ }
+ else if (id == IID_ICorDebugGenericValue)
+ {
+ *pInterface = static_cast<ICorDebugGenericValue*>(this);
+ }
+ else if (id == IID_ICorDebugHeapValue)
+ {
+ *pInterface = static_cast<ICorDebugHeapValue*>(this);
+ }
+ else if (id == IID_ICorDebugHeapValue2)
+ {
+ *pInterface = static_cast<ICorDebugHeapValue2*>(this);
+ }
+ else if (id == IID_ICorDebugHeapValue3)
+ {
+ *pInterface = static_cast<ICorDebugHeapValue3*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugArrayValue*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+} // CordbArrayValue::QueryInterface
+
+// gets the type of the array elements
+// Arguments:
+// output: pType - the element type unless pType is NULL
+// Return Value: S_OK on success or E_INVALIDARG if pType is null
+HRESULT CordbArrayValue::GetElementType(CorElementType *pType)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pType, CorElementType *);
+
+ *pType = m_elemtype->m_elementType;
+ return S_OK;
+} // CordbArrayValue::GetElementType
+
+
+// gets the rank of the array
+// Arguments:
+// output: pnRank - the rank of the array unless pnRank is null
+// Return Value: S_OK on success or E_INVALIDARG if pnRank is null
+HRESULT CordbArrayValue::GetRank(ULONG32 *pnRank)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pnRank, SIZE_T *);
+
+ // Rank info is duplicated for sanity checking - double check it here.
+ _ASSERTE(m_info.arrayInfo.rank == m_type->m_rank);
+ *pnRank = m_type->m_rank;
+ return S_OK;
+} // CordbArrayValue::GetRank
+
+// gets the number of elements in the array
+// Arguments:
+// output: pnCount - the number of dimensions for the array unless pnCount is null
+// Return Value: S_OK on success or E_INVALIDARG if pnCount is null
+HRESULT CordbArrayValue::GetCount(ULONG32 *pnCount)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pnCount, ULONG32 *);
+
+ *pnCount = (ULONG32)m_info.arrayInfo.componentCount;
+ return S_OK;
+} // CordbArrayValue::GetCount
+
+// get the size of each dimension of the array
+// Arguments:
+// input: cdim - the number of dimensions about which to get dimensions--this must be the same as the rank
+// output: dims - an array to hold the sizes of the dimensions of the array--this is allocated and
+// managed by the caller
+// Return Value: S_OK on success or E_INVALIDARG
+HRESULT CordbArrayValue::GetDimensions(ULONG32 cdim, ULONG32 dims[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(dims, SIZE_T, cdim, true, true);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // Rank info is duplicated for sanity checking - double check it here.
+ _ASSERTE(m_info.arrayInfo.rank == m_type->m_rank);
+ if (cdim != m_type->m_rank)
+ return E_INVALIDARG;
+
+ // SDArrays don't have bounds info, so return the component count.
+ if (cdim == 1)
+ dims[0] = (ULONG32)m_info.arrayInfo.componentCount;
+ else
+ {
+ _ASSERTE(m_info.arrayInfo.offsetToUpperBounds != 0);
+ _ASSERTE(m_arrayUpperBase != NULL);
+
+ // The upper bounds info in the array is the true size of each
+ // dimension.
+ for (unsigned int i = 0; i < cdim; i++)
+ dims[i] = m_arrayUpperBase[i];
+ }
+
+ return S_OK;
+} // CordbArrayValue::GetDimensions
+
+//
+// indicates whether the array has base indices
+// Arguments:
+// output: pbHasBaseIndices - true iff the array has more than one dimension and pbHasBaseIndices is not null
+// Return Value: S_OK on success or E_INVALIDARG if pbHasBaseIndices is null
+HRESULT CordbArrayValue::HasBaseIndicies(BOOL *pbHasBaseIndices)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pbHasBaseIndices, BOOL *);
+
+ *pbHasBaseIndices = m_info.arrayInfo.offsetToLowerBounds != 0;
+ return S_OK;
+} // CordbArrayValue::HasBaseIndicies
+
+// gets the base indices for a multidimensional array
+// Arguments:
+// input: cdim - the number of dimensions (this must be the same as the actual rank of the array)
+// indices - an array to hold the base indices for the array dimensions (allocated and managed
+// by the caller, it must have space for cdim elements)
+// Return Value: S_OK on success or E_INVALIDARG if cdim is not equal to the array rank or indices is null
+HRESULT CordbArrayValue::GetBaseIndicies(ULONG32 cdim, ULONG32 indices[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(indices, SIZE_T, cdim, true, true);
+
+ // Rank info is duplicated for sanity checking - double check it here.
+ _ASSERTE(m_info.arrayInfo.rank == m_type->m_rank);
+ if ((cdim != m_type->m_rank) ||
+ (m_info.arrayInfo.offsetToLowerBounds == 0))
+ return E_INVALIDARG;
+
+ _ASSERTE(m_arrayLowerBase != NULL);
+
+ for (unsigned int i = 0; i < cdim; i++)
+ indices[i] = m_arrayLowerBase[i];
+
+ return S_OK;
+} // CordbArrayValue::GetBaseIndicies
+
+// Get an element at the position indicated by the values in indices (one index for each dimension)
+// Arguments:
+// input: cdim - the number of dimensions and thus the number of elements in indices. This must match
+// the actual rank of the array value.
+// indices - an array of indices to specify the position of the element. For example, to get a[2][1][0],
+// indices would contain 2, 1, and 0 in that order.
+// output: ppValue - an ICDValue representing the element, unless an error occurs
+// Return Value: S_OK on success or E_INVALIDARG if cdim != rank, indices is NULL or ppValue is NULL
+// or a variety of possible failures: OOM, E_FAIL, errors from
+// ReadProcessMemory.
+HRESULT CordbArrayValue::GetElement(ULONG32 cdim,
+ ULONG32 indices[],
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(indices, SIZE_T, cdim, true, true);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ *ppValue = NULL;
+
+ // Rank info is duplicated for sanity checking - double check it here.
+ _ASSERTE(m_info.arrayInfo.rank == m_type->m_rank);
+ if ((cdim != m_type->m_rank) || (indices == NULL))
+ return E_INVALIDARG;
+
+ // If the array has lower bounds, adjust the indices.
+ if (m_info.arrayInfo.offsetToLowerBounds != 0)
+ {
+ _ASSERTE(m_arrayLowerBase != NULL);
+
+ for (unsigned int i = 0; i < cdim; i++)
+ indices[i] -= m_arrayLowerBase[i];
+ }
+
+ SIZE_T offset = 0;
+
+ // SDArrays don't have upper bounds
+ if (cdim == 1)
+ {
+ offset = indices[0];
+
+ // Bounds check
+ if (offset >= m_info.arrayInfo.componentCount)
+ return E_INVALIDARG;
+ }
+ else
+ {
+ _ASSERTE(m_info.arrayInfo.offsetToUpperBounds != 0);
+ _ASSERTE(m_arrayUpperBase != NULL);
+
+ // Calculate the offset in bytes for all dimensions.
+ SIZE_T multiplier = 1;
+
+ for (int i = cdim - 1; i >= 0; i--)
+ {
+ // Bounds check
+ if (indices[i] >= m_arrayUpperBase[i])
+ return E_INVALIDARG;
+
+ offset += indices[i] * multiplier;
+ multiplier *= m_arrayUpperBase[i];
+ }
+
+ _ASSERTE(offset < m_info.arrayInfo.componentCount);
+ }
+
+ return GetElementAtPosition((ULONG32)offset, ppValue);
+} // CordbArrayValue::GetElement
+
+// get an ICDValue to represent the element at a given position
+// Arguments:
+// input: nPosition - the offset from the beginning of the array to the element
+// output: ppValue - the ICDValue representing the array element on success
+// Return Value: S_OK on success, E_INVALIDARG or a variety of possible failures: OOM, E_FAIL, errors from
+// ReadProcessMemory.
+HRESULT CordbArrayValue::GetElementAtPosition(ULONG32 nPosition,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (nPosition >= m_info.arrayInfo.componentCount)
+ {
+ *ppValue = NULL;
+ return E_INVALIDARG;
+ }
+
+ // Rank info is duplicated for sanity checking - double check it here.
+ _ASSERTE(m_info.arrayInfo.rank == m_type->m_rank);
+
+ // The header consists of two DWORDs for each dimension, representing the upper and lower bound for that dimension. A
+ // vector of lower bounds comes first, followed by a vector of upper bounds. We want to copy a range of
+ // elements into m_pObjectCopy following these vectors, so we need to compute the address where the
+ // vectors end and the elements begin.
+ const int cbHeader = 2 * m_type->m_rank * sizeof(DWORD);
+ HRESULT hr = S_OK;
+
+ // Ensure that the proper subset is in the cache. m_idxLower and m_idxUpper are initialized to -1, so the
+ // first time we hit this condition check, it will evaluate to true. We will set these inside the
+ // consequent to the range starting at nPosition and ending at the last available cache position. Thus,
+ // after the first time we hit this, we are asking if nPosition lies outside the range we've cached.
+ if (nPosition < m_idxLower || nPosition >= m_idxUpper)
+ {
+ const SIZE_T cbElemSize = m_info.arrayInfo.elementSize;
+ SIZE_T len = 1;
+
+ if (cbElemSize != 0)
+ {
+ // the element size could be bigger than the cache, but we want len to be at least 1.
+ len = max(ARRAY_CACHE_SIZE / cbElemSize, len);
+ }
+ else _ASSERTE(cbElemSize != 0);
+
+ m_idxLower = nPosition;
+ m_idxUpper = min(m_idxLower + len, m_info.arrayInfo.componentCount);
+ _ASSERTE(m_idxLower < m_idxUpper);
+
+ SIZE_T cbOffsetFrom = m_info.arrayInfo.offsetToArrayBase + m_idxLower * cbElemSize;
+
+ SIZE_T cbSize = (m_idxUpper - m_idxLower) * cbElemSize; // we'll copy the largest range of ellements possible
+
+ _ASSERTE(cbSize <= m_info.objSize);
+ // Copy the proper subrange of the array over
+ EX_TRY
+ {
+ m_valueHome.GetInternalValue(MemoryRange(m_pObjectCopy + cbHeader, cbSize), cbOffsetFrom); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+ }
+
+ SIZE_T size = m_info.arrayInfo.elementSize;
+ _ASSERTE(size <= m_info.objSize);
+
+ SIZE_T offset = m_info.arrayInfo.offsetToArrayBase + (nPosition * size);
+ void * localAddress = m_pObjectCopy + cbHeader + ((nPosition - m_idxLower) * size);
+
+ EX_TRY
+ {
+ m_valueHome.CreateInternalValue(m_elemtype,
+ offset,
+ localAddress,
+ (ULONG32)size,
+ ppValue); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+
+} // CordbArrayValue::GetElementAtPosition
+
+HRESULT CordbArrayValue::IsValid(BOOL *pbValid)
+{
+ VALIDATE_POINTER_TO_OBJECT(pbValid, BOOL *);
+
+ // <TODO>@todo: implement tracking of objects across collections.</TODO>
+
+ return E_NOTIMPL;
+}
+
+HRESULT CordbArrayValue::CreateRelocBreakpoint(
+ ICorDebugValueBreakpoint **ppBreakpoint)
+{
+ VALIDATE_POINTER_TO_OBJECT(ppBreakpoint, ICorDebugValueBreakpoint **);
+
+ return E_NOTIMPL;
+}
+
+// Creates a handle of the given type for this heap value.
+// Not Implemented In-Proc.
+// Arguments:
+// input: handleType - type of the handle to be created
+// output: ppHandle - on success, the newly created handle
+// Return Value: S_OK on success or E_INVALIDARG, E_OUTOFMEMORY, or CORDB_E_HELPER_MAY_DEADLOCK
+HRESULT CordbArrayValue::CreateHandle(
+ CorDebugHandleType handleType,
+ ICorDebugHandleValue ** ppHandle)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return CordbValue::InternalCreateHandle(handleType, ppHandle);
+} // CordbArrayValue::CreateHandle
+
+// get a copy of the array
+// Arguments
+// output: pTo - pointer to a caller-allocated and managed buffer to hold the copy. The caller must guarantee
+// that this is large enough to hold the entire array
+// Return Value: S_OK on success, E_INVALIDARG or read process memory errors on failure
+HRESULT CordbArrayValue::GetValue(void *pTo)
+{
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pTo, void *, 1, false, true);
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Copy out the value, which is the whole array.
+ // There's no lazy-evaluation here, so this could be rather large
+ m_valueHome.GetValue(MemoryRange(pTo, m_size)); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbArrayValue::GetValue
+
+HRESULT CordbArrayValue::SetValue(void *pFrom)
+{
+ // You're not allowed to set a whole array at once.
+ return E_INVALIDARG;
+}
+
+// initialize a new instance of CordbArrayValue
+// Arguments: none
+// Return Value: S_OK on success or E_OUTOFMEMORY or read process memory errors on failure
+// Note: we are only initializing information about the array (rank, sizes, dimensions, etc) here. We will not
+// attempt to read array contents until we receive a request to do so.
+HRESULT CordbArrayValue::Init()
+{
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess()); //
+ HRESULT hr = S_OK;
+
+ SIZE_T cbVector = m_info.arrayInfo.rank * sizeof(DWORD);
+ _ASSERTE(cbVector <= m_info.objSize);
+
+ int cbHeader = 2 * (int)cbVector;
+
+ // Find largest data size that will fit in cache
+ SIZE_T cbData = m_info.arrayInfo.componentCount * m_info.arrayInfo.elementSize;
+ if (cbData > ARRAY_CACHE_SIZE)
+ {
+ cbData = (ARRAY_CACHE_SIZE / m_info.arrayInfo.elementSize)
+ * m_info.arrayInfo.elementSize;
+ }
+
+ if (cbData < m_info.arrayInfo.elementSize)
+ {
+ cbData = m_info.arrayInfo.elementSize;
+ }
+
+ // Allocate memory
+ m_pObjectCopy = new (nothrow) BYTE[cbHeader + cbData];
+ if (m_pObjectCopy == NULL)
+ return E_OUTOFMEMORY;
+
+
+ m_arrayLowerBase = NULL;
+ m_arrayUpperBase = NULL;
+
+ // Copy base vectors into header. (Offsets are 0 if the vectors aren't used)
+ if (m_info.arrayInfo.offsetToLowerBounds != 0)
+ {
+ m_arrayLowerBase = (DWORD*)(m_pObjectCopy);
+ EX_TRY
+ {
+ m_valueHome.GetInternalValue(MemoryRange(m_arrayLowerBase, cbVector),
+ m_info.arrayInfo.offsetToLowerBounds); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+ }
+
+
+ if (m_info.arrayInfo.offsetToUpperBounds != 0)
+ {
+ m_arrayUpperBase = (DWORD*)(m_pObjectCopy + cbVector);
+ EX_TRY
+ {
+ m_valueHome.GetInternalValue(MemoryRange(m_arrayUpperBase, cbVector),
+ m_info.arrayInfo.offsetToUpperBounds); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+ }
+
+ // That's all for now. We'll do lazy-evaluation for the array contents.
+
+ return hr;
+} // CordbArrayValue::Init
+
+// CordbArrayValue::GetThreadOwningMonitorLock
+// If a managed thread owns the monitor lock on this object then *ppThread
+// will point to that thread and S_OK will be returned. The thread object is valid
+// until the thread exits. *pAcquisitionCount will indicate the number of times
+// this thread would need to release the lock before it returns to being
+// unowned.
+// If no managed thread owns the monitor lock on this object then *ppThread
+// and pAcquisitionCount will be unchanged and S_FALSE returned.
+// If ppThread or pAcquisitionCount is not a valid pointer the result is
+// undefined.
+// If any error occurs such that it cannot be determined which, if any, thread
+// owns the monitor lock on this object then a failing HRESULT will be returned
+HRESULT CordbArrayValue::GetThreadOwningMonitorLock(ICorDebugThread **ppThread, DWORD *pAcquisitionCount)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return CordbHeapValue3Impl::GetThreadOwningMonitorLock(GetProcess(),
+ GetValueHome()->GetAddress(), ppThread, pAcquisitionCount);
+}
+
+// CordbArrayValue::GetMonitorEventWaitList
+// Provides an ordered list of threads which are queued on the event associated
+// with a monitor lock. The first thread in the list is the first thread which
+// will be released by the next call to Monitor.Pulse, the next thread in the list
+// will be released on the following call, and so on.
+// If this list is non-empty S_OK will be returned, if it is empty S_FALSE
+// will be returned (the enumeration is still valid, just empty).
+// In either case the enumeration interface is only usable for the duration
+// of the current synchronized state, however the threads interfaces dispensed
+// from it are valid until the thread exits.
+// If ppThread is not a valid pointer the result is undefined.
+// If any error occurs such that it cannot be determined which, if any, threads
+// are waiting for the monitor then a failing HRESULT will be returned
+HRESULT CordbArrayValue::GetMonitorEventWaitList(ICorDebugThreadEnum **ppThreadEnum)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return CordbHeapValue3Impl::GetMonitorEventWaitList(GetProcess(),
+ GetValueHome()->GetAddress(),
+ ppThreadEnum);
+}
+
+/* ------------------------------------------------------------------------- *
+ * Handle Value
+ * ------------------------------------------------------------------------- */
+// constructor
+// Arguments:
+// input:
+// pAppDomain - app domain to which the value belongs
+// pType - type information for the value
+// handleType - indicates whether we are constructing a strong or weak handle
+CordbHandleValue::CordbHandleValue(
+ CordbAppDomain * pAppdomain,
+ CordbType * pType, // The type of object that we create handle on
+ CorDebugHandleType handleType) // strong or weak handle
+ : CordbValue(pAppdomain, pType, NULL, false,
+ pAppdomain->GetSweepableExitNeuterList()
+ )
+{
+ m_vmHandle = VMPTR_OBJECTHANDLE::NullPtr();
+ m_fCanBeValid = TRUE;
+
+ m_handleType = handleType;
+ m_size = sizeof(void*);
+} // CordbHandleValue::CordbHandleValue
+
+//-----------------------------------------------------------------------------
+// Assign internal handle to the given value, and update pertinent counters
+//
+// Arguments:
+// handle - non-null CLR ObjectHandle that this CordbHandleValue will represent
+//
+// Notes:
+// Call code:CordbHandleValue::ClearHandle to clear the handle value.
+void CordbHandleValue::AssignHandle(VMPTR_OBJECTHANDLE handle)
+{
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+ _ASSERTE(m_vmHandle.IsNull());
+
+ // Use code:CordbHandleValue::ClearHandle to clear the handle value.
+ _ASSERTE(!handle.IsNull());
+
+ m_vmHandle = handle;
+ GetProcess()->IncrementOutstandingHandles();
+}
+
+//-----------------------------------------------------------------------------
+// Clear the handle value
+//
+// Assumptions:
+// Caller only clears if not already cleared.
+//
+// Notes:
+// This is the inverse of code:CordbHandleValue::AssignHandle
+void CordbHandleValue::ClearHandle()
+{
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+ _ASSERTE(!m_vmHandle.IsNull());
+
+ m_vmHandle = VMPTR_OBJECTHANDLE::NullPtr();
+ GetProcess()->DecrementOutstandingHandles();
+}
+
+// initialize a new instance of CordbHandleValue
+// Arguments:
+// input: pHandle - non-null CLR ObjectHandle that this CordbHandleValue will represent
+// Return Value: S_OK on success or CORDBG_E_TARGET_INCONSISTENT, E_INVALIDARG, read process memory errors.
+HRESULT CordbHandleValue::Init(VMPTR_OBJECTHANDLE pHandle)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+ HRESULT hr = S_OK;
+
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ // If it is a strong handle, m_pHandle will not be NULL unless Dispose method is called.
+ // If it is a weak handle, m_pHandle can be NULL when Dispose is called.
+ AssignHandle(pHandle);
+ }
+
+ // This will init m_info.
+ IfFailRet(RefreshHandleValue());
+
+ // objRefBad is currently overloaded to mean that 1) the object ref is invalid, or 2) the object ref is NULL.
+ // NULL is clearly not a bad object reference, but in either case we have no more type data to work with,
+ // so don't attempt to assign more specific type information to the reference.
+ if (!m_info.objRefBad)
+ {
+ // We need to get the type info from the left side.
+ CordbType *newtype;
+
+ IfFailRet(CordbType::TypeDataToType(m_appdomain, &m_info.objTypeData, &newtype));
+
+ m_type.Assign(newtype);
+ }
+
+ return hr;
+} // CordbHandleValue::Init
+
+// destructor
+CordbHandleValue::~CordbHandleValue()
+{
+ DTOR_ENTRY(this);
+
+ _ASSERTE(IsNeutered());
+} // CordbHandleValue::~CordbHandleValue
+
+// Free left-side resources, mainly the GC handle keeping the object alive.
+void CordbHandleValue::NeuterLeftSideResources()
+{
+ Dispose();
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ Neuter();
+} // CordbHandleValue::NeuterLeftSideResources
+
+// Neuter
+// Notes:
+// CordbHandleValue may hold Left-Side resources via the GC handle.
+// By the time we neuter it, those resources must have been freed,
+// either explicitly by calling code:CordbHandleValue::Dispose, or
+// implicitly by the left-side process exiting.
+void CordbHandleValue::Neuter()
+{
+ // CordbHandleValue is on the AppDomainExit neuter list.
+
+ // We should have cleaned up our Left-side resource by now (m_vmHandle
+ // should be null). If AppDomain / Process has already exited, then the LS
+ // already cleaned them up for us, and so we don't worry about them.
+ bool fAppDomainIsAlive = (m_appdomain != NULL && !m_appdomain->IsNeutered());
+ if (fAppDomainIsAlive)
+ {
+ BOOL fTargetIsDead = !GetProcess()->IsSafeToSendEvents() || GetProcess()->m_exiting;
+ if (!fTargetIsDead)
+ {
+ _ASSERTE(m_vmHandle.IsNull());
+ }
+ }
+
+ CordbValue::Neuter();
+} // CordbHandleValue::Neuter
+
+// Helper: Refresh the handle value object.
+// Gets information about the object to which the handle points.
+// Arguments: none
+// Return Value: S_OK on success, CORDBG_E_HANDLE_HAS_BEEN_DISPOSED, CORDBG_E_BAD_REFERENCE_VALUE,
+// errors from read process memory.
+HRESULT CordbHandleValue::RefreshHandleValue()
+{
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess()); //
+ _ASSERTE(m_appdomain != NULL);
+ _ASSERTE(!m_appdomain->IsNeutered());
+
+ // If Dispose has been called, don't bother to refresh handle value.
+ if (m_vmHandle.IsNull())
+ {
+ return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED;
+ }
+
+ // If weak handle and the object was dead, no point to refresh the handle value
+ if (m_fCanBeValid == FALSE)
+ {
+ return CORDBG_E_BAD_REFERENCE_VALUE;
+ }
+
+ HRESULT hr = S_OK;
+ CorElementType type = m_type->m_elementType;
+
+ _ASSERTE((m_pProcess != NULL));
+
+ _ASSERTE (type != ELEMENT_TYPE_GENERICINST);
+ _ASSERTE (type != ELEMENT_TYPE_VAR);
+ _ASSERTE (type != ELEMENT_TYPE_MVAR);
+
+ CordbProcess * pProcess = GetProcess();
+ void * objectAddress = NULL;
+ CORDB_ADDRESS objectHandle = 0;
+
+ EX_TRY
+ {
+ objectHandle = pProcess->GetDAC()->GetHandleAddressFromVmHandle(m_vmHandle);
+ if (type != ELEMENT_TYPE_TYPEDBYREF)
+ {
+ pProcess->SafeReadBuffer(TargetBuffer(objectHandle, sizeof(void *)), (BYTE *)&objectAddress);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+ EX_TRY
+ {
+ if (type == ELEMENT_TYPE_TYPEDBYREF)
+ {
+ CordbReferenceValue::GetTypedByRefData(pProcess,
+ objectHandle,
+ type,
+ m_appdomain->GetADToken(),
+ &m_info);
+ }
+ else
+ {
+ CordbReferenceValue::GetObjectData(pProcess,
+ objectAddress,
+ type,
+ m_appdomain->GetADToken(),
+ &m_info);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+
+ // If reference is already gone bad or reference is NULL,
+ // don't bother to refetch in the future.
+ //
+ if ((m_info.objRefBad) || (m_info.objRef == NULL))
+ {
+ m_fCanBeValid = FALSE;
+ }
+
+ return hr;
+}
+ // CordbHandleValue::RefreshHandleValue
+
+HRESULT CordbHandleValue::QueryInterface(REFIID id, void **pInterface)
+{
+ VALIDATE_POINTER_TO_OBJECT(pInterface, void **);
+
+ if (id == IID_ICorDebugValue)
+ {
+ *pInterface = static_cast<ICorDebugValue*>(this);
+ }
+ else if (id == IID_ICorDebugValue2)
+ {
+ *pInterface = static_cast<ICorDebugValue2*>(this);
+ }
+ else if (id == IID_ICorDebugValue3)
+ {
+ *pInterface = static_cast<ICorDebugValue3*>(this);
+ }
+ else if (id == IID_ICorDebugReferenceValue)
+ {
+ *pInterface = static_cast<ICorDebugReferenceValue*>(this);
+ }
+ else if (id == IID_ICorDebugHandleValue)
+ {
+ *pInterface = static_cast<ICorDebugHandleValue*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugHandleValue*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+} // CordbHandleValue::QueryInterface
+
+
+// return handle type. Currently we have strong and weak.
+// Arguments:
+// output: pType - the handle type unless pType is null
+// Return Value: S_OK on success or E_INVALIDARG or CORDBG_E_HANDLE_HAS_BEEN_DISPOSED on failure
+HRESULT CordbHandleValue::GetHandleType(CorDebugHandleType *pType)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pType, CorDebugHandleType *);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ _ASSERTE(m_appdomain != NULL);
+ _ASSERTE(!m_appdomain->IsNeutered());
+
+ if (m_vmHandle.IsNull())
+ {
+ // handle has been disposed!
+ return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED;
+ }
+ *pType = m_handleType;
+ return S_OK;
+} // CordbHandleValue::GetHandleType
+
+// Dispose will cause handle to be recycled.
+// Arguments: none
+// Return Value: S_OK on success, CORDBG_E_HANDLE_HAS_BEEN_DISPOSED or errors from the
+// DB_IPCE_DISPOSE_HANDLE event
+
+// @dbgtodo Microsoft inspection: remove the dispose handle hresults when the IPC events are eliminated
+HRESULT CordbHandleValue::Dispose()
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ _ASSERTE(m_appdomain != NULL);
+ _ASSERTE(!m_appdomain->IsNeutered());
+
+ HRESULT hr = S_OK;
+ DebuggerIPCEvent event;
+ CordbProcess *process;
+
+ process = GetProcess();
+
+ // Process should still be alive because it would have neutered us if it became invalid.
+ _ASSERTE(process != NULL);
+
+ VMPTR_OBJECTHANDLE vmObjHandle = VMPTR_OBJECTHANDLE::NullPtr();
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ if (m_vmHandle.IsNull())
+ {
+ // handle has been disposed!
+ return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED;
+ }
+
+ vmObjHandle = m_vmHandle;
+ ClearHandle(); // set m_pHandle to null.
+
+ if (process->m_exiting)
+ {
+ // process is exiting. Don't do anything
+ return S_OK;
+ }
+ }
+
+ // recycle the handle to EE
+ process->InitIPCEvent(&event,
+ DB_IPCE_DISPOSE_HANDLE,
+ false,
+ m_appdomain->GetADToken());
+
+ event.DisposeHandle.vmObjectHandle = vmObjHandle;
+ if (m_handleType == HANDLE_STRONG)
+ {
+ event.DisposeHandle.fStrong = TRUE;
+ }
+ else
+ {
+ event.DisposeHandle.fStrong = FALSE;
+ }
+
+ // Note: one-way event here...
+ hr = process->SendIPCEvent(&event, sizeof(DebuggerIPCEvent));
+
+ hr = WORST_HR(hr, event.hr);
+
+ return hr;
+} // CordbHandleValue::Dispose
+
+// get the type of the object to which the handle points
+// Arguments:
+// output: pType - the object type on success
+// Return Value: S_OK on success, CORDBG_E_HANDLE_HAS_BEEN_DISPOSED, CORDBG_E_CLASS_NOT_LOADED or synchronization errors on
+// failure
+HRESULT CordbHandleValue::GetType(CorElementType *pType)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pType, CorElementType *);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ _ASSERTE(m_appdomain != NULL);
+ _ASSERTE(!m_appdomain->IsNeutered());
+
+ HRESULT hr = S_OK;
+
+ if (m_vmHandle.IsNull())
+ {
+ return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED;
+ }
+
+ bool isBoxedVCObject = false;
+ if ((m_type->m_pClass != NULL) && (m_type->m_elementType != ELEMENT_TYPE_STRING))
+ {
+ EX_TRY
+ {
+ isBoxedVCObject = m_type->m_pClass->IsValueClass();
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ return hr;
+ }
+
+ if (isBoxedVCObject)
+ {
+ // if we create the handle to a boxed value type, then the type is
+ // E_T_CLASS. m_type is the underlying value type. That is incorrect to
+ // return.
+ //
+ *pType = ELEMENT_TYPE_CLASS;
+ return S_OK;
+ }
+
+ return m_type->GetType(pType);
+} // CordbHandleValue::GetType
+
+// get the size of the handle-- this will always return the size of the handle itself (just pointer size), so
+// it's not particularly interesting.
+// Arguments:
+// output: pSize - the size of the handle (on success). This must be non-null. Memory management belongs
+// to the caller.
+// Return Value: S_OK on success, E_INVALIDARG (if pSize is null), or CORDBG_E_HANDLE_HAS_BEEN_DISPOSED on failure
+HRESULT CordbHandleValue::GetSize(ULONG32 *pSize)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pSize, ULONG32 *);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ _ASSERTE(m_appdomain != NULL);
+ _ASSERTE(!m_appdomain->IsNeutered());
+
+ if (m_vmHandle.IsNull())
+ {
+ return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED;
+ }
+
+ if (m_size > ULONG_MAX)
+ {
+ *pSize = ULONG_MAX;
+ return (COR_E_OVERFLOW);
+ }
+
+ //return the size of reference
+ *pSize = (ULONG)m_size;
+ return S_OK;
+} // CordbHandleValue::GetSize
+
+// get the size of the handle-- this will always return the size of the handle itself (just pointer size), so
+// it's not particularly interesting.
+// Arguments:
+// output: pSize - the size of the handle (on success). This must be non-null. Memory management belongs
+// to the caller.
+// Return Value: S_OK on success, E_INVALIDARG (if pSize is null), or CORDBG_E_HANDLE_HAS_BEEN_DISPOSED on failure
+HRESULT CordbHandleValue::GetSize64(ULONG64 *pSize)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pSize, ULONG64 *);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ _ASSERTE(m_appdomain != NULL);
+ _ASSERTE(!m_appdomain->IsNeutered());
+
+ if (m_vmHandle.IsNull())
+ {
+ return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED;
+ }
+
+ //return the size of reference
+ *pSize = m_size;
+ return S_OK;
+} // CordbHandleValue::GetSize
+
+// Get the target address of the handle
+// Arguments:
+// output: pAddress - handle address on success. This must be non-null and memory is managed by the caller
+// Return Value: S_OK on success or CORDBG_E_HANDLE_HAS_BEEN_DISPOSED or E_INVALIDARG on failure
+HRESULT CordbHandleValue::GetAddress(CORDB_ADDRESS *pAddress)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pAddress, CORDB_ADDRESS *);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ _ASSERTE(m_appdomain != NULL);
+ _ASSERTE(!m_appdomain->IsNeutered());
+
+ if (m_vmHandle.IsNull())
+ {
+ return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED;
+ }
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ *pAddress = GetProcess()->GetDAC()->GetHandleAddressFromVmHandle(m_vmHandle);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbHandleValue::GetAddress
+
+HRESULT CordbHandleValue::CreateBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint)
+{
+ return E_NOTIMPL;
+} // CreateBreakpoint
+
+// indicates whether a handle is null
+// Arguments:
+// output: pbNull - true iff the handle is null and pbNull is non-null.Memory is managed by the caller
+// Return Value: S_OK on success or CORDBG_E_HANDLE_HAS_BEEN_DISPOSED or E_INVALIDARG, CORDBG_E_BAD_REFERENCE_VALUE,
+// errors from read process memory.
+HRESULT CordbHandleValue::IsNull(BOOL *pbNull)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pbNull, BOOL *);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ _ASSERTE(m_appdomain != NULL);
+ _ASSERTE(!m_appdomain->IsNeutered());
+
+ HRESULT hr = S_OK;
+
+ *pbNull = FALSE;
+
+ if (m_vmHandle.IsNull())
+ {
+ return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED;
+ }
+
+
+ // Only return true if handle is long weak handle and is disposed.
+ if (m_handleType == HANDLE_WEAK_TRACK_RESURRECTION)
+ {
+ hr = RefreshHandleValue();
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ if (m_info.objRef == NULL)
+ {
+ *pbNull = TRUE;
+ }
+ }
+ else if (m_info.objRef == NULL)
+ {
+ *pbNull = TRUE;
+ }
+
+ // strong handle always return false for IsNull
+
+ return S_OK;
+} // CordbHandleValue::IsNull
+
+// gets a copy of the value of the handle
+// Arguments:
+// output: pValue - handle { on success. This must be non-null and memory is managed by the caller
+// Return Value: S_OK on success or CORDBG_E_HANDLE_HAS_BEEN_DISPOSED or E_INVALIDARG, CORDBG_E_BAD_REFERENCE_VALUE,
+// errors from read process memory.
+HRESULT CordbHandleValue::GetValue(CORDB_ADDRESS *pValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pValue, CORDB_ADDRESS *);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ _ASSERTE(m_appdomain != NULL);
+ _ASSERTE(!m_appdomain->IsNeutered());
+
+ if (m_vmHandle.IsNull())
+ {
+ return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED;
+ }
+
+ RefreshHandleValue();
+ *pValue = PTR_TO_CORDB_ADDRESS(m_info.objRef);
+ return S_OK;
+} // CordbHandleValue::GetValue
+
+HRESULT CordbHandleValue::SetValue(CORDB_ADDRESS value)
+{
+ // do not support SetValue on Handle
+ return E_FAIL;
+} // CordbHandleValue::GetValue
+
+// get an ICDValue to represent the object to which the handle refers
+// Arguments:
+// output: ppValue - pointer to the ICDValue for the handle referent as long as ppValue is non-null
+// Return Value: S_OK on success or CORDBG_E_HANDLE_HAS_BEEN_DISPOSED or E_INVALIDARG, CORDBG_E_BAD_REFERENCE_VALUE,
+// errors from read process memory.
+HRESULT CordbHandleValue::Dereference(ICorDebugValue **ppValue)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ _ASSERTE(m_appdomain != NULL);
+ _ASSERTE(!m_appdomain->IsNeutered());
+
+ *ppValue = NULL;
+
+ if (m_vmHandle.IsNull())
+ {
+ return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED;
+ }
+
+ hr = RefreshHandleValue();
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ if ((m_info.objRefBad) || (m_info.objRef == NULL))
+ {
+ return CORDBG_E_BAD_REFERENCE_VALUE;
+ }
+
+ EX_TRY
+ {
+ hr = CordbReferenceValue::DereferenceCommon(m_appdomain,
+ m_type,
+ NULL, // don't support typed-by-refs
+ &m_info,
+ ppValue);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbHandleValue::Dereference
+
+HRESULT CordbHandleValue::DereferenceStrong(ICorDebugValue **ppValue)
+{
+ return E_NOTIMPL;
+}
+
+// CordbHeapValue3Impl::GetThreadOwningMonitorLock
+// If a managed thread owns the monitor lock on this object then *ppThread
+// will point to that thread and S_OK will be returned. The thread object is valid
+// until the thread exits. *pAcquisitionCount will indicate the number of times
+// this thread would need to release the lock before it returns to being
+// unowned.
+// If no managed thread owns the monitor lock on this object then *ppThread
+// and pAcquisitionCount will be unchanged and S_FALSE returned.
+// If ppThread or pAcquisitionCount is not a valid pointer the result is
+// undefined.
+// If any error occurs such that it cannot be determined which, if any, thread
+// owns the monitor lock on this object then a failing HRESULT will be returned
+HRESULT CordbHeapValue3Impl::GetThreadOwningMonitorLock(CordbProcess* pProcess,
+ CORDB_ADDRESS remoteObjAddress,
+ ICorDebugThread **ppThread,
+ DWORD *pAcquisitionCount)
+{
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ IDacDbiInterface *pDac = pProcess->GetDAC();
+ VMPTR_Object vmObj = pDac->GetObject(remoteObjAddress);
+ MonitorLockInfo info = pDac->GetThreadOwningMonitorLock(vmObj);
+ if(info.acquisitionCount == 0)
+ {
+ // unowned
+ *ppThread = NULL;
+ *pAcquisitionCount = 0;
+ hr = S_FALSE;
+ }
+ else
+ {
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ CordbThread* pThread = pProcess->LookupOrCreateThread(info.lockOwner);
+ pThread->QueryInterface(__uuidof(ICorDebugThread), (VOID**) ppThread);
+ *pAcquisitionCount = info.acquisitionCount;
+ hr = S_OK;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+// A small helper for CordbHeapValue3Impl::GetMonitorEventWaitList that adds each enumerated thread to an array
+// Arguments:
+// vmThread - The thread to add
+// puserData - the array to add it to
+VOID ThreadEnumerationCallback(VMPTR_Thread vmThread, VOID* pUserData)
+{
+ CQuickArrayList<VMPTR_Thread>* pThreadList = (CQuickArrayList<VMPTR_Thread>*) pUserData;
+ pThreadList->Push(vmThread);
+}
+
+// CordbHeapValue3Impl::GetMonitorEventWaitList
+// Provides an ordered list of threads which are queued on the event associated
+// with a monitor lock. The first thread in the list is the first thread which
+// will be released by the next call to Monitor.Pulse, the next thread in the list
+// will be released on the following call, and so on.
+// If this list is non-empty S_OK will be returned, if it is empty S_FALSE
+// will be returned (the enumeration is still valid, just empty).
+// In either case the enumeration interface is only usable for the duration
+// of the current synchronized state, however the threads interfaces dispensed
+// from it are valid until the thread exits.
+// If ppThread is not a valid pointer the result is undefined.
+// If any error occurs such that it cannot be determined which, if any, threads
+// are waiting for the monitor then a failing HRESULT will be returned
+HRESULT CordbHeapValue3Impl::GetMonitorEventWaitList(CordbProcess* pProcess,
+ CORDB_ADDRESS remoteObjAddress,
+ ICorDebugThreadEnum **ppThreadEnum)
+{
+ HRESULT hr = S_OK;
+ RSSmartPtr<CordbThread> *rsThreads = NULL;
+ EX_TRY
+ {
+ IDacDbiInterface *pDac = pProcess->GetDAC();
+ VMPTR_Object vmObj = pDac->GetObject(remoteObjAddress);
+ CQuickArrayList<VMPTR_Thread> threads;
+ pDac->EnumerateMonitorEventWaitList(vmObj,
+ (IDacDbiInterface::FP_THREAD_ENUMERATION_CALLBACK)ThreadEnumerationCallback, (VOID*)&threads);
+
+ rsThreads = new RSSmartPtr<CordbThread>[threads.Size()];
+ {
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ for(DWORD i = 0; i < threads.Size(); i++)
+ {
+ rsThreads[i].Assign(pProcess->LookupOrCreateThread(threads[i]));
+ }
+ }
+
+ CordbThreadEnumerator* threadEnum =
+ new CordbThreadEnumerator(pProcess, rsThreads, (DWORD)threads.Size());
+ pProcess->GetContinueNeuterList()->Add(pProcess, threadEnum);
+ threadEnum->QueryInterface(__uuidof(ICorDebugThreadEnum), (VOID**)ppThreadEnum);
+ if(threads.Size() == 0)
+ {
+ hr = S_FALSE;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ delete [] rsThreads;
+ return hr;
+}
diff --git a/src/debug/di/eventchannel.h b/src/debug/di/eventchannel.h
new file mode 100644
index 0000000000..5a4ff23ea8
--- /dev/null
+++ b/src/debug/di/eventchannel.h
@@ -0,0 +1,264 @@
+// 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.
+//*****************************************************************************
+// EventChannel.h
+//
+
+//
+// This file contains the old-style event channel interface.
+//*****************************************************************************
+
+
+#ifndef _EVENT_CHANNEL_H_
+#define _EVENT_CHANNEL_H_
+
+//---------------------------------------------------------------------------------------
+//
+// This is the abstract base class for the old-style "IPC" event channel. (Despite the name, these events are
+// no longer transmitted in an IPC shared memory block.) The event channel owns the DebuggerIPCControlBlock.
+//
+// Assumptions:
+// This class is NOT thread-safe. Caller is assumed to have taken the appropriate measures for
+// synchronization.
+//
+// Notes:
+// In Whidbey, both LS-to-RS and RS-to-LS communication are done by IPC shared memory block. We allocate
+// a DebuggerIPCControlBlock (DCB) on the IPC shared memory block. The DCB contains both a send buffer
+// and a receive buffer (from the perspective of the LS, e.g. the send buffer is for LS-to-RS communication).
+//
+// In the new architecture, LS-to-RS communication is mostly done by raising an exception on the LS and
+// calling code:INativeEventPipeline::WaitForDebugEvent on the RS. This communication is handled by
+// code:INativeEventPipeline. RS-to-LS communication is mostly done by calling into the code:IDacDbiInterface,
+// which on Windows is just a structured way to do ReadProcessMemory().
+//
+// There are still cases where we are sending IPC events in not-yet-DACized code. There are two main
+// categories:
+//
+// 1) There are three types of events which the RS can send to the LS:
+// a) asynchronous: the RS can just send the event and continue
+// b) synchronous, but no reply: the RS must wait for an acknowledgement, but there is no reply
+// c) synchronous, reply required: the RS must wait for an acknowledgement before it can get the reply
+//
+// For (c), the RS sends a synchronous IPC event to the LS and wait for a reply. The reply is returned
+// in the same buffer space used to send the event, i.e. in the receive buffer.
+// - RS: code:CordbRCEventThread::SendIPCEvent
+// - LS: code:DebuggerRCThread::SendIPCReply
+//
+// 2) In the case where the information from the LS has a variable size (and so we are not sure if it will
+// fit in one event), the RS sends an asynchronous IPC event to the LS and wait for one or more
+// events from the LS. The events from the LS are actually sent using the native pipeline. This is
+// somewhat tricky because we need to make sure the event from the native pipeline is passed along to
+// the thread which is waiting for the IPC events from the LS. (For more information, see how we use
+// code:CordbProcess::m_leftSideEventAvailable and code:CordbProcess::m_leftSideEventRead). Currently,
+// the only place where we use send IPC events this way is in the inspection code used to check the
+// results from the DAC against the results from the IPC events.
+// - RS: code:Cordb::WaitForIPCEventFromProcess
+// - LS: code:DebuggerRCThread::SendIPCEvent
+//
+// In a sense, you can think of the LS and the RS sharing 3 channels: one for debug events (see
+// code:INativeEventPipeline), one for DDI calls (see code:IDacDbiInterface),
+// and one for "IPC" events. This is the interface for the "IPC" events.
+//
+
+class IEventChannel
+{
+public:
+
+ //
+ // Inititalize the event channel.
+ //
+ // Arguments:
+ // hTargetProc - the handle of the debuggee process
+ //
+ // Return Value:
+ // S_OK if successful
+ //
+ // Notes:
+ // For Mac debugging, the handle is not necessary.
+ //
+
+ virtual HRESULT Init(HANDLE hTargetProc) = 0;
+
+ //
+ // Called when the debugger is detaching. Depending on the implementation, this may be necessary to
+ // make sure the debuggee state is reset in case another debugger attaches to it.
+ //
+ // Notes:
+ // This is currently a nop on for Mac debugging.
+ //
+
+ virtual void Detach() = 0;
+
+ //
+ // Delete the event channel and clean up all the resources it owns. This function can only be called once.
+ //
+
+ virtual void Delete() = 0;
+
+ //
+ // Update a single field with a value stored in the RS copy of the DCB. We can't update the entire LS DCB
+ // because in some cases, the LS and RS are simultaneously initializing the DCB. If we initialize a field on
+ // the RS and write back the whole thing, we may overwrite something the LS has initialized in the interim.
+ //
+ // Arguments:
+ // rsFieldAddr - the address of the field in the RS copy of the DCB that we want to write back to
+ // the LS DCB. We use this to compute the offset of the field from the beginning of the
+ // DCB and then add this offset to the starting address of the LS DCB to get the LS
+ // address of the field we are updating
+ // size - the size of the field we're updating.
+ //
+ // Return Value:
+ // S_OK if successful, otherwise whatever failure HR returned by the actual write operation
+ //
+
+ virtual HRESULT UpdateLeftSideDCBField(void * rsFieldAddr, SIZE_T size) = 0;
+
+ //
+ // Update the entire RS copy of the debugger control block by reading the LS copy. The RS copy is treated as
+ // a throw-away temporary buffer, rather than a true cache. That is, we make no assumptions about the
+ // validity of the information over time. Thus, before using any of the values, we need to update it. We
+ // update everything for simplicity; any perf hit we take by doing this instead of updating the individual
+ // fields we want at any given point isn't significant, particularly if we are updating multiple fields.
+ //
+ // Return Value:
+ // S_OK if successful, otherwise whatever failure HR returned by the actual read operation
+ //
+
+ virtual HRESULT UpdateRightSideDCB() = 0;
+
+ //
+ // Get the pointer to the RS DCB. The LS copy isn't updated until UpdateLeftSideDCBField() is called.
+ // Note that the DCB is owned by the event channel.
+ //
+ // Return Value:
+ // Return a pointer to the RS DCB. The memory is owned by the event channel.
+ //
+
+ virtual DebuggerIPCControlBlock * GetDCB() = 0;
+
+ //
+ // Check whether we need to wait for an acknowledgement from the LS after sending an IPC event.
+ // If so, wait for GetRightSideEventAckHandle().
+ //
+ // Arguments:
+ // pEvent - the IPC event which has just been sent to the LS
+ //
+ // Return Value:
+ // TRUE if an acknowledgement is required (see the comment for this class for more information)
+ //
+
+ virtual BOOL NeedToWaitForAck(DebuggerIPCEvent * pEvent) = 0;
+
+ //
+ // Get a handle to wait on after sending an IPC event to the LS. The caller should call NeedToWaitForAck()
+ // first to see if it is necessary to wait for an acknowledgement.
+ //
+ // Return Value:
+ // a handle to a Win32 event which will be signaled when the LS acknowledges the receipt of the IPC event
+ //
+ // Assumptions:
+ // NeedToWaitForAck() returns true after sending an IPC event to the LS
+ //
+
+ virtual HANDLE GetRightSideEventAckHandle() = 0;
+
+ //
+ // After sending an event to the LS and determining that we need to wait for the LS's acknowledgement,
+ // if any failure occurs, the LS may not have reset the Win32 event which is signaled when an event is
+ // available on the RS (i.e. what's called the Right-Side-Event-Available (RSEA) event). This function
+ // should be called if any failure occurs to make sure our state is consistent.
+ //
+
+ virtual void ClearEventForLeftSide() = 0;
+
+ //
+ // Send an IPC event to the LS. The caller should call NeedToWaitForAck() to check if it needs to wait
+ // for an acknowledgement, and wait on GetRightSideEventAckHandle() if necessary.
+ //
+ // Arguments:
+ // pEvent - the IPC event to be sent over to the LS
+ // eventSize - the size of the IPC event; cannot be bigger than CorDBIPC_BUFFER_SIZE
+ //
+ // Return Value:
+ // S_OK if successful
+ //
+ // Notes:
+ // This function returns a failure HR for recoverable errors. It throws on unrecoverable errors.
+ //
+
+ virtual HRESULT SendEventToLeftSide(DebuggerIPCEvent * pEvent, SIZE_T eventSize) = 0;
+
+ //
+ // Get the reply from the LS for a previously sent IPC event. The caller must have waited on
+ // GetRightSdieEventAckHandle().
+ //
+ // Arguments:
+ // pReplyEvent - buffer for the replyl event
+ // eventSize - size of the buffer
+ //
+ // Return Value:
+ // S_OK if successful
+ //
+
+ virtual HRESULT GetReplyFromLeftSide(DebuggerIPCEvent * pReplyEvent, SIZE_T eventSize) = 0;
+
+ //
+ // This function and GetEventFromLeftSide() are for the second category of IPC events described in the
+ // class header above, i.e. for events which take more than one IPC event to reply. The event actually
+ // doesn't come from the IPC channel. Instead, it comes from the native pipeline. We need to save the
+ // event from the native pipeline and then wake up the thread which is waiting for this event. Then the
+ // thread can call GetEventFromLeftSide() to receive this event.
+ //
+ // Arguments:
+ // pEventFromLeftSide - IPC event from the LS
+ //
+ // Return Value:
+ // S_OK if successful, E_FAIL if an event has already been saved
+ //
+ // Assumptions:
+ // At any given time there should only be one event saved. The caller is responsible for the
+ // synchronization.
+ //
+
+ virtual HRESULT SaveEventFromLeftSide(DebuggerIPCEvent * pEventFromLeftSide) = 0;
+
+ //
+ // See the function header for SaveEventFromLeftSide.
+ //
+ // Arguments:
+ // pLocalManagedEvent - buffer to be filled with the IPC event from the LS
+ //
+ // Return Value:
+ // S_OK if successful
+ //
+ // Assumptions:
+ // At any given time there should only be one event saved. The caller is responsible for the
+ // synchronization.
+ //
+
+ virtual HRESULT GetEventFromLeftSide(DebuggerIPCEvent * pLocalManagedEvent) = 0;
+};
+
+//-----------------------------------------------------------------------------
+//
+// Allocate and return an old-style event channel object for this target platform.
+//
+// Arguments:
+// pLeftSideDCB - target address of the DCB on the LS
+// pMutableDataTarget - data target for reading from and writing to the target process's address space
+// dwProcessId - used for Mac debugging; specifies the target process ID
+// machineInfo - used for Mac debugging; specifies the machine and the port number of the proxy
+// ppEventChannel - out parament; returns the newly created event channel
+//
+// Return Value:
+// S_OK if successful
+//
+
+HRESULT NewEventChannelForThisPlatform(CORDB_ADDRESS pLeftSideDCB,
+ ICorDebugMutableDataTarget * pMutableDataTarget,
+ DWORD dwProcessId,
+ MachineInfo machineInfo,
+ IEventChannel ** ppEventChannel);
+
+#endif // _EVENT_CHANNEL_H_
diff --git a/src/debug/di/eventredirectionpipeline.cpp b/src/debug/di/eventredirectionpipeline.cpp
new file mode 100644
index 0000000000..23405d643a
--- /dev/null
+++ b/src/debug/di/eventredirectionpipeline.cpp
@@ -0,0 +1,350 @@
+// 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.
+//*****************************************************************************
+// File: EventRedirectionPipeline.cpp
+//
+
+//
+// Implement a native pipeline that redirects events.
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "nativepipeline.h"
+#include "sstring.h"
+
+#if defined(ENABLE_EVENT_REDIRECTION_PIPELINE)
+#include "eventredirection.h"
+#include "eventredirectionpipeline.h"
+
+
+// Constructor
+EventRedirectionPipeline::EventRedirectionPipeline()
+{
+ m_pBlock = NULL;
+ m_dwProcessId = 0;
+
+ InitConfiguration();
+}
+
+// Dtor
+EventRedirectionPipeline::~EventRedirectionPipeline()
+{
+ CloseBlock();
+}
+
+// Call to free up the pipeline.
+void EventRedirectionPipeline::Delete()
+{
+ delete this;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Returns true if the Redirection is enabled.
+//
+// Arguments:
+// szOptions - specific Create/attach options to include in the overal format string
+// pidTarget - pid of real debuggeee.
+//
+// Return Value:
+// S_OK on success.
+//
+//
+// Notes:
+// This will spin up an auxillary debugger (windbg) and attach it to the existing
+// process. If this is a create case, then we're attaching to a create-suspended process.
+//
+//---------------------------------------------------------------------------------------
+void EventRedirectionPipeline::InitConfiguration()
+{
+ // We need some config strings. See header for possible values.
+ m_DebuggerCmd.Init_DontUse_(CLRConfig::EXTERNAL_DbgRedirectApplication);
+ m_AttachParams.Init_DontUse_(CLRConfig::EXTERNAL_DbgRedirectAttachCmd);
+ m_CreateParams.Init_DontUse_(CLRConfig::EXTERNAL_DbgRedirectCreateCmd);
+ m_CommonParams.Init_DontUse_(CLRConfig::EXTERNAL_DbgRedirectCommonCmd);
+}
+
+
+// Implement INativeEventPipeline::DebugSetProcessKillOnExit
+BOOL EventRedirectionPipeline::DebugSetProcessKillOnExit(bool fKillOnExit)
+{
+ // Not implemented for redirection pipeline. That's ok. Redirection pipeline doesn't care.
+ // Return success so caller can assert for other pipeline types.
+ return TRUE;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Attach a real debugger to the target.
+//
+// Arguments:
+// szOptions - specific Create/attach options to include in the overal format string
+// pidTarget - pid of real debuggeee.
+//
+// Return Value:
+// S_OK on success.
+//
+//
+// Notes:
+// This will spin up an auxillary debugger (windbg) and attach it to the existing
+// process. If this is a create case, then we're attaching to a create-suspended process.
+//
+//---------------------------------------------------------------------------------------
+HRESULT EventRedirectionPipeline::AttachDebuggerToTarget(LPCWSTR szOptions, DWORD pidTarget)
+{
+ SString s;
+
+ BOOL fRemap = false;
+
+ LPCWSTR lpApplicationName = NULL;
+ LPCWSTR lpCommandLine = NULL;
+
+ EX_TRY
+ {
+ m_pBlock = new (nothrow) RedirectionBlock(); // $$ make throwing
+
+ ZeroMemory(m_pBlock, sizeof(RedirectionBlock));
+
+ // Initialize
+ m_pBlock->m_versionCookie = EVENT_REDIRECTION_CURRENT_VERSION;
+
+ s.Printf(m_CommonParams.Value(), GetCurrentProcessId(), m_pBlock, szOptions, pidTarget);
+ lpCommandLine = s.GetUnicode();
+
+
+ lpApplicationName = m_DebuggerCmd.Value(); // eg, something like L"c:\\debuggers_amd64\\windbg.exe";
+
+ // Initialize events.
+ const BOOL kManualResetEvent = TRUE;
+ const BOOL kAutoResetEvent = FALSE;
+
+ m_pBlock->m_hEventAvailable = WszCreateEvent(NULL, kAutoResetEvent, FALSE, NULL);
+ m_pBlock->m_hEventConsumed = WszCreateEvent(NULL, kAutoResetEvent, FALSE, NULL);
+
+ m_pBlock->m_hDetachEvent = WszCreateEvent(NULL, kManualResetEvent, FALSE, NULL);
+
+ fRemap = true;
+ }EX_CATCH {}
+ EX_END_CATCH(RethrowTerminalExceptions);
+
+ if (!fRemap)
+ {
+ return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
+ }
+
+ STARTUPINFO startupInfo = {0};
+ startupInfo.cb = sizeof (STARTUPINFOW);
+
+ PROCESS_INFORMATION procInfo = {0};
+
+ // Now create the debugger
+ BOOL fStatus = WszCreateProcess(
+ lpApplicationName,
+ lpCommandLine,
+ NULL,
+ NULL,
+ FALSE,
+ 0, // flags
+ NULL,
+ NULL,
+ &startupInfo,
+ &procInfo);
+
+ if (!fStatus)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ CloseHandle(procInfo.hProcess);
+ CloseHandle(procInfo.hThread);
+
+ return S_OK;
+
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Close the event block
+//
+// Notes:
+// This can be called multiple times.
+//---------------------------------------------------------------------------------------
+void EventRedirectionPipeline::CloseBlock()
+{
+ if (m_pBlock == NULL)
+ {
+ return;
+ }
+
+ // Close our handle to the IPC events. When server closes its handles, OS will free the events.
+
+ // Setting the detach event signals the Server that this block is closing.
+ if (m_pBlock->m_hDetachEvent != NULL)
+ {
+ SetEvent(m_pBlock->m_hDetachEvent);
+ CloseHandle(m_pBlock->m_hDetachEvent);
+ }
+
+ if (m_pBlock->m_hEventAvailable != NULL)
+ {
+ CloseHandle(m_pBlock->m_hEventAvailable);
+ }
+
+ if (m_pBlock->m_hEventConsumed != NULL)
+ {
+ CloseHandle(m_pBlock->m_hEventConsumed);
+ }
+
+ delete m_pBlock;
+ m_pBlock = NULL;
+}
+
+
+// Wait for a debug event
+BOOL EventRedirectionPipeline::WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess)
+{
+ // Get debug event via Redirection from control block
+ DWORD res = WaitForSingleObject(m_pBlock->m_hEventAvailable, dwTimeout);
+ if (res == WAIT_TIMEOUT)
+ {
+ // No event is available.
+ return FALSE;
+ }
+
+
+ pEvent->dwDebugEventCode = EXCEPTION_DEBUG_EVENT;
+ pEvent->dwProcessId = m_pBlock->m_dwProcessId;
+ pEvent->dwThreadId = m_pBlock->m_dwThreadId;
+ pEvent->u.Exception.dwFirstChance = m_pBlock->m_dwFirstChance;
+
+ _ASSERTE(sizeof(m_pBlock->m_record) == sizeof(pEvent->u.Exception.ExceptionRecord));
+ memcpy(&pEvent->u.Exception.ExceptionRecord, &m_pBlock->m_record, sizeof(m_pBlock->m_record));
+
+ // We've got an event!
+ return TRUE;
+}
+
+// Continue a debug event
+BOOL EventRedirectionPipeline::ContinueDebugEvent(
+ DWORD dwProcessId,
+ DWORD dwThreadId,
+ DWORD dwContinueStatus
+)
+{
+ m_pBlock->m_ContinuationStatus = dwContinueStatus;
+ m_pBlock->m_counterConsumed++;
+
+ // Sanity check the block. If these checks fail, then the block is corrupted (perhaps a issue in the
+ // extension dll feeding us the events?).
+
+
+ _ASSERTE(dwProcessId == m_pBlock->m_dwProcessId);
+ _ASSERTE(dwThreadId == m_pBlock->m_dwThreadId);
+ _ASSERTE(m_pBlock->m_counterAvailable == m_pBlock->m_counterConsumed);
+
+ SetEvent(m_pBlock->m_hEventConsumed);
+
+ return TRUE;
+}
+
+// Create
+HRESULT EventRedirectionPipeline::CreateProcessUnderDebugger(
+ MachineInfo machineInfo,
+ LPCWSTR lpApplicationName,
+ LPCWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation)
+{
+ DWORD dwRealCreationFlags = dwCreationFlags;
+ dwRealCreationFlags |= CREATE_SUSPENDED;
+ dwRealCreationFlags &= ~(DEBUG_ONLY_THIS_PROCESS | DEBUG_PROCESS);
+
+ // We must create the real process so that startup info and process information are correct.
+ BOOL fStatus = WszCreateProcess(
+ lpApplicationName,
+ lpCommandLine,
+ lpProcessAttributes,
+ lpThreadAttributes,
+ bInheritHandles,
+ dwRealCreationFlags,
+ lpEnvironment,
+ lpCurrentDirectory,
+ lpStartupInfo,
+ lpProcessInformation);
+ if (!fStatus)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ // Attach the real debugger.
+ AttachDebuggerToTarget(m_CreateParams.Value(), lpProcessInformation->dwProcessId);
+
+ m_dwProcessId = lpProcessInformation->dwProcessId;
+
+ return S_OK;
+}
+
+
+// Attach
+HRESULT EventRedirectionPipeline::DebugActiveProcess(MachineInfo machineInfo, DWORD processId)
+{
+ m_dwProcessId = processId;
+
+ // Use redirected pipeline
+ // Spin up debugger to attach to target.
+ return AttachDebuggerToTarget(m_AttachParams.Value(), processId);
+}
+
+// Detach
+HRESULT EventRedirectionPipeline::DebugActiveProcessStop(DWORD processId)
+{
+ // Use redirected pipeline
+ SetEvent(m_pBlock->m_hDetachEvent);
+ CloseBlock();
+
+ // Assume detach can't fail (true on WinXP and above)
+ return S_OK;
+}
+
+// Return a handle for the debuggee process.
+HANDLE EventRedirectionPipeline::GetProcessHandle()
+{
+ _ASSERTE(m_dwProcessId != 0);
+
+ return ::OpenProcess(PROCESS_DUP_HANDLE |
+ PROCESS_QUERY_INFORMATION |
+ PROCESS_TERMINATE |
+ PROCESS_VM_OPERATION |
+ PROCESS_VM_READ |
+ PROCESS_VM_WRITE |
+ SYNCHRONIZE,
+ FALSE,
+ m_dwProcessId);
+}
+
+// Terminate the debuggee process.
+BOOL EventRedirectionPipeline::TerminateProcess(UINT32 exitCode)
+{
+ _ASSERTE(m_dwProcessId != 0);
+
+ // Get a process handle for the process ID.
+ HANDLE hProc = OpenProcess(PROCESS_TERMINATE, FALSE, m_dwProcessId);
+
+ if (hProc == NULL)
+ {
+ return FALSE;
+ }
+
+ return ::TerminateProcess(hProc, exitCode);
+}
+
+#endif // ENABLE_EVENT_REDIRECTION_PIPELINE
+
+
diff --git a/src/debug/di/eventredirectionpipeline.h b/src/debug/di/eventredirectionpipeline.h
new file mode 100644
index 0000000000..87549c1150
--- /dev/null
+++ b/src/debug/di/eventredirectionpipeline.h
@@ -0,0 +1,145 @@
+// 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.
+//*****************************************************************************
+// EventRedirectionPipeline.h
+//
+
+//
+// defines native pipeline abstraction for debug-support
+// for event redirection.
+//*****************************************************************************
+
+#ifndef _EVENTREDIRECTION_PIPELINE_
+#define _EVENTREDIRECTION_PIPELINE_
+
+#include "nativepipeline.h"
+
+struct RedirectionBlock;
+//-----------------------------------------------------------------------------
+// For debugging purposes, helper class to allow native debug events to get
+// redirected through StrikeRS debugger extension. Only 1 OS debugger can be
+// attached to a process. This allows a debugger (such as Windbg) to attach directly
+// to the Left-side (and thus be used to debug the left-side). ICorDebug then does a
+// "virtual attach" through this pipeline.
+//
+// If this is a raw native attach, all calls go right through to the native pipeline.
+//-----------------------------------------------------------------------------
+class EventRedirectionPipeline :
+ public INativeEventPipeline
+{
+public:
+ EventRedirectionPipeline();
+ ~EventRedirectionPipeline();
+
+ // Returns null if redirection is not enabled, else returns a new redirection pipeline.
+
+ //
+ // Implementation of INativeEventPipeline
+ //
+
+ // Call to free up the pipeline.
+ virtual void Delete();
+
+ // Mark what to do with outstanding debuggees when event thread is killed.
+ virtual BOOL DebugSetProcessKillOnExit(bool fKillOnExit);
+
+ // Create
+ virtual HRESULT CreateProcessUnderDebugger(
+ MachineInfo machineInfo,
+ LPCWSTR lpApplicationName,
+ LPCWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation);
+
+ // Attach
+ virtual HRESULT DebugActiveProcess(MachineInfo machineInfo, DWORD processId);
+
+ // Detach
+ virtual HRESULT DebugActiveProcessStop(DWORD processId);
+
+ // GEt a debug event
+ virtual BOOL WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess);
+
+ // Continue a debug event received from WaitForDebugEvent
+ virtual BOOL ContinueDebugEvent(
+ DWORD dwProcessId,
+ DWORD dwThreadId,
+ DWORD dwContinueStatus
+ );
+
+ // Return a handle for the debuggee process.
+ virtual HANDLE GetProcessHandle();
+
+ // Terminate the debuggee process.
+ virtual BOOL TerminateProcess(UINT32 exitCode);
+
+protected:
+
+ //
+ // Following us support for event-redirection.
+ //
+
+
+ // Rediretion block, or NULL if we're using the native pipeline.
+ RedirectionBlock * m_pBlock;
+
+ // Initialize configuration values.
+ void InitConfiguration();
+
+ HRESULT AttachDebuggerToTarget(LPCWSTR szOptions, DWORD pid);
+ void CloseBlock();
+
+ //
+ // Configuration information to launch the debugger.
+ // These are retrieved via the standard Config helpers.
+ //
+
+ // The debugger application to launch. eg:
+ // c:\debuggers_amd64\windbg.exe
+ ConfigStringHolder m_DebuggerCmd;
+
+ // The common format string for the command line.
+ // This will get the following printf args:
+ // int (%d or %x): this process's pid (the ICD Client)
+ // pointer (%p): the address of the control block (m_pBlock). The launched debugger will
+ // then use this to communicate with this process.
+ // extra format string (%s): args specific for either launch or attach
+ // target debuggee (%d or %x): pid of the debuggee.
+ // eg (for windbg):
+ // -c ".load C:\vbl\ClrDbg\ndp\clr\src\Tools\strikeRS\objc\amd64\strikeRS.dll; !watch %x %p" %s -p %d
+ ConfigStringHolder m_CommonParams;
+
+ // Command parameters for create case.
+ // Note that we must always physically call CreateProcess on the debuggee so that we get the proper out-parameters
+ // from create-processs (eg, target's handle, startup info, etc). So we always attach the auxillary debugger
+ // even in the create case. Use "-pr -pb" in Windbg to attach to a create-suspended process.
+ //
+ // Common Windbg options:
+ // -WX disable automatic workspace loading. This gaurantees the newly created windbg has a clean
+ // environment and is not tainted with settings that will break the extension dll.
+ // -pr option will tell real Debugger to resume main thread. This goes with the CREATE_SUSPENDED flag we passed to CreateProcess.
+ // -pb option is required when attaching to newly created suspended process. It tells the debugger
+ // to not create the break-in thread (which it can't do on a pre-initialized process).
+ // eg:
+ // "-WX -pb -pr"
+ ConfigStringHolder m_CreateParams;
+
+ // command parameters for attach. The WFDE server will send a loader breakpoint.
+ // eg:
+ // "-WX"
+ ConfigStringHolder m_AttachParams;
+
+ DWORD m_dwProcessId;
+};
+
+
+
+#endif // _EVENTREDIRECTION_PIPELINE_
+
diff --git a/src/debug/di/hash.cpp b/src/debug/di/hash.cpp
new file mode 100644
index 0000000000..554edaa308
--- /dev/null
+++ b/src/debug/di/hash.cpp
@@ -0,0 +1,638 @@
+// 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.
+//*****************************************************************************
+// File: hash.cpp
+//
+
+//
+//*****************************************************************************
+#include "stdafx.h"
+
+/* ------------------------------------------------------------------------- *
+ * Hash Table class
+ * ------------------------------------------------------------------------- */
+
+CordbHashTable::~CordbHashTable()
+{
+ HASHFIND find;
+
+ for (CordbHashEntry *entry = (CordbHashEntry *) FindFirstEntry(&find);
+ entry != NULL;
+ entry = (CordbHashEntry *) FindNextEntry(&find))
+ entry->pBase->InternalRelease();
+}
+
+HRESULT CordbHashTable::UnsafeAddBase(CordbBase *pBase)
+{
+ AssertIsProtected();
+ DbgIncChangeCount();
+
+ HRESULT hr = S_OK;
+
+ if (!m_initialized)
+ {
+ HRESULT res = NewInit(m_iBuckets, sizeof(CordbHashEntry), 0xffff);
+
+ if (res != S_OK)
+ {
+ return res;
+ }
+
+ m_initialized = true;
+ }
+
+ CordbHashEntry *entry = (CordbHashEntry *) Add(HASH((ULONG_PTR)pBase->m_id));
+
+ if (entry == NULL)
+ {
+ hr = E_FAIL;
+ }
+ else
+ {
+ entry->pBase = pBase;
+ m_count++;
+ pBase->InternalAddRef();
+ }
+ return hr;
+}
+bool CordbHashTable::IsInitialized()
+{
+ return m_initialized;
+}
+
+CordbBase *CordbHashTable::UnsafeGetBase(ULONG_PTR id, BOOL fFab)
+{
+ AssertIsProtected();
+
+ CordbHashEntry *entry = NULL;
+
+ if (!m_initialized)
+ return (NULL);
+
+ entry = (CordbHashEntry *) Find(HASH((ULONG_PTR)id), KEY(id));
+ return (entry ? entry->pBase : NULL);
+}
+
+HRESULT CordbHashTable::UnsafeSwapBase(CordbBase *pOldBase, CordbBase *pNewBase)
+{
+ if (!m_initialized)
+ return E_FAIL;
+
+ AssertIsProtected();
+ DbgIncChangeCount();
+
+ ULONG_PTR id = (ULONG_PTR)pOldBase->m_id;
+
+ CordbHashEntry *entry
+ = (CordbHashEntry *) Find(HASH((ULONG_PTR)id), KEY(id));
+
+ if (entry == NULL)
+ {
+ return E_FAIL;
+ }
+
+ _ASSERTE(entry->pBase == pOldBase);
+ entry->pBase = pNewBase;
+
+ // release the hash table's reference to the old base and transfer it
+ // to the new one.
+ pOldBase->InternalRelease();
+ pNewBase->InternalAddRef();
+
+ return S_OK;
+}
+
+
+CordbBase *CordbHashTable::UnsafeRemoveBase(ULONG_PTR id)
+{
+ AssertIsProtected();
+
+ if (!m_initialized)
+ return NULL;
+
+ DbgIncChangeCount();
+
+
+ CordbHashEntry *entry
+ = (CordbHashEntry *) Find(HASH((ULONG_PTR)id), KEY(id));
+
+ if (entry == NULL)
+ {
+ return NULL;
+ }
+
+ CordbBase *base = entry->pBase;
+
+ Delete(HASH((ULONG_PTR)id), (HASHENTRY *) entry);
+ m_count--;
+ base->InternalRelease();
+
+ return base;
+}
+
+CordbBase *CordbHashTable::UnsafeFindFirst(HASHFIND *find)
+{
+ AssertIsProtected();
+ return UnsafeUnlockedFindFirst(find);
+}
+
+CordbBase *CordbHashTable::UnsafeUnlockedFindFirst(HASHFIND *find)
+{
+ CordbHashEntry *entry = (CordbHashEntry *) FindFirstEntry(find);
+
+ if (entry == NULL)
+ return NULL;
+ else
+ return entry->pBase;
+}
+
+CordbBase *CordbHashTable::UnsafeFindNext(HASHFIND *find)
+{
+ AssertIsProtected();
+ return UnsafeUnlockedFindNext(find);
+}
+
+CordbBase *CordbHashTable::UnsafeUnlockedFindNext(HASHFIND *find)
+{
+ CordbHashEntry *entry = (CordbHashEntry *) FindNextEntry(find);
+
+ if (entry == NULL)
+ return NULL;
+ else
+ return entry->pBase;
+}
+
+/* ------------------------------------------------------------------------- *
+ * Hash Table Enumerator class
+ * ------------------------------------------------------------------------- */
+
+// This constructor is part of 2 phase construction.
+// Use the BuildOrThrow method to instantiate.
+CordbHashTableEnum::CordbHashTableEnum(
+ CordbBase * pOwnerObj, NeuterList * pOwnerList,
+ CordbHashTable *table,
+ REFIID guid
+)
+ : CordbBase(pOwnerObj->GetProcess(), 0, enumCordbHashTableEnum),
+ m_pOwnerObj(pOwnerObj),
+ m_pOwnerNeuterList(pOwnerList),
+ m_table(table),
+ m_started(false),
+ m_done(false),
+ m_guid(guid),
+ m_iCurElt(0),
+ m_count(0),
+ m_fCountInit(FALSE)
+{
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Build a new Hash enumerator or throw
+//
+// Arguments:
+// pOwnerObj - owner
+// pOwnerList - neuter list to add to
+// table - hash table to enumerate.
+// id - guid of objects to enumerate
+// pHolder - holder to get ownership.
+//
+void CordbHashTableEnum::BuildOrThrow(
+ CordbBase * pOwnerObj,
+ NeuterList * pOwnerList,
+ CordbHashTable *pTable,
+ const _GUID &id,
+ RSInitHolder<CordbHashTableEnum> * pHolder)
+{
+ CordbHashTableEnum * pEnum = new CordbHashTableEnum(pOwnerObj, pOwnerList, pTable, id);
+ pHolder->Assign(pEnum);
+
+ // If no neuter-list supplied, then our owner is manually managing us.
+ // It also means we can't be cloned.
+ if (pOwnerList != NULL)
+ {
+ pOwnerList->Add(pOwnerObj->GetProcess(), pEnum);
+ }
+
+#ifdef _DEBUG
+ pEnum->m_DbgChangeCount = pEnum->m_table->GetChangeCount();
+#endif
+}
+
+// Only for cloning.
+// Copy constructor makes life easy & fun!
+CordbHashTableEnum::CordbHashTableEnum(CordbHashTableEnum *cloneSrc)
+ : CordbBase(cloneSrc->m_pOwnerObj->GetProcess(), 0, enumCordbHashTableEnum),
+ m_pOwnerObj(cloneSrc->m_pOwnerObj),
+ m_pOwnerNeuterList(cloneSrc->m_pOwnerNeuterList),
+ m_started(cloneSrc->m_started),
+ m_done(cloneSrc->m_done),
+ m_hashfind(cloneSrc->m_hashfind),
+ m_guid(cloneSrc->m_guid),
+ m_iCurElt(cloneSrc->m_iCurElt),
+ m_count(cloneSrc->m_count),
+ m_fCountInit(cloneSrc->m_fCountInit)
+{
+ // We can get cloned at any time, so our owner can't manually control us,
+ // so we need explicit access to a neuter list.
+ _ASSERTE(m_pOwnerNeuterList != NULL);
+
+ m_table = cloneSrc->m_table;
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Add to neuter list
+ if (m_pOwnerObj->GetProcess() != NULL)
+ {
+ // Normal case. For things enumerating stuff within a CordbProcess tree.
+ m_pOwnerNeuterList->Add(m_pOwnerObj->GetProcess(), this);
+ }
+ else
+ {
+ // For Process-list enums that have broken neuter semantics.
+ // @dbgtodo: this goes away once we remove the top-level ICorDebug interface,
+ // and thus no longer have a Process enumerator.
+ m_pOwnerNeuterList->UnsafeAdd(NULL, this);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+
+#ifdef _DEBUG
+ m_DbgChangeCount = cloneSrc->m_DbgChangeCount;
+#endif
+}
+
+CordbHashTableEnum::~CordbHashTableEnum()
+{
+ _ASSERTE(this->IsNeutered());
+
+ _ASSERTE(m_table == NULL);
+ _ASSERTE(m_pOwnerObj == NULL);
+ _ASSERTE(m_pOwnerNeuterList == NULL);
+}
+
+void CordbHashTableEnum::Neuter()
+{
+ m_table = NULL;
+ m_pOwnerObj = NULL;
+ m_pOwnerNeuterList = NULL;
+
+ CordbBase::Neuter();
+}
+
+
+HRESULT CordbHashTableEnum::Reset()
+{
+ HRESULT hr = S_OK;
+
+ m_started = false;
+ m_done = false;
+
+ return hr;
+}
+
+HRESULT CordbHashTableEnum::Clone(ICorDebugEnum **ppEnum)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ AssertValid();
+
+ VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorDebugEnum **);
+
+ HRESULT hr;
+ hr = S_OK;
+
+ CordbHashTableEnum *e = NULL;
+
+ CordbProcess * pProc = GetProcess();
+
+ if (pProc != NULL)
+ {
+ // @todo - this is really ugly. This macro sets up stack-based dtor cleanup
+ // objects, and so it has to be in the same block of code as the code
+ // it protectes. Eg, we couldn't say: 'if (...) { ATT_ } { common code }'
+ // because the ATT_ stack based object would get destructed before the 'common code'
+ // was executed.
+ ATT_REQUIRE_STOPPED_MAY_FAIL(pProc);
+ e = new (nothrow) CordbHashTableEnum(this);
+ }
+ else
+ {
+ e = new (nothrow) CordbHashTableEnum(this);
+
+ }
+
+ if (e == NULL)
+ {
+ (*ppEnum) = NULL;
+ hr = E_OUTOFMEMORY;
+ goto LExit;
+ }
+
+ e->QueryInterface(m_guid, (void **) ppEnum);
+
+LExit:
+ return hr;
+}
+
+HRESULT CordbHashTableEnum::GetCount(ULONG *pcelt)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ AssertValid();
+
+ VALIDATE_POINTER_TO_OBJECT(pcelt, ULONG *);
+
+ *pcelt = m_table->GetCount();
+
+ return S_OK;
+}
+
+HRESULT CordbHashTableEnum::PrepForEnum(CordbBase **pBase)
+{
+ HRESULT hr = S_OK;
+
+ if (!m_started)
+ {
+ (*pBase) = m_table->UnsafeUnlockedFindFirst(&m_hashfind);
+ m_started = true;
+ }
+ else
+ {
+ (*pBase) = m_table->UnsafeUnlockedFindNext(&m_hashfind);
+ }
+
+ return hr;
+}
+
+HRESULT CordbHashTableEnum::SetupModuleEnum(void)
+{
+ return S_OK;
+}
+
+
+HRESULT CordbHashTableEnum::AdvancePreAssign(CordbBase **pBase)
+{
+ HRESULT hr = S_OK;
+ return hr;
+}
+
+HRESULT CordbHashTableEnum::AdvancePostAssign(CordbBase **pBase,
+ CordbBase **b,
+ CordbBase **bEnd)
+{
+ CordbBase *base;
+
+ if (pBase == NULL)
+ pBase = &base;
+
+ // If we're looping like normal, or we're in skip
+ if ( ((b < bEnd) || ((b ==bEnd)&&(b==NULL)))
+ )
+ {
+ (*pBase) = m_table->UnsafeUnlockedFindNext(&m_hashfind);
+ if (*pBase == NULL)
+ m_done = true;
+ }
+
+ return S_OK;
+}
+
+// This is an public function implementing all of the ICorDebugXXXEnum interfaces.
+HRESULT CordbHashTableEnum::Next(ULONG celt,
+ CordbBase *bases[],
+ ULONG *pceltFetched)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ AssertValid();
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(bases,
+ CordbBase *,
+ celt,
+ true,
+ true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched,
+ ULONG *);
+
+ if ((pceltFetched == NULL) && (celt != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ if (celt == 0)
+ {
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = 0;
+ }
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+ CordbBase *base = NULL;
+ CordbBase **b = bases;
+ CordbBase **bEnd = bases + celt;
+
+ hr = PrepForEnum(&base);
+ if (FAILED(hr))
+ {
+ goto LError;
+ }
+
+ while (b < bEnd && !m_done)
+ {
+ hr = AdvancePreAssign(&base);
+ if (FAILED(hr))
+ {
+ goto LError;
+ }
+
+ if (base == NULL)
+ {
+ m_done = true;
+ }
+ else
+ {
+ if (m_guid == IID_ICorDebugProcessEnum)
+ {
+ *b = (CordbBase*)(ICorDebugProcess*)(CordbProcess*)base;
+ }
+ else if (m_guid == IID_ICorDebugBreakpointEnum)
+ {
+ *b = (CordbBase*)(ICorDebugBreakpoint*)(CordbBreakpoint*)base;
+ }
+ else if (m_guid == IID_ICorDebugStepperEnum)
+ {
+ *b = (CordbBase*)(ICorDebugStepper*)(CordbStepper*)base;
+ }
+ else if (m_guid == IID_ICorDebugModuleEnum)
+ {
+ *b = (CordbBase*)(ICorDebugModule*)(CordbModule*)base;
+ }
+ else if (m_guid == IID_ICorDebugThreadEnum)
+ {
+ *b = (CordbBase*)(ICorDebugThread*)(CordbThread*)base;
+ }
+ else if (m_guid == IID_ICorDebugAppDomainEnum)
+ {
+ *b = (CordbBase*)(ICorDebugAppDomain*)(CordbAppDomain*)base;
+ }
+ else if (m_guid == IID_ICorDebugAssemblyEnum)
+ {
+ *b = (CordbBase*)(ICorDebugAssembly*)(CordbAssembly*)base;
+ }
+ else
+ {
+ *b = (CordbBase*)(IUnknown*)base;
+ }
+
+ if (*b)
+ {
+ // 'b' is not a valid CordbBase ptr.
+ base->ExternalAddRef();
+ b++;
+ }
+
+ hr = AdvancePostAssign(&base, b, bEnd);
+ if (FAILED(hr))
+ {
+ goto LError;
+ }
+ }
+ }
+
+LError:
+ //
+ // If celt == 1, then the pceltFetched parameter is optional.
+ //
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = (ULONG)(b - bases);
+ }
+
+ //
+ // If we reached the end of the enumeration, but not the end
+ // of the number of requested items, we return S_FALSE.
+ //
+ if (!FAILED(hr) && m_done && (b != bEnd))
+ {
+ hr = S_FALSE;
+ }
+
+ return hr;
+}
+
+HRESULT CordbHashTableEnum::Skip(ULONG celt)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ AssertValid();
+
+ HRESULT hr = S_OK;
+
+ CordbBase *base;
+
+ if (celt > 0)
+ {
+ if (!m_started)
+ {
+ base = m_table->UnsafeUnlockedFindFirst(&m_hashfind);
+
+ if (base == NULL)
+ m_done = true;
+ else
+ celt--;
+
+ m_started = true;
+ }
+
+ while (celt > 0 && !m_done)
+ {
+ base = m_table->UnsafeUnlockedFindNext(&m_hashfind);
+
+ if (base == NULL)
+ m_done = true;
+ else
+ celt--;
+ }
+ }
+
+ return hr;
+}
+
+HRESULT CordbHashTableEnum::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugEnum)
+ {
+ ExternalAddRef();
+ *pInterface = static_cast<ICorDebugEnum *>(static_cast<ICorDebugProcessEnum *>(this));
+
+ return S_OK;
+ }
+ if (id == IID_IUnknown)
+ {
+ ExternalAddRef();
+ *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugProcessEnum *>(this));
+
+ return S_OK;
+ }
+ if (id == m_guid)
+ {
+ ExternalAddRef();
+
+ if (id == IID_ICorDebugProcessEnum)
+ *pInterface = static_cast<ICorDebugProcessEnum *>(this);
+ else if (id == IID_ICorDebugBreakpointEnum)
+ *pInterface = static_cast<ICorDebugBreakpointEnum *>(this);
+ else if (id == IID_ICorDebugStepperEnum)
+ *pInterface = static_cast<ICorDebugStepperEnum *>(this);
+ else if (id == IID_ICorDebugModuleEnum)
+ *pInterface = static_cast<ICorDebugModuleEnum *>(this);
+ else if (id == IID_ICorDebugThreadEnum)
+ *pInterface = static_cast<ICorDebugThreadEnum *>(this);
+ else if (id == IID_ICorDebugAppDomainEnum)
+ *pInterface = static_cast<ICorDebugAppDomainEnum *>(this);
+ else if (id == IID_ICorDebugAssemblyEnum)
+ *pInterface = static_cast<ICorDebugAssemblyEnum *>(this);
+
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+#ifdef _DEBUG
+void CordbHashTableEnum::AssertValid()
+{
+ // @todo - Our behavior is undefined when enumerating a collection that changes underneath us.
+ // We'd love to just call this situatation illegal, but clients could very reasonably taken a depedency
+ // on it. Various APIs (eg, ICDStepper::Deactivate) may remove items from the hash.
+ // So we need to figure out what the behavior is here, spec it, and then enforce that and enable the
+ // strongest asserts possible there.
+ //
+ // Specifically, we cannot check that the hash hasn't change from underneath us:
+ // CONSISTENCY_CHECK_MSGF(m_DbgChangeCount == m_table->GetChangeCount(),
+ // ("Underlying hashtable has changed while enumerating.\nOriginal stamp=%d\nNew stamp=%d\nThis enum=0x%p",
+ // m_DbgChangeCount, m_table->GetChangeCount(), this));
+}
+
+
+//
+void CordbHashTable::AssertIsProtected()
+{
+#ifdef RSCONTRACTS
+ if (m_pDbgLock != NULL)
+ {
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+ if (pThread->IsInRS())
+ {
+ CONSISTENCY_CHECK_MSGF(m_pDbgLock->HasLock(), ("Hash table being accessed w/o holding '%s'", m_pDbgLock->Name()));
+ }
+ }
+#endif
+}
+#endif
diff --git a/src/debug/di/helpers.h b/src/debug/di/helpers.h
new file mode 100644
index 0000000000..3c6b73cdd7
--- /dev/null
+++ b/src/debug/di/helpers.h
@@ -0,0 +1,210 @@
+// 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.
+//*****************************************************************************
+// helpers.h
+//
+
+//
+// public helpers for debugger.
+//*****************************************************************************
+
+#ifndef _HELPERS_H
+#define _HELPERS_H
+
+//-----------------------------------------------------------------------------
+// Smartpointer for internal Addref/Release
+// Using Wrapper / Holder infrastructure from src\inc\Holder.h
+//-----------------------------------------------------------------------------
+template <typename TYPE>
+inline void HolderRSRelease(TYPE *value)
+{
+ _ASSERTE(value != NULL);
+ value->InternalRelease();
+}
+
+template <typename TYPE>
+inline void HolderRSAddRef(TYPE *value)
+{
+ _ASSERTE(value != NULL);
+ value->InternalAddRef();
+}
+
+// Smart ptrs for external refs. External refs are important
+// b/c they may keep an object alive.
+template <typename TYPE>
+inline void HolderRSReleaseExternal(TYPE *value)
+{
+ _ASSERTE(value != NULL);
+ value->Release();
+}
+
+template <typename TYPE>
+inline void HolderRSAddRefExternal(TYPE *value)
+{
+ _ASSERTE(value != NULL);
+ value->AddRef();
+}
+
+// The CordbBase::m_pProcess backpointer needs to adjust the external reference count, but manipulate it from
+// within the RS. This means we need to skip debugging checks that ensure
+// that the external count is only manipulated from outside the RS. Since we're
+// skipping these checks, we call this an "Unsafe" pointer.
+template <typename TYPE>
+inline void HolderRSUnsafeExtRelease(TYPE *value)
+{
+ _ASSERTE(value != NULL);
+ value->BaseRelease();
+}
+template <typename TYPE>
+inline void HolderRSUnsafeExtAddRef(TYPE *value)
+{
+ _ASSERTE(value != NULL);
+ value->BaseAddRef();
+}
+
+//-----------------------------------------------------------------------------
+// Base Smart pointer implementation.
+// This abstracts out the AddRef + Release methods.
+//-----------------------------------------------------------------------------
+template <typename TYPE, void (*ACQUIREF)(TYPE*), void (*RELEASEF)(TYPE*)>
+class BaseSmartPtr
+{
+public:
+ BaseSmartPtr () {
+ // Ensure that these smart-ptrs are really ptr-sized.
+ static_assert_no_msg(sizeof(*this) == sizeof(void*));
+ m_ptr = NULL;
+ }
+ explicit BaseSmartPtr (TYPE * ptr) : m_ptr(NULL) {
+ if (ptr != NULL)
+ {
+ RawAcquire(ptr);
+ }
+ }
+
+ ~BaseSmartPtr() {
+ Clear();
+ }
+
+ FORCEINLINE void Assign(TYPE * ptr)
+ {
+ // Do the AddRef before the release to avoid the release pinging 0 if we assign to ourself.
+ if (ptr != NULL)
+ {
+ ACQUIREF(ptr);
+ }
+ if (m_ptr != NULL)
+ {
+ RELEASEF(m_ptr);
+ }
+ m_ptr = ptr;
+ };
+
+ FORCEINLINE void Clear()
+ {
+ if (m_ptr != NULL)
+ {
+ RawRelease();
+ }
+ }
+
+ FORCEINLINE operator TYPE*() const
+ {
+ return m_ptr;
+ }
+
+ FORCEINLINE TYPE* GetValue() const
+ {
+ return m_ptr;
+ }
+
+ FORCEINLINE TYPE** operator & ()
+ {
+ // We allow getting the address so we can pass it in as an outparam.
+ // BTW/@TODO: this is a subtle and dangerous thing to do, since it easily leads to situations
+ // when pointer gets assigned without the ref counter being incremented.
+ // This can cause premature freeing of the object after the pointer dtor was called.
+
+ // But if we have a non-null m_Ptr, then it may get silently overwritten,
+ // and thus we'll lose the chance to call release on it.
+ // So we'll just avoid that pattern and assert to enforce it.
+ _ASSERTE(m_ptr == NULL);
+ return &m_ptr;
+ }
+
+ // For legacy purposes, some pre smart-pointer code needs to be able to get the
+ // address of the pointer. This is needed for RSPtrArray::GetAddrOfIndex.
+ FORCEINLINE TYPE** UnsafeGetAddr()
+ {
+ return &m_ptr;
+ }
+
+ FORCEINLINE TYPE* operator->()
+ {
+ return m_ptr;
+ }
+
+ FORCEINLINE int operator==(TYPE* p)
+ {
+ return (m_ptr == p);
+ }
+
+ FORCEINLINE int operator!= (TYPE* p)
+ {
+ return (m_ptr != p);
+ }
+
+private:
+ TYPE * m_ptr;
+
+ // Don't allow copy ctor. Explicitly don't define body to force linker errors if they're called.
+ BaseSmartPtr(BaseSmartPtr<TYPE,ACQUIREF,RELEASEF> & other);
+ void operator=(BaseSmartPtr<TYPE,ACQUIREF,RELEASEF> & other);
+
+ void RawAcquire(TYPE * p)
+ {
+ _ASSERTE(m_ptr == NULL);
+ m_ptr= p;
+ ACQUIREF(m_ptr);
+ }
+ void RawRelease()
+ {
+ _ASSERTE(m_ptr != NULL);
+ RELEASEF(m_ptr);
+ m_ptr = NULL;
+ }
+
+};
+
+//-----------------------------------------------------------------------------
+// Helper to make it easy to declare new SmartPtrs
+//-----------------------------------------------------------------------------
+#define DECLARE_MY_NEW_HOLDER(NAME, ADDREF, RELEASE) \
+template<typename TYPE> \
+class NAME : public BaseSmartPtr<TYPE, ADDREF, RELEASE> { \
+public: \
+ NAME() { }; \
+ NAME(NAME & other) { this->Assign(other.GetValue()); } \
+ explicit NAME(TYPE * p) : BaseSmartPtr<TYPE, ADDREF, RELEASE>(p) { }; \
+ FORCEINLINE NAME * GetAddr() { return this; } \
+ void operator=(NAME & other) { this->Assign(other.GetValue()); } \
+}; \
+
+//-----------------------------------------------------------------------------
+// Declare the various smart ptrs.
+//-----------------------------------------------------------------------------
+DECLARE_MY_NEW_HOLDER(RSSmartPtr, HolderRSAddRef, HolderRSRelease);
+DECLARE_MY_NEW_HOLDER(RSExtSmartPtr, HolderRSAddRefExternal, HolderRSReleaseExternal);
+
+// The CordbBase::m_pProcess backpointer needs to adjust the external reference count, but manipulate it from
+// within the RS. This means we need to skip debugging checks that ensure
+// that the external count is only manipulated from outside the RS. Since we're
+// skipping these checks, we call this an "Unsafe" pointer.
+// This is purely used by CordbBase::m_pProcess.
+DECLARE_MY_NEW_HOLDER(RSUnsafeExternalSmartPtr, HolderRSUnsafeExtAddRef, HolderRSUnsafeExtRelease);
+
+
+
+#endif // _HELPERS_H
+
diff --git a/src/debug/di/i386/.gitmirror b/src/debug/di/i386/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/di/i386/.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/debug/di/i386/cordbregisterset.cpp b/src/debug/di/i386/cordbregisterset.cpp
new file mode 100644
index 0000000000..3418e3ca60
--- /dev/null
+++ b/src/debug/di/i386/cordbregisterset.cpp
@@ -0,0 +1,222 @@
+// 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.
+//*****************************************************************************
+// File: CordbRegisterSet.cpp
+//
+
+//
+//*****************************************************************************
+#include "primitives.h"
+
+
+HRESULT CordbRegisterSet::GetRegistersAvailable(ULONG64 *pAvailable)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pAvailable, ULONG64 *);
+
+ (*pAvailable) = SETBITULONG64( REGISTER_INSTRUCTION_POINTER )
+ | SETBITULONG64( REGISTER_STACK_POINTER )
+ | SETBITULONG64( REGISTER_FRAME_POINTER );
+
+ if (!m_quickUnwind || m_active)
+ (*pAvailable) |= SETBITULONG64( REGISTER_X86_EAX )
+ | SETBITULONG64( REGISTER_X86_ECX )
+ | SETBITULONG64( REGISTER_X86_EDX )
+ | SETBITULONG64( REGISTER_X86_EBX )
+ | SETBITULONG64( REGISTER_X86_ESI )
+ | SETBITULONG64( REGISTER_X86_EDI );
+
+ if (m_active)
+ (*pAvailable) |= SETBITULONG64( REGISTER_X86_FPSTACK_0 )
+ | SETBITULONG64( REGISTER_X86_FPSTACK_1 )
+ | SETBITULONG64( REGISTER_X86_FPSTACK_2 )
+ | SETBITULONG64( REGISTER_X86_FPSTACK_3 )
+ | SETBITULONG64( REGISTER_X86_FPSTACK_4 )
+ | SETBITULONG64( REGISTER_X86_FPSTACK_5 )
+ | SETBITULONG64( REGISTER_X86_FPSTACK_6 )
+ | SETBITULONG64( REGISTER_X86_FPSTACK_7 );
+
+ return S_OK;
+}
+
+
+#define FPSTACK_FROM_INDEX( _index ) (m_thread->m_floatValues[m_thread->m_floatStackTop -( (REGISTER_X86_FPSTACK_##_index)-REGISTER_X86_FPSTACK_0 ) ] )
+
+HRESULT CordbRegisterSet::GetRegisters(ULONG64 mask, ULONG32 regCount,
+ CORDB_REGISTER regBuffer[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ UINT iRegister = 0;
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(regBuffer, CORDB_REGISTER, regCount, true, true);
+
+ //If we need some floating point value, tell the thread to get it
+ if ( mask & ( SETBITULONG64(REGISTER_X86_FPSTACK_0)
+ | SETBITULONG64(REGISTER_X86_FPSTACK_1)
+ | SETBITULONG64(REGISTER_X86_FPSTACK_2)
+ | SETBITULONG64(REGISTER_X86_FPSTACK_3)
+ | SETBITULONG64(REGISTER_X86_FPSTACK_4)
+ | SETBITULONG64(REGISTER_X86_FPSTACK_5)
+ | SETBITULONG64(REGISTER_X86_FPSTACK_6)
+ | SETBITULONG64(REGISTER_X86_FPSTACK_7 ) ) )
+ {
+ HRESULT hr = S_OK;
+
+ if (!m_active)
+ return E_INVALIDARG;
+
+ if (!m_thread->m_fFloatStateValid)
+ {
+ EX_TRY
+ {
+ m_thread->LoadFloatState();
+ }
+ EX_CATCH_HRESULT(hr);
+ if ( !SUCCEEDED(hr) )
+ {
+ return hr;
+ }
+ LOG( ( LF_CORDB, LL_INFO1000, "CRS::GR: Loaded float state\n" ) );
+ }
+ }
+
+ // Make sure that the registers are really available
+ if ( mask & ( SETBITULONG64( REGISTER_X86_EAX )
+ | SETBITULONG64( REGISTER_X86_ECX )
+ | SETBITULONG64( REGISTER_X86_EDX )
+ | SETBITULONG64( REGISTER_X86_EBX )
+ | SETBITULONG64( REGISTER_X86_ESI )
+ | SETBITULONG64( REGISTER_X86_EDI ) ) )
+ {
+ if (!m_active && m_quickUnwind)
+ return E_INVALIDARG;
+ }
+
+ for ( int i = REGISTER_INSTRUCTION_POINTER
+ ; i<=REGISTER_X86_FPSTACK_7 && iRegister < regCount
+ ; i++)
+ {
+ if( mask & SETBITULONG64(i) )
+ {
+ switch( i )
+ {
+ case REGISTER_INSTRUCTION_POINTER:
+ regBuffer[iRegister++] = m_rd->PC; break;
+ case REGISTER_STACK_POINTER:
+ regBuffer[iRegister++] = m_rd->SP; break;
+ case REGISTER_FRAME_POINTER:
+ regBuffer[iRegister++] = m_rd->FP; break;
+ case REGISTER_X86_EAX:
+ regBuffer[iRegister++] = m_rd->Eax; break;
+ case REGISTER_X86_EBX:
+ regBuffer[iRegister++] = m_rd->Ebx; break;
+ case REGISTER_X86_ECX:
+ regBuffer[iRegister++] = m_rd->Ecx; break;
+ case REGISTER_X86_EDX:
+ regBuffer[iRegister++] = m_rd->Edx; break;
+ case REGISTER_X86_ESI:
+ regBuffer[iRegister++] = m_rd->Esi; break;
+ case REGISTER_X86_EDI:
+ regBuffer[iRegister++] = m_rd->Edi; break;
+
+ //for floats, copy the bits, not the integer part of
+ //the value, into the register
+ case REGISTER_X86_FPSTACK_0:
+ memcpy(&regBuffer[iRegister++],
+ &(FPSTACK_FROM_INDEX(0)),
+ sizeof(CORDB_REGISTER));
+ break;
+ case REGISTER_X86_FPSTACK_1:
+ memcpy( &regBuffer[iRegister++],
+ & (FPSTACK_FROM_INDEX( 1 ) ),
+ sizeof(CORDB_REGISTER) );
+ break;
+ case REGISTER_X86_FPSTACK_2:
+ memcpy( &regBuffer[iRegister++],
+ & (FPSTACK_FROM_INDEX( 2 ) ),
+ sizeof(CORDB_REGISTER) ); break;
+ case REGISTER_X86_FPSTACK_3:
+ memcpy( &regBuffer[iRegister++],
+ & (FPSTACK_FROM_INDEX( 3 ) ),
+ sizeof(CORDB_REGISTER) ); break;
+ case REGISTER_X86_FPSTACK_4:
+ memcpy( &regBuffer[iRegister++],
+ & (FPSTACK_FROM_INDEX( 4 ) ),
+ sizeof(CORDB_REGISTER) ); break;
+ case REGISTER_X86_FPSTACK_5:
+ memcpy( &regBuffer[iRegister++],
+ & (FPSTACK_FROM_INDEX( 5 ) ),
+ sizeof(CORDB_REGISTER) ); break;
+ case REGISTER_X86_FPSTACK_6:
+ memcpy( &regBuffer[iRegister++],
+ & (FPSTACK_FROM_INDEX( 6 ) ),
+ sizeof(CORDB_REGISTER) ); break;
+ case REGISTER_X86_FPSTACK_7:
+ memcpy( &regBuffer[iRegister++],
+ & (FPSTACK_FROM_INDEX( 7 ) ),
+ sizeof(CORDB_REGISTER) ); break;
+ }
+ }
+ }
+
+ _ASSERTE( iRegister <= regCount );
+ return S_OK;
+}
+
+
+HRESULT CordbRegisterSet::GetRegistersAvailable(ULONG32 regCount,
+ BYTE pAvailable[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pAvailable, CORDB_REGISTER, regCount, true, true);
+
+ // Defer to adapter for v1.0 interface
+ return GetRegistersAvailableAdapter(regCount, pAvailable);
+}
+
+
+HRESULT CordbRegisterSet::GetRegisters(ULONG32 maskCount, BYTE mask[],
+ ULONG32 regCount, CORDB_REGISTER regBuffer[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(regBuffer, CORDB_REGISTER, regCount, true, true);
+
+ // Defer to adapter for v1.0 interface
+ return GetRegistersAdapter(maskCount, mask, regCount, regBuffer);
+}
+
+
+// This is just a convenience function to convert a regdisplay into a Context.
+// Since a context has more info than a regdisplay, the conversion isn't perfect
+// and the context can't be fully accurate.
+void CordbRegisterSet::InternalCopyRDToContext(DT_CONTEXT * pInputContext)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+ _ASSERTE(pInputContext);
+
+ //now update the registers based on the current frame
+ if((pInputContext->ContextFlags & DT_CONTEXT_INTEGER)==DT_CONTEXT_INTEGER)
+ {
+ pInputContext->Eax = m_rd->Eax;
+ pInputContext->Ebx = m_rd->Ebx;
+ pInputContext->Ecx = m_rd->Ecx;
+ pInputContext->Edx = m_rd->Edx;
+ pInputContext->Esi = m_rd->Esi;
+ pInputContext->Edi = m_rd->Edi;
+ }
+
+
+ if((pInputContext->ContextFlags & DT_CONTEXT_CONTROL)==DT_CONTEXT_CONTROL)
+ {
+ pInputContext->Eip = m_rd->PC;
+ pInputContext->Esp = m_rd->SP;
+ pInputContext->Ebp = m_rd->FP;
+ }
+}
+
diff --git a/src/debug/di/i386/primitives.cpp b/src/debug/di/i386/primitives.cpp
new file mode 100644
index 0000000000..1e173327e3
--- /dev/null
+++ b/src/debug/di/i386/primitives.cpp
@@ -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.
+
+//
+
+
+#include "../../shared/i386/primitives.cpp"
+
+
diff --git a/src/debug/di/localeventchannel.cpp b/src/debug/di/localeventchannel.cpp
new file mode 100644
index 0000000000..f31c46f7bb
--- /dev/null
+++ b/src/debug/di/localeventchannel.cpp
@@ -0,0 +1,499 @@
+// 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.
+//*****************************************************************************
+// File: LocalEventChannel.cpp
+//
+
+//
+// Implements the old-style event channel between two processes on a local Windows machine.
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "eventchannel.h"
+
+
+//---------------------------------------------------------------------------------------
+//
+// This is the implementation of the event channel for the normal case, where both the debugger and the
+// debuggee are on the same Windows machine. See code:IEventChannel for more information.
+//
+
+class LocalEventChannel : public IEventChannel
+{
+public:
+ LocalEventChannel(CORDB_ADDRESS pLeftSideDCB,
+ DebuggerIPCControlBlock * pDCBBuffer,
+ ICorDebugMutableDataTarget * pMutableDataTarget);
+
+ // Inititalize the event channel.
+ virtual HRESULT Init(HANDLE hTargetProc);
+
+ // Called when the debugger is detaching.
+ virtual void Detach();
+
+ // Delete the event channel and clean up all the resources it owns. This function can only be called once.
+ virtual void Delete();
+
+
+
+ // Update a single field with a value stored in the RS copy of the DCB.
+ virtual HRESULT UpdateLeftSideDCBField(void *rsFieldAddr, SIZE_T size);
+
+ // Update the entire RS copy of the debugger control block by reading the LS copy.
+ virtual HRESULT UpdateRightSideDCB();
+
+ // Get the pointer to the RS DCB.
+ virtual DebuggerIPCControlBlock * GetDCB();
+
+
+
+ // Check whether we need to wait for an acknowledgement from the LS after sending an IPC event.
+ virtual BOOL NeedToWaitForAck(DebuggerIPCEvent * pEvent);
+
+ // Get a handle to wait on after sending an IPC event to the LS. The caller should call NeedToWaitForAck()
+ virtual HANDLE GetRightSideEventAckHandle();
+
+ // Clean up the state if the wait for an acknowledgement is unsuccessful.
+ virtual void ClearEventForLeftSide();
+
+
+
+ // Send an IPC event to the LS.
+ virtual HRESULT SendEventToLeftSide(DebuggerIPCEvent * pEvent, SIZE_T eventSize);
+
+ // Get the reply from the LS for a previously sent IPC event.
+ virtual HRESULT GetReplyFromLeftSide(DebuggerIPCEvent * pReplyEvent, SIZE_T eventSize);
+
+
+
+ // Save an IPC event from the LS.
+ // Used for transferring an IPC event from the native pipeline to the IPC event channel.
+ virtual HRESULT SaveEventFromLeftSide(DebuggerIPCEvent * pEventFromLeftSide);
+
+ // Get a saved IPC event from the LS.
+ // Used for transferring an IPC event from the native pipeline to the IPC event channel.
+ virtual HRESULT GetEventFromLeftSide(DebuggerIPCEvent * pLocalManagedEvent);
+
+private:
+ // Get a target buffer representing the area of the DebuggerIPCControlBlock on the helper thread that
+ // holds information received from the LS as the result of an IPC event.
+ TargetBuffer RemoteReceiveBuffer(SIZE_T size);
+
+ // Get a target buffer representing the area of the DebuggerIPCControlBlock on the helper thread that
+ // holds information sent to the LS with an IPC event.
+ TargetBuffer RemoteSendBuffer(SIZE_T size);
+
+ // write memory to the LS using the data target
+ HRESULT SafeWriteBuffer(TargetBuffer tb, const BYTE * pLocalBuffer);
+
+ // read memory from the LS using the data target
+ HRESULT SafeReadBuffer(TargetBuffer tb, BYTE * pLocalBuffer);
+
+ // duplicate a remote handle into the local process
+ HRESULT DuplicateHandleToLocalProcess(HANDLE * pLocalHandle, RemoteHANDLE * pRemoteHandle);
+
+ // target address of the DCB on the LS
+ CORDB_ADDRESS m_pLeftSideDCB;
+
+ // used to signal the to the LS that an event is available
+ HANDLE m_rightSideEventAvailable;
+
+ // used by the LS to signal that the event is read
+ HANDLE m_rightSideEventRead;
+
+ // handle of the debuggee process
+ HANDLE m_hTargetProc;
+
+ // local buffer for the DCB on the RS
+ DebuggerIPCControlBlock * m_pDCBBuffer;
+
+ // data target used for cross-process memory reads and writes
+ RSExtSmartPtr<ICorDebugMutableDataTarget> m_pMutableDataTarget;
+};
+
+// Allocate and return an old-style event channel object for this target platform.
+HRESULT NewEventChannelForThisPlatform(CORDB_ADDRESS pLeftSideDCB,
+ ICorDebugMutableDataTarget * pMutableDataTarget,
+ DWORD dwProcessId,
+ MachineInfo machineInfo,
+ IEventChannel ** ppEventChannel)
+{
+ _ASSERTE(ppEventChannel != NULL);
+
+ LocalEventChannel * pEventChannel = NULL;
+ DebuggerIPCControlBlock * pDCBBuffer = NULL;
+
+ pDCBBuffer = new (nothrow) DebuggerIPCControlBlock;
+ if (pDCBBuffer == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ pEventChannel = new (nothrow) LocalEventChannel(pLeftSideDCB, pDCBBuffer, pMutableDataTarget);
+ if (pEventChannel == NULL)
+ {
+ delete pDCBBuffer;
+ return E_OUTOFMEMORY;
+ }
+
+ *ppEventChannel = pEventChannel;
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+//
+// This is the constructor.
+//
+// Arguments:
+// pLeftSideDCB - target address of the DCB on the LS
+// pDCBBuffer - local buffer for storing the DCB on the RS; the memory is owned by this class
+// pMutableDataTarget - data target for reading from and writing to the target process's address space
+//
+
+LocalEventChannel::LocalEventChannel(CORDB_ADDRESS pLeftSideDCB,
+ DebuggerIPCControlBlock * pDCBBuffer,
+ ICorDebugMutableDataTarget * pMutableDataTarget)
+{
+ m_pLeftSideDCB = pLeftSideDCB;
+ m_pDCBBuffer = pDCBBuffer;
+
+ m_rightSideEventAvailable = NULL;
+ m_rightSideEventRead = NULL;
+
+ m_pMutableDataTarget.Assign(pMutableDataTarget);
+}
+
+// Inititalize the event channel.
+//
+// virtual
+HRESULT LocalEventChannel::Init(HANDLE hTargetProc)
+{
+ HRESULT hr = E_FAIL;
+
+ m_hTargetProc = hTargetProc;
+
+ // Duplicate the handle of the RS process (i.e. the debugger) to the LS process's address space.
+ BOOL fSuccess =
+ m_pDCBBuffer->m_rightSideProcessHandle.DuplicateToRemoteProcess(m_hTargetProc, GetCurrentProcess());
+ if (!fSuccess)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ IfFailRet(UpdateLeftSideDCBField(&(m_pDCBBuffer->m_rightSideProcessHandle),
+ sizeof(m_pDCBBuffer->m_rightSideProcessHandle)));
+
+ // Dup RSEA and RSER into this process if we don't already have them.
+ // On Launch, we don't have them yet, but on attach we do.
+ IfFailRet(DuplicateHandleToLocalProcess(&m_rightSideEventAvailable,
+ &m_pDCBBuffer->m_rightSideEventAvailable));
+ IfFailRet(DuplicateHandleToLocalProcess(&m_rightSideEventRead,
+ &m_pDCBBuffer->m_rightSideEventRead));
+
+ return S_OK;
+}
+
+// Called when the debugger is detaching.
+//
+// virtual
+void LocalEventChannel::Detach()
+{
+ // This averts a race condition wherein we'll detach, then reattach,
+ // and find these events in the still-signalled state.
+ if (m_rightSideEventAvailable != NULL)
+ {
+ ResetEvent(m_rightSideEventAvailable);
+ }
+ if (m_rightSideEventRead != NULL)
+ {
+ ResetEvent(m_rightSideEventRead);
+ }
+}
+
+// Delete the event channel and clean up all the resources it owns. This function can only be called once.
+//
+// virtual
+void LocalEventChannel::Delete()
+{
+ if (m_hTargetProc != NULL)
+ {
+ m_pDCBBuffer->m_rightSideProcessHandle.CloseInRemoteProcess(m_hTargetProc);
+ UpdateLeftSideDCBField(&(m_pDCBBuffer->m_rightSideProcessHandle), sizeof(m_pDCBBuffer->m_rightSideProcessHandle));
+ m_hTargetProc = NULL;
+ }
+
+ if (m_rightSideEventAvailable != NULL)
+ {
+ CloseHandle(m_rightSideEventAvailable);
+ m_rightSideEventAvailable = NULL;
+ }
+
+ if (m_rightSideEventRead!= NULL)
+ {
+ CloseHandle(m_rightSideEventRead);
+ m_rightSideEventRead = NULL;
+ }
+
+ if (m_pDCBBuffer != NULL)
+ {
+ delete m_pDCBBuffer;
+ m_pDCBBuffer = NULL;
+ }
+
+ if (m_pMutableDataTarget != NULL)
+ {
+ m_pMutableDataTarget.Clear();
+ }
+
+ delete this;
+}
+
+// Update a single field with a value stored in the RS copy of the DCB.
+//
+// virtual
+HRESULT LocalEventChannel::UpdateLeftSideDCBField(void * rsFieldAddr, SIZE_T size)
+{
+ _ASSERTE(m_pDCBBuffer != NULL);
+ _ASSERTE(m_pLeftSideDCB != NULL);
+
+ BYTE * pbRSFieldAddr = reinterpret_cast<BYTE *>(rsFieldAddr);
+ CORDB_ADDRESS lsFieldAddr = m_pLeftSideDCB + (pbRSFieldAddr - reinterpret_cast<BYTE *>(m_pDCBBuffer));
+ return SafeWriteBuffer(TargetBuffer(lsFieldAddr, (ULONG)size), const_cast<const BYTE *>(pbRSFieldAddr));
+}
+
+// Update the entire RS copy of the debugger control block by reading the LS copy.
+//
+// virtual
+HRESULT LocalEventChannel::UpdateRightSideDCB()
+{
+ _ASSERTE(m_pDCBBuffer != NULL);
+ _ASSERTE(m_pLeftSideDCB != NULL);
+
+ return SafeReadBuffer(TargetBuffer(m_pLeftSideDCB, sizeof(DebuggerIPCControlBlock)),
+ reinterpret_cast<BYTE *>(m_pDCBBuffer));
+}
+
+// Get the pointer to the RS DCB.
+//
+// virtual
+DebuggerIPCControlBlock * LocalEventChannel::GetDCB()
+{
+ return m_pDCBBuffer;
+}
+
+
+// Check whether we need to wait for an acknowledgement from the LS after sending an IPC event.
+//
+// virtual
+BOOL LocalEventChannel::NeedToWaitForAck(DebuggerIPCEvent * pEvent)
+{
+ // On Windows, we need to wait for acknowledgement for every synchronous event.
+ return !pEvent->asyncSend;
+}
+
+// Get a handle to wait on after sending an IPC event to the LS. The caller should call NeedToWaitForAck()
+//
+// virtual
+HANDLE LocalEventChannel::GetRightSideEventAckHandle()
+{
+ return m_rightSideEventRead;
+}
+
+// Clean up the state if the wait for an acknowledgement is unsuccessful.
+//
+// virtual
+void LocalEventChannel::ClearEventForLeftSide()
+{
+ ResetEvent(m_rightSideEventAvailable);
+}
+
+// Send an IPC event to the LS.
+//
+// virtual
+HRESULT LocalEventChannel::SendEventToLeftSide(DebuggerIPCEvent * pEvent, SIZE_T eventSize)
+{
+ _ASSERTE(eventSize <= CorDBIPC_BUFFER_SIZE);
+
+ HRESULT hr = E_FAIL;
+ BOOL fSuccess = FALSE;
+
+ // Copy the event into the shared memory segment.
+ hr = SafeWriteBuffer(RemoteReceiveBuffer(eventSize), reinterpret_cast<BYTE *>(pEvent));
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ // Do some safety-checks for sending an Async-Event.
+#if defined(_DEBUG)
+ {
+ // We can only send 1 event from RS-->LS at a time.
+ // For non-async events, this is obviously enforced. (since the events are blocking & serialized)
+ // If this is an AsyncSend, then our caller was responsible for making sure it
+ // was safe to send.
+ // There should be no other IPC event in the pipeline. This, both RSEA & RSER
+ // should be non-signaled. check that now.
+ // It's ok if these fail - we detect that below.
+ int res2 = ::WaitForSingleObject(m_rightSideEventAvailable, 0);
+ CONSISTENCY_CHECK_MSGF(res2 != WAIT_OBJECT_0, ("RSEA:%d", res2));
+
+ int res3 = ::WaitForSingleObject(m_rightSideEventRead, 0);
+ CONSISTENCY_CHECK_MSGF(res3 != WAIT_OBJECT_0, ("RSER:%d", res3));
+ }
+#endif // _DEBUG
+
+ // Tell the runtime controller there is an event ready.
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "Set RSEA\n");
+ fSuccess = SetEvent(m_rightSideEventAvailable);
+
+ if (!fSuccess)
+ {
+ ThrowHR(HRESULT_FROM_GetLastError());
+ }
+
+ return S_OK;
+}
+
+// Get the reply from the LS for a previously sent IPC event.
+//
+// virtual
+HRESULT LocalEventChannel::GetReplyFromLeftSide(DebuggerIPCEvent * pReplyEvent, SIZE_T eventSize)
+{
+ // Simply read the IPC event reply directly from the receive buffer on the LS.
+ return SafeReadBuffer(RemoteReceiveBuffer(eventSize), reinterpret_cast<BYTE *>(pReplyEvent));
+}
+
+// Save an IPC event from the LS.
+// Used for transferring an IPC event from the native pipeline to the IPC event channel.
+//
+// virtual
+HRESULT LocalEventChannel::SaveEventFromLeftSide(DebuggerIPCEvent * pEventFromLeftSide)
+{
+ // On Windows, when a thread raises a debug event through the native pipeline, the process is suspended.
+ // Thus, the LS IPC event will still be in the send buffer in the debuggee's address space.
+ // Since there is no chance the send buffer can be altered, we don't need to save the event.
+ // We can simply read from it.
+ return S_OK;
+}
+
+// Get a saved IPC event from the LS.
+// Used for transferring an IPC event from the native pipeline to the IPC event channel.
+//
+// virtual
+HRESULT LocalEventChannel::GetEventFromLeftSide(DebuggerIPCEvent * pLocalManagedEvent)
+{
+ // See code:LocalEventChannel::SaveEventFromLeftSide.
+ // Make sure we are reading form the send buffer, not the receive buffer.
+ return SafeReadBuffer(RemoteSendBuffer(CorDBIPC_BUFFER_SIZE), reinterpret_cast<BYTE *>(pLocalManagedEvent));
+}
+
+//-----------------------------------------------------------------------------
+//
+// Get a target buffer representing the area of the DebuggerIPCControlBlock on the helper thread that
+// holds information received from the LS as the result of an IPC event.
+//
+// Arguments:
+// size - size of the receive buffer
+//
+// Return Value:
+// a TargetBuffer representing the receive buffer on the LS
+//
+
+TargetBuffer LocalEventChannel::RemoteReceiveBuffer(SIZE_T size)
+{
+ return TargetBuffer(m_pLeftSideDCB + offsetof(DebuggerIPCControlBlock, m_receiveBuffer), (ULONG)size);
+}
+
+//-----------------------------------------------------------------------------
+//
+// Get a target buffer representing the area of the DebuggerIPCControlBlock on the helper thread that
+// holds information sent to the LS with an IPC event.
+//
+// Arguments:
+// size - size of the send buffer
+//
+// Return Value:
+// a TargetBuffer representing the send buffer on the LS
+//
+
+TargetBuffer LocalEventChannel::RemoteSendBuffer(SIZE_T size)
+{
+ return TargetBuffer(m_pLeftSideDCB + offsetof(DebuggerIPCControlBlock, m_sendBuffer), (ULONG)size);
+}
+
+//-----------------------------------------------------------------------------
+//
+// Write memory to the LS using the data target.
+//
+// Arguments:
+// tb - target address and size to be written to
+// pLocalBuffer - data to write
+//
+// Return Value:
+// S_OK if successful
+//
+
+HRESULT LocalEventChannel::SafeWriteBuffer(TargetBuffer tb, const BYTE * pLocalBuffer)
+{
+ return m_pMutableDataTarget->WriteVirtual(tb.pAddress, pLocalBuffer, tb.cbSize);
+}
+
+//-----------------------------------------------------------------------------
+//
+// Read memory from the LS using the data target.
+//
+// Arguments:
+// tb - target address and size to be read from
+// pLocalBuffer - buffer for storing the data read from the LS
+//
+// Return Value:
+// S_OK if the entire specified range is read successful
+//
+
+HRESULT LocalEventChannel::SafeReadBuffer(TargetBuffer tb, BYTE * pLocalBuffer)
+{
+ ULONG32 cbRead;
+ HRESULT hr = m_pMutableDataTarget->ReadVirtual(tb.pAddress, pLocalBuffer, tb.cbSize, &cbRead);
+ if (FAILED(hr))
+ {
+ return CORDBG_E_READVIRTUAL_FAILURE;
+ }
+
+ if (cbRead != tb.cbSize)
+ {
+ return HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY);
+ }
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+//
+// Duplicate a remote handle into the local process.
+//
+// Arguments:
+// pLocalHandle - out parameter; return the duplicated handle
+// pRemoteHandle - remote handle to be duplicated
+//
+// Return Value:
+// S_OK if successful
+//
+// Notes:
+// nop if pLocalHandle is already initialized
+//
+
+HRESULT LocalEventChannel::DuplicateHandleToLocalProcess(HANDLE * pLocalHandle, RemoteHANDLE * pRemoteHandle)
+{
+ // Dup RSEA and RSER into this process if we don't already have them.
+ // On Launch, we don't have them yet, but on attach we do.
+ if (*pLocalHandle == NULL)
+ {
+ BOOL fSuccess = pRemoteHandle->DuplicateToLocalProcess(m_hTargetProc, pLocalHandle);
+ if (!fSuccess)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+ }
+ return S_OK;
+}
diff --git a/src/debug/di/module.cpp b/src/debug/di/module.cpp
new file mode 100644
index 0000000000..6717e8575f
--- /dev/null
+++ b/src/debug/di/module.cpp
@@ -0,0 +1,4925 @@
+// 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.
+
+//*****************************************************************************
+// File: module.cpp
+//
+
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "winbase.h"
+
+#include "metadataexports.h"
+
+#include "winbase.h"
+#include "corpriv.h"
+#include "corsym.h"
+#include "ildbsymlib.h"
+
+#include "pedecoder.h"
+
+//---------------------------------------------------------------------------------------
+// Update an existing metadata importer with a buffer
+//
+// Arguments:
+// pUnk - IUnknoown of importer to update.
+// pData - local buffer containing new metadata
+// cbData - size of buffer in bytes.
+// dwReOpenFlags - metadata flags to pass for reopening.
+//
+// Returns:
+// S_OK on success. Else failure.
+//
+// Notes:
+// This will call code:MDReOpenMetaDataWithMemoryEx from the metadata engine.
+STDAPI ReOpenMetaDataWithMemoryEx(
+ void *pUnk,
+ LPCVOID pData,
+ ULONG cbData,
+ DWORD dwReOpenFlags)
+{
+ HRESULT hr = MDReOpenMetaDataWithMemoryEx(pUnk,pData, cbData, dwReOpenFlags);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Initialize a new CordbModule around a Module in the target.
+//
+// Arguments:
+// pProcess - process that this module lives in
+// vmDomainFile - CLR cookie for module.
+CordbModule::CordbModule(
+ CordbProcess * pProcess,
+ VMPTR_Module vmModule,
+ VMPTR_DomainFile vmDomainFile)
+: CordbBase(pProcess, vmDomainFile.IsNull() ? VmPtrToCookie(vmModule) : VmPtrToCookie(vmDomainFile), enumCordbModule),
+ m_pAssembly(0),
+ m_pAppDomain(0),
+ m_classes(11),
+ m_functions(101),
+ m_vmDomainFile(vmDomainFile),
+ m_vmModule(vmModule),
+ m_EnCCount(0),
+ m_isIlWinMD(Uninitialized),
+ m_fForceMetaDataSerialize(FALSE),
+ m_nativeCodeTable(101)
+{
+ _ASSERTE(pProcess->GetProcessLock()->HasLock());
+
+ _ASSERTE(!vmModule.IsNull());
+
+ m_nLoadEventContinueCounter = 0;
+#ifdef _DEBUG
+ m_classes.DebugSetRSLock(pProcess->GetProcessLock());
+ m_functions.DebugSetRSLock(pProcess->GetProcessLock());
+#endif
+
+ // Fill out properties via DAC.
+ ModuleInfo modInfo;
+ pProcess->GetDAC()->GetModuleData(vmModule, &modInfo); // throws
+
+ m_PEBuffer.Init(modInfo.pPEBaseAddress, modInfo.nPESize);
+
+ m_fDynamic = modInfo.fIsDynamic;
+ m_fInMemory = modInfo.fInMemory;
+ m_vmPEFile = modInfo.vmPEFile;
+
+ if (!vmDomainFile.IsNull())
+ {
+ DomainFileInfo dfInfo;
+
+ pProcess->GetDAC()->GetDomainFileData(vmDomainFile, &dfInfo); // throws
+
+ m_pAppDomain = pProcess->LookupOrCreateAppDomain(dfInfo.vmAppDomain);
+ m_pAssembly = m_pAppDomain->LookupOrCreateAssembly(dfInfo.vmDomainAssembly);
+ }
+ else
+ {
+ // Not yet implemented
+ m_pAppDomain = pProcess->GetSharedAppDomain();
+ m_pAssembly = m_pAppDomain->LookupOrCreateAssembly(modInfo.vmAssembly);
+ }
+#ifdef _DEBUG
+ m_nativeCodeTable.DebugSetRSLock(GetProcess()->GetProcessLock());
+#endif
+
+ // MetaData is initialized lazily (via code:CordbModule::GetMetaDataImporter).
+ // Getting the metadata may be very expensive (especially if we go through the metadata locator, which
+ // invokes back to the data-target), so don't do it until asked.
+ // m_pIMImport, m_pInternalMetaDataImport are smart pointers that already initialize to NULL.
+}
+
+
+#ifdef _DEBUG
+//---------------------------------------------------------------------------------------
+// Callback helper for code:CordbModule::DbgAssertModuleDeleted
+//
+// Arguments
+// vmDomainFile - domain file in the enumeration
+// pUserData - pointer to the CordbModule that we just got an exit event for.
+//
+void DbgAssertModuleDeletedCallback(VMPTR_DomainFile vmDomainFile, void * pUserData)
+{
+ CordbModule * pThis = reinterpret_cast<CordbModule *>(pUserData);
+ INTERNAL_DAC_CALLBACK(pThis->GetProcess());
+
+ if (!pThis->m_vmDomainFile.IsNull())
+ {
+ VMPTR_DomainFile vmDomainFileDeleted = pThis->m_vmDomainFile;
+
+ CONSISTENCY_CHECK_MSGF((vmDomainFileDeleted != vmDomainFile),
+ ("A Module Unload event was sent for a module, but it still shows up in the enumeration.\n vmDomainFileDeleted=%p\n",
+ VmPtrToCookie(vmDomainFileDeleted)));
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// Assert that a module is no longer discoverable via enumeration.
+//
+// Notes:
+// See code:IDacDbiInterface#Enumeration for rules that we're asserting.
+// This is a debug only method. It's conceptually similar to
+// code:CordbProcess::DbgAssertAppDomainDeleted.
+//
+void CordbModule::DbgAssertModuleDeleted()
+{
+ GetProcess()->GetDAC()->EnumerateModulesInAssembly(
+ m_pAssembly->GetDomainAssemblyPtr(),
+ DbgAssertModuleDeletedCallback,
+ this);
+}
+#endif // _DEBUG
+
+CordbModule::~CordbModule()
+{
+ // We should have been explicitly neutered before our internal ref went to 0.
+ _ASSERTE(IsNeutered());
+
+ _ASSERTE(m_pIMImport == NULL);
+}
+
+// Neutered by CordbAppDomain
+void CordbModule::Neuter()
+{
+ // m_pAppDomain, m_pAssembly assigned w/o AddRef()
+ m_classes.NeuterAndClear(GetProcess()->GetProcessLock());
+ m_functions.NeuterAndClear(GetProcess()->GetProcessLock());
+
+ m_nativeCodeTable.NeuterAndClear(GetProcess()->GetProcessLock());
+ m_pClass.Clear();
+
+ // This is very important because it also releases the metadata's potential file locks.
+ m_pInternalMetaDataImport.Clear();
+ m_pIMImport.Clear();
+
+ CordbBase::Neuter();
+}
+
+//
+// Creates an IStream based off the memory described by the TargetBuffer.
+//
+// Arguments:
+// pProcess - process that buffer is valid in.
+// buffer - memory range in target
+// ppStream - out parameter to receive the new stream. *ppStream == NULL on input.
+// caller owns the new object and must call Release.
+//
+// Returns:
+// Throws on error.
+// Common errors include if memory is missing in the target.
+//
+// Notes:
+// This will copy the memory over from the TargetBuffer, and then create a new IStream
+// object around it.
+//
+void GetStreamFromTargetBuffer(CordbProcess * pProcess, TargetBuffer buffer, IStream ** ppStream)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(ppStream != NULL);
+ _ASSERTE(*ppStream == NULL);
+
+ int cbSize = buffer.cbSize;
+ NewArrayHolder<BYTE> localBuffer(new BYTE[cbSize]);
+
+ pProcess->SafeReadBuffer(buffer, localBuffer);
+
+ HRESULT hr = E_FAIL;
+ hr = CInMemoryStream::CreateStreamOnMemoryCopy(localBuffer, cbSize, ppStream);
+ IfFailThrow(hr);
+ _ASSERTE(*ppStream != NULL);
+}
+
+//
+// Helper API to get in-memory symbols from the target into a host stream object.
+//
+// Arguments:
+// ppStream - out parameter to receive the new stream. *ppStream == NULL on input.
+// caller owns the new object and must call Release.
+//
+// Returns:
+// kSymbolFormatNone if no PDB stream is present. This is a common case for
+// file-based modules, and also for dynamic modules that just aren't tracking
+// debug information.
+// The format of the symbols stored into ppStream. This is common:
+// - Ref.Emit modules if the debuggee generated debug symbols,
+// - in-memory modules (such as Load(Byte[], Byte[])
+// - hosted modules.
+// Throws on error
+//
+IDacDbiInterface::SymbolFormat CordbModule::GetInMemorySymbolStream(IStream ** ppStream)
+{
+ // @dbgtodo : add a PUBLIC_REENTRANT_API_ENTRY_FOR_SHIM contract
+ // This function is mainly called internally in dbi, and also by the shim to emulate the
+ // UpdateModuleSymbols callback on attach.
+
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(ppStream != NULL);
+ _ASSERTE(*ppStream == NULL);
+ *ppStream = NULL;
+
+ TargetBuffer bufferPdb;
+ IDacDbiInterface::SymbolFormat symFormat;
+ GetProcess()->GetDAC()->GetSymbolsBuffer(m_vmModule, &bufferPdb, &symFormat);
+ if (bufferPdb.IsEmpty())
+ {
+ // No in-memory PDB. Common case.
+ _ASSERTE(symFormat == IDacDbiInterface::kSymbolFormatNone);
+ return IDacDbiInterface::kSymbolFormatNone;
+ }
+ else
+ {
+ _ASSERTE(symFormat != IDacDbiInterface::kSymbolFormatNone);
+ GetStreamFromTargetBuffer(GetProcess(), bufferPdb, ppStream);
+ return symFormat;
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// Accessor for PE file.
+//
+// Returns:
+// VMPTR_PEFile for this module. Should always be non-null
+//
+// Notes:
+// A main usage of this is to find the proper internal MetaData importer.
+// DACized code needs to map from PEFile --> IMDInternalImport.
+//
+VMPTR_PEFile CordbModule::GetPEFile()
+{
+ return m_vmPEFile;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Top-level getter for the public metadata importer for this module
+//
+// Returns:
+// metadata importer.
+// Never returns NULL. Will throw some hr (likely CORDBG_E_MISSING_METADATA) instead.
+//
+// Notes:
+// This will lazily create the metadata, possibly invoking back into the data-target.
+IMetaDataImport * CordbModule::GetMetaDataImporter()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+
+ // If we already have it, then we're done.
+ // This is critical to do at the top of this function to avoid potential recursion.
+ if (m_pIMImport != NULL)
+ {
+ return m_pIMImport;
+ }
+
+ // Lazily initialize
+
+
+ // Fetch metadata from target
+ LOG((LF_CORDB,LL_INFO1000, "CM::GMI Lazy init refreshing metadata\n"));
+
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ RefreshMetaData();
+ );
+
+ // If lookup failed from the Module & target memory, try the metadata locator interface
+ // from debugger, if we have one.
+ if (m_pIMImport == NULL)
+ {
+ bool isILMetaDataForNGENImage; // Not currently used for anything.
+
+ // The process's LookupMetaData will ping the debugger's ICorDebugMetaDataLocator iface.
+ CordbProcess * pProcess = GetProcess();
+ RSLockHolder processLockHolder(pProcess->GetProcessLock());
+ m_pInternalMetaDataImport.Clear();
+
+ // Do not call code:CordbProcess::LookupMetaData from this function. It will try to load
+ // through the CordbModule again which will end up back here, and on failure you'll fill the stack.
+ // Since we've already done everything possible from the Module anyhow, just call the
+ // stuff that talks to the debugger.
+ // Don't do anything with the ptr returned here, since it's really m_pInternalMetaDataImport.
+ pProcess->LookupMetaDataFromDebugger(m_vmPEFile, isILMetaDataForNGENImage, this);
+ }
+
+ // If we still can't get it, throw.
+ if (m_pIMImport == NULL)
+ {
+ ThrowHR(CORDBG_E_MISSING_METADATA);
+ }
+
+ return m_pIMImport;
+}
+
+// Refresh the metadata cache if a profiler added new rows.
+//
+// Arguments:
+// token - token that we want to ensure is in the metadata cache.
+//
+// Notes:
+// In profiler case, this may be referred to new rows and we may need to update the metadata
+// This only supports StandAloneSigs.
+//
+void CordbModule::UpdateMetaDataCacheIfNeeded(mdToken token)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO10000, "CM::UMCIN token=0x%x\n", token));
+
+ // If we aren't trying to keep parity with our legacy profiler metadata update behavior
+ // then we should avoid this temporary update mechanism entirely
+ if(GetProcess()->GetWriteableMetadataUpdateMode() != LegacyCompatPolicy)
+ {
+ return;
+ }
+
+ // the metadata in WinMD is currently static since there's no
+ // support for profilers or EnC so we can simply exit early.
+ if (IsWinMD())
+ {
+ LOG((LF_CORDB,LL_INFO10000, "CM::UMCIN token is in WinMD, exiting\n"));
+ return;
+ }
+
+ //
+ // 1) Check if in-range? Compare against tables, etc.
+ //
+ if(CheckIfTokenInMetaData(token))
+ {
+ LOG((LF_CORDB,LL_INFO10000, "CM::UMCIN token was present\n"));
+ return;
+ }
+
+ //
+ // 2) Copy over new MetaData. From now on we assume that the profiler is
+ // modifying module metadata and that we need to serialize in process
+ // at each refresh
+ //
+ LOG((LF_CORDB,LL_INFO10000, "CM::UMCIN token was not present, refreshing\n"));
+ m_fForceMetaDataSerialize = TRUE;
+ RefreshMetaData();
+
+ // If we are dump debugging, we may still not have it. Nothing to be done.
+}
+
+// Returns TRUE if the token is present, FALSE if not.
+BOOL CordbModule::CheckIfTokenInMetaData(mdToken token)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+ LOG((LF_CORDB,LL_INFO10000, "CM::CITIM token=0x%x\n", token));
+ _ASSERTE(TypeFromToken(token) == mdtSignature);
+ // we shouldn't be doing this on WinMD modules since they don't implement IID_IMetaDataTables
+ _ASSERTE(!IsWinMD());
+ RSExtSmartPtr<IMetaDataTables> pTable;
+
+ HRESULT hr = GetMetaDataImporter()->QueryInterface(IID_IMetaDataTables, (void**) &pTable);
+
+ _ASSERTE(SUCCEEDED(hr));
+ if (FAILED(hr))
+ {
+ ThrowHR(hr);
+ }
+
+ ULONG cbRowsAvailable; // number of rows in the table
+
+ hr = pTable->GetTableInfo(
+ mdtSignature >> 24, // [IN] Which table.
+ NULL, // [OUT] Size of a row, bytes.
+ &cbRowsAvailable, // [OUT] Number of rows.
+ NULL, // [OUT] Number of columns in each row.
+ NULL, // [OUT] Key column, or -1 if none.
+ NULL); // [OUT] Name of the table.
+
+ _ASSERTE(SUCCEEDED(hr));
+ if (FAILED(hr))
+ {
+ ThrowHR(hr);
+ }
+
+
+ // Rows start counting with number 1.
+ ULONG rowRequested = RidFromToken(token);
+ LOG((LF_CORDB,LL_INFO10000, "CM::UMCIN requested=0x%x available=0x%x\n", rowRequested, cbRowsAvailable));
+ return (rowRequested <= cbRowsAvailable);
+}
+
+// This helper class ensures the remote serailzied buffer gets deleted in the RefreshMetaData
+// function below
+class CleanupRemoteBuffer
+{
+public:
+ CordbProcess* pProcess;
+ CordbModule* pModule;
+ TargetBuffer bufferMetaData;
+ BOOL fDoCleanup;
+
+ CleanupRemoteBuffer() :
+ fDoCleanup(FALSE) { }
+
+ ~CleanupRemoteBuffer()
+ {
+ if(fDoCleanup)
+ {
+ //
+ // Send 2nd event to free buffer.
+ //
+ DebuggerIPCEvent event;
+ pProcess->InitIPCEvent(&event,
+ DB_IPCE_RESOLVE_UPDATE_METADATA_2,
+ true,
+ pModule->GetAppDomain()->GetADToken());
+
+ event.MetadataUpdateRequest.pMetadataStart = CORDB_ADDRESS_TO_PTR(bufferMetaData.pAddress);
+
+ // Note: two-way event here...
+ IfFailThrow(pProcess->SendIPCEvent(&event, sizeof(DebuggerIPCEvent)));
+ _ASSERTE(event.type == DB_IPCE_RESOLVE_UPDATE_METADATA_2_RESULT);
+ }
+ }
+
+};
+
+// Called to refetch metadata. This occurs when a dynamic module grows or the profiler
+// has edited the metadata
+void CordbModule::RefreshMetaData()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO1000, "CM::RM\n"));
+
+ // There are several different ways we can get the metadata
+ // 1) [Most common] Module is loaded into VM and never changed. The importer
+ // will be constructed refering to the file on disk. This is a significant
+ // working set win because the VM and debugger share the image. If there is
+ // an error reading by file we can fall back to case #2 for these modules
+ // 2) Most modules have a buffer in target memory that represents their
+ // metadata. We copy that data over the RS and construct an in-memory
+ // importer on top of it.
+ // 3) The only modules that don't have a suitable buffer (case #2) are those
+ // modified in memory via the profiling API (or ENC). A message can be sent from
+ // the debugger to the debuggee instructing it to allocate a buffer and
+ // serialize the metadata into it. Then we copy that data to the RS and
+ // construct an in-memory importer on top of it.
+ // We don't need to send this message in the ENC case because the debugger
+ // has the same changes applied as the debuggee.
+ // 4) Case #3 won't work when dump debugging because we can't send IPC events.
+ // Instead we can locate chunks of the metadata pointed to in the implementation
+ // details of a remote MDInternalRW object, marshal that memory over to the
+ // debugger process, and then put a metadata reader on top of it.
+ // In time this DAC'ized metadata could be used in almost any scenario,
+ // although its probably worth keeping the file mapping technique in case
+ // #1 around for its performance wins.
+
+ CordbProcess * pProcess = GetProcess();
+ TargetBuffer bufferMetaData;
+ CleanupRemoteBuffer cleanup; // this local has a destructor to do some finally work
+
+
+ // check for scenarios we might want to handle with case #4
+ if (GetProcess()->GetShim() == NULL &&
+ GetProcess()->GetWriteableMetadataUpdateMode() == AlwaysShowUpdates &&
+ !m_fDynamic)
+ {
+ //None of the above requirements are particularly hard to change in the future as needed...
+ // a) dump-debugging mode - If we do this on a process that can move forward we need a mechanism to determine
+ // when to refetch the metadata.
+ // b) AlwaysShowUpdates - this is purely a risk mitigation choice, there aren't any known back-compat issues
+ // using DAC'ized metadata. If you want back-compat with the in-proc debugging behavior
+ // you need to figure out how to ReOpen the same public MD interface with new data.
+ // c) !m_fDynamic - A risk mitigation choice. Initial testing suggests it would work fine.
+
+
+ // So far we've only got a reader for in-memory-writable metadata (MDInternalRW implementation)
+ // We could make a reader for MDInternalRO, but no need yet. This also ensures we don't encroach into common
+ // scenario where we can map a file on disk.
+ TADDR remoteMDInternalRWAddr = NULL;
+ GetProcess()->GetDAC()->GetPEFileMDInternalRW(m_vmPEFile, &remoteMDInternalRWAddr);
+ if (remoteMDInternalRWAddr != NULL)
+ {
+ // we should only be doing this once to initialize, we don't support reopen with this technique
+ _ASSERTE(m_pIMImport == NULL);
+ ULONG32 mdStructuresVersion;
+ HRESULT hr = GetProcess()->GetDAC()->GetMDStructuresVersion(&mdStructuresVersion);
+ IfFailThrow(hr);
+ ULONG32 mdStructuresDefines;
+ hr = GetProcess()->GetDAC()->GetDefinesBitField(&mdStructuresDefines);
+ IfFailThrow(hr);
+ IMetaDataDispenserCustom* pDispCustom = NULL;
+ hr = GetProcess()->GetDispenser()->QueryInterface(IID_IMetaDataDispenserCustom, (void**)&pDispCustom);
+ IfFailThrow(hr);
+ IMDCustomDataSource* pDataSource = NULL;
+ hr = CreateRemoteMDInternalRWSource(remoteMDInternalRWAddr, GetProcess()->GetDataTarget(), mdStructuresDefines, mdStructuresVersion, &pDataSource);
+ IfFailThrow(hr);
+ IMetaDataImport* pImport = NULL;
+ hr = pDispCustom->OpenScopeOnCustomDataSource(pDataSource, 0, IID_IMetaDataImport, (IUnknown**)&m_pIMImport);
+ IfFailThrow(hr);
+ UpdateInternalMetaData();
+ return;
+ }
+ }
+
+ if(!m_fForceMetaDataSerialize) // case 1 and 2
+ {
+ LOG((LF_CORDB,LL_INFO10000, "CM::RM !m_fForceMetaDataSerialize case\n"));
+ GetProcess()->GetDAC()->GetMetadata(m_vmModule, &bufferMetaData); // throws
+ }
+ else if (GetProcess()->GetShim() == NULL) // case 3 won't work on a dump so don't try
+ {
+ return;
+ }
+ else // case 3 on a live process
+ {
+ LOG((LF_CORDB,LL_INFO10000, "CM::RM m_fForceMetaDataSerialize case\n"));
+ //
+ // Send 1 event to get metadata. This allocates a buffer
+ //
+ DebuggerIPCEvent event;
+ pProcess->InitIPCEvent(&event,
+ DB_IPCE_RESOLVE_UPDATE_METADATA_1,
+ true,
+ GetAppDomain()->GetADToken());
+
+ event.MetadataUpdateRequest.vmModule = m_vmModule;
+
+ // Note: two-way event here...
+ IfFailThrow(pProcess->SendIPCEvent(&event, sizeof(DebuggerIPCEvent)));
+
+ _ASSERTE(event.type == DB_IPCE_RESOLVE_UPDATE_METADATA_1_RESULT);
+
+ //
+ // Update it on the RS
+ //
+ bufferMetaData.Init(PTR_TO_CORDB_ADDRESS(event.MetadataUpdateRequest.pMetadataStart), (ULONG) event.MetadataUpdateRequest.nMetadataSize);
+
+ // init the cleanup object to ensure the buffer gets destroyed later
+ cleanup.bufferMetaData = bufferMetaData;
+ cleanup.pProcess = pProcess;
+ cleanup.pModule = this;
+ cleanup.fDoCleanup = TRUE;
+ }
+
+ InitMetaData(bufferMetaData, IsFileMetaDataValid()); // throws
+}
+
+// Determines whether the on-disk metadata for this module is usable as the
+// current metadata
+BOOL CordbModule::IsFileMetaDataValid()
+{
+ bool fOpenFromFile = true;
+
+ // Dynamic, In-memory, modules must be OpenScopeOnMemory.
+ // For modules that require the metadata to be serialized in memory, we must also OpenScopeOnMemory
+ // For Enc, we'll can use OpenScope(onFile) and it will get converted to Memory when we get an emitter.
+ // We're called from before the ModuleLoad callback, so EnC status hasn't been set yet, so
+ // EnC will be false.
+ if (m_fDynamic || m_fInMemory || m_fForceMetaDataSerialize)
+ {
+ LOG((LF_CORDB,LL_INFO10000, "CM::IFMV: m_fDynamic=0x%x m_fInMemory=0x%x m_fForceMetaDataSerialize=0x%x\n",
+ m_fDynamic, m_fInMemory, m_fForceMetaDataSerialize));
+ fOpenFromFile = false;
+ }
+
+#ifdef _DEBUG
+ // Reg key override to force us to use Open-by-memory. This can let us run perf tests to
+ // compare the Open-by-mem vs. Open-by-file.
+ static DWORD openFromFile = 99;
+ if (openFromFile == 99)
+ openFromFile = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgNoOpenMDByFile);
+
+ if (openFromFile)
+ {
+ LOG((LF_CORDB,LL_INFO10000, "CM::IFMV: INTERNAL_DbgNoOpenMDByFile is set\n"));
+ fOpenFromFile = false;
+ }
+#endif
+
+ LOG((LF_CORDB,LL_INFO10000, "CM::IFMV: returns 0x%x\n", fOpenFromFile));
+ return fOpenFromFile;
+}
+
+//---------------------------------------------------------------------------------------
+// Accessor for Internal MetaData importer. This is lazily initialized.
+//
+// Returns:
+// Internal MetaDataImporter, which can be handed off to DAC. Not AddRef().
+// Should be non-null. Throws on error.
+//
+// Notes:
+// An internal metadata importer is used extensively by DAC-ized code (And Edit-and-continue).
+// This should not be handed out through ICorDebug.
+IMDInternalImport * CordbModule::GetInternalMD()
+{
+ if (m_pInternalMetaDataImport == NULL)
+ {
+ UpdateInternalMetaData(); // throws
+ }
+ return m_pInternalMetaDataImport;
+}
+
+//---------------------------------------------------------------------------------------
+// The one-stop top-level initialization function the metadata (both public and private) for this module.
+//
+// Arguments:
+// buffer - valid buffer into target containing the metadata.
+// useFileMappingOptimization - if true this allows us to attempt just opening the importer
+// by using the metadata in the module on disk. if false or
+// if the attempt fails we open the metadata import on memory in
+// target buffer
+//
+// Notes:
+// This will initialize both the internal and public metadata from the buffer in the target.
+// Only called as a helper from RefreshMetaData()
+//
+// This may throw (eg, target buffer is missing).
+//
+void CordbModule::InitMetaData(TargetBuffer buffer, BOOL allowFileMappingOptimization)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO100000, "CM::IM: initing with remote buffer 0x%p length 0x%x\n",
+ CORDB_ADDRESS_TO_PTR(buffer.pAddress), buffer.cbSize));
+
+ // clear all the metadata
+ m_pInternalMetaDataImport.Clear();
+
+ if (m_pIMImport == NULL)
+ {
+ // The optimization we're going for here is that the OS will use the same physical memory to
+ // back multiple ReadOnly opens of the same file. Thus since we expect the target process in
+ // live debugging, or the debugger in dump debugging, has already opened the file we would
+ // like to not create a local buffer and spend time copying in metadata from the target when
+ // the OS will happily do address lookup magic against the same physical memory for everyone.
+
+
+ // Try getting the data from the file if allowed, and fall back to using the buffer
+ // if required
+ HRESULT hr = S_OK;
+ if (allowFileMappingOptimization)
+ {
+ hr = InitPublicMetaDataFromFile();
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB,LL_INFO1000000, "CM::IPM: File mapping failed with hr=0x%x\n", hr));
+ }
+ }
+
+ if(!allowFileMappingOptimization || FAILED(hr))
+ {
+ // This is where the expensive copy of all metadata content from target memory
+ // that we would like to try and avoid happens.
+ InitPublicMetaData(buffer);
+ }
+ }
+ else
+ {
+ // We've already handed out an Import object, and so we can't create a new pointer instance.
+ // Instead, we update the existing instance with new data.
+ UpdatePublicMetaDataFromRemote(buffer);
+ }
+
+ // if we haven't set it by this point UpdateInternalMetaData below is going to get us
+ // in an infinite loop of refreshing public metadata
+ _ASSERTE(m_pIMImport != NULL);
+
+ // Now that public metadata has changed, force internal metadata to update too.
+ // Public and internal metadata expose different access interfaces to the same underlying storage.
+ UpdateInternalMetaData();
+}
+
+//---------------------------------------------------------------------------------------
+// Updates the Internal MetaData object from the public importer. Lazily fetch public importer if needed.
+//
+// Assumptions:
+// Caller has cleared Internal metadata before even updating public metadata.
+// This way, if the caller fails halfway through updating the public metadata, we don't have
+// stale internal MetaData.
+void CordbModule::UpdateInternalMetaData()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ // Caller should have already cleared it.
+ _ASSERTE(m_pInternalMetaDataImport == NULL);
+
+ // Get the importer. If it's currently null, this will go fetch it.
+ IMetaDataImport * pImport = GetMetaDataImporter(); // throws
+
+ // If both the public and the private interfaces are NULL on entry to this function, the call above will
+ // recursively call this function. This can happen if the caller calls GetInternalMD() directly
+ // instead of InitMetaData(). In this case, the above function call will have initialized the internal
+ // interface as well, so we need to check for it here.
+
+ if (m_pInternalMetaDataImport == NULL)
+ {
+ HRESULT hr = GetMDInternalInterfaceFromPublic(
+ pImport,
+ IID_IMDInternalImport,
+ reinterpret_cast<void**> (&m_pInternalMetaDataImport));
+
+ if (m_pInternalMetaDataImport == NULL)
+ {
+ ThrowHR(hr);
+ }
+ }
+
+ _ASSERTE(m_pInternalMetaDataImport != NULL);
+}
+
+// Initialize the public metadata.
+//
+// The debuggee already has a copy of the metadata in its process.
+// If we OpenScope on file as read-only, the OS file-system will share our metadata with the
+// copy in the debuggee. This can be a major perf win. FX metadata can be over 8 MB+.
+// OpenScopeOnMemory can't be shared b/c we allocate a buffer.
+HRESULT CordbModule::InitPublicMetaDataFromFile()
+{
+ INTERNAL_API_ENTRY(this->GetProcess());
+
+ // @dbgtodo metadata - In v3, we can't assume we have the same path namespace as the target (i.e. it could be
+ // a dump or remote), so we can't just try and open the file. Instead we have to rely on interfaces
+ // on the datatarget to map the metadata here. Note that this must also work for minidumps where the
+ // metadata isn't necessarily in the dump image.
+
+ // Get filename. There are 2 filenames to choose from:
+ // - ngen (if applicable).
+ // - non-ngen (aka "normal").
+ // By loading metadata out of the same OS file as loaded into the debuggee space, the OS can share those pages.
+ const WCHAR * szFullPathName = NULL;
+ bool fDebuggerLoadingNgen = false;
+ bool fDebuggeeLoadedNgen = false;
+ szFullPathName = GetNGenImagePath();
+
+ if(szFullPathName != NULL)
+ {
+ fDebuggeeLoadedNgen = true;
+ fDebuggerLoadingNgen = true;
+
+#ifndef FEATURE_PAL
+ // NGEN images are large and we shouldn't load them if they won't be shared, therefore fail the NGEN mapping and
+ // fallback to IL image if the debugger doesn't have the image loaded already.
+ // Its possible that the debugger would still load the NGEN image sometime in the future and we will miss a sharing
+ // opportunity. Its an acceptable loss from an imperfect heuristic.
+ if (NULL == WszGetModuleHandle(szFullPathName))
+#endif
+ {
+ szFullPathName = NULL;
+ fDebuggerLoadingNgen = false;
+ }
+
+ }
+
+ // If we don't have or decided not to load the NGEN image, check to see if IL image is available
+ if (!fDebuggerLoadingNgen)
+ {
+ szFullPathName = GetModulePath();
+ }
+
+ // If we are doing live debugging we shouldn't use metadata from an IL image because it doesn't match closely enough.
+ // In particular the RVAs for IL code headers are different between the two images which will cause all IL code and
+ // local var signature lookups to fail. With further work we could compensate for the RVAs by computing
+ // the image layout differences and adjusting the returned RVAs, but there may be other differences that need to be accounted
+ // for as well. If we did go that route we should do a binary diff across a variety of NGEN/IL image metadata blobs to
+ // get a concrete understanding of the format differences.
+ //
+ // This check should really be 'Are we OK with only getting the functionality level of mini-dump debugging?' but since we
+ // don't know the debugger's intent we guess whether or not we are doing dump debugging by checking if we are shimmed. Once
+ // the shim supports live debugging we should probably just stop automatically falling back to IL image and let the debugger
+ // decide via the ICorDebugMetadataLocator interface.
+ if(fDebuggeeLoadedNgen && !fDebuggerLoadingNgen && GetProcess()->GetShim()!=NULL)
+ {
+ // The IL image might be there, but we shouldn't use it for live debugging
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+
+ // @dbgtodo metadata - This is really a CreateFile() call which we can't do. We must offload this to
+ // the data target for the dump-debugging scenarios.
+ //
+ // We're opening it as "read". If we QI for an IEmit interface (which we need for EnC),
+ // then the metadata engine will convert it to a "write" underneath us.
+ // We want "read" so that we can let the OS share the pages.
+ DWORD dwOpenFlags = 0;
+
+ // If we know we're never going to need to write (i.e. never do EnC), then we should indicate
+ // that to metadata by telling it this interface will always be read-only. By passing read-only,
+ // the metadata library will then also share the VM space for the image when the same image is
+ // opened multiple times for multiple AppDomains.
+ // We don't currently have a way to tell absolutely whether this module will support EnC, but we
+ // know that NGen modules NEVER support EnC, and NGen is the common case that eats up a lot of VM.
+ // So we'll use the heuristic of opening the metadata for all ngen images as read-only. Ideally
+ // we'd go even further here (perhaps even changing metadata to map only the region of the file it
+ // needs).
+ if (fDebuggerLoadingNgen)
+ {
+ dwOpenFlags = ofReadOnly | ofTrustedImage;
+ }
+
+ // This is the only place we ever validate that the file matches, because we're potentially
+ // loading the file from disk ourselves. We're doing this without giving the debugger a chance
+ // to do anything. We should never load a file that isn't an exact match.
+ return InitPublicMetaDataFromFile(szFullPathName, dwOpenFlags, true);
+}
+
+// We should only ever validate we have the correct file if it's a file we found ourselves.
+// We allow the debugger to choose their own policy with regard to using metadata from the IL image
+// when debugging an NI, or even intentionally using mismatched metadata if they like.
+HRESULT CordbModule::InitPublicMetaDataFromFile(const WCHAR * pszFullPathName,
+ DWORD dwOpenFlags,
+ bool validateFileInfo)
+{
+#ifdef FEATURE_PAL
+ // UNIXTODO: Some intricate details of file mapping don't work on Linux as on Windows.
+ // We have to revisit this and try to fix it for POSIX system.
+ return E_FAIL;
+#else
+ if (validateFileInfo)
+ {
+ // Check that we've got the right file to target.
+ // There's nothing to prevent some other file being copied in for live, and with
+ // dump debugging there's nothing to say that we're not on another machine where a different
+ // file is at the same path.
+ // If we can't validate we have a hold of the correct file, we should not open it.
+ // We will fall back on asking the debugger to get us the correct file, or copying
+ // target memory back to the debugger.
+ DWORD dwImageTimeStamp = 0;
+ DWORD dwImageSize = 0;
+ bool isNGEN = false; // unused
+ StringCopyHolder filePath;
+
+
+ _ASSERTE(!m_vmPEFile.IsNull());
+ // MetaData lookup favors the NGEN image, which is what we want here.
+ if (!this->GetProcess()->GetDAC()->GetMetaDataFileInfoFromPEFile(m_vmPEFile,
+ dwImageTimeStamp,
+ dwImageSize,
+ isNGEN,
+ &filePath))
+ {
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: Couldn't get metadata info for file \"%s\"\n", pszFullPathName));
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+ // If the timestamp and size don't match, then this is the wrong file!
+ // Map the file and check them.
+ HandleHolder hMDFile = WszCreateFile(pszFullPathName,
+ GENERIC_READ,
+ FILE_SHARE_READ,
+ NULL, // default security descriptor
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+
+ if (hMDFile == INVALID_HANDLE_VALUE)
+ {
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: Couldn't open file \"%s\" (GLE=%x)\n", pszFullPathName, GetLastError()));
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+ DWORD dwFileHigh = 0;
+ DWORD dwFileLow = GetFileSize(hMDFile, &dwFileHigh);
+ if (dwFileLow == INVALID_FILE_SIZE)
+ {
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: File \"%s\" had invalid size.\n", pszFullPathName));
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+ _ASSERTE(dwFileHigh == 0);
+
+ HandleHolder hMap = WszCreateFileMapping(hMDFile, NULL, PAGE_READONLY, dwFileHigh, dwFileLow, NULL);
+ if (hMap == NULL)
+ {
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: Couldn't create mapping of file \"%s\" (GLE=%x)\n", pszFullPathName, GetLastError()));
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+ MapViewHolder hMapView = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
+ if (hMapView == NULL)
+ {
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: Couldn't map view of file \"%s\" (GLE=%x)\n", pszFullPathName, GetLastError()));
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+ // Mapped as flat file, have PEDecoder go find what we want.
+ PEDecoder pedecoder(hMapView, (COUNT_T)dwFileLow);
+
+ if (!pedecoder.HasNTHeaders())
+ {
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: \"%s\" did not have PE headers!\n", pszFullPathName));
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+ if ((dwImageSize != pedecoder.GetVirtualSize()) ||
+ (dwImageTimeStamp != pedecoder.GetTimeDateStamp()))
+ {
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: Validation of \"%s\" failed. "
+ "Expected size=%x, Expected timestamp=%x, Actual size=%x, Actual timestamp=%x\n",
+ pszFullPathName,
+ pedecoder.GetVirtualSize(),
+ pedecoder.GetTimeDateStamp(),
+ dwImageSize,
+ dwImageTimeStamp));
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+ // All checks passed, go ahead and load this file for real.
+ }
+
+ // Get metadata Dispenser.
+ IMetaDataDispenserEx * pDisp = GetProcess()->GetDispenser();
+
+ HRESULT hr = pDisp->OpenScope(pszFullPathName, dwOpenFlags, IID_IMetaDataImport, (IUnknown**)&m_pIMImport);
+ _ASSERTE(SUCCEEDED(hr) == (m_pIMImport != NULL));
+
+ if (FAILED(hr))
+ {
+ // This should never happen in normal scenarios. It could happen if someone has renamed
+ // the assembly after it was opened by the debugee process, but this should be rare enough
+ // that we don't mind taking the perf. hit and loading from memory.
+ // @dbgtodo metadata - would this happen in the shadow-copy scenario?
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: Couldn't open metadata in file \"%s\" (hr=%x)\n", pszFullPathName, hr));
+ }
+
+ return hr;
+#endif // FEATURE_PAL
+}
+
+//---------------------------------------------------------------------------------------
+// Initialize the public metadata.
+//
+// Arguments:
+// buffer - valid buffer into target containing the metadata.
+//
+// Assumptions:
+// This is an internal function which should only be called once to initialize the
+// metadata. Future attempts to re-initialize (in dynamic cases) should call code:CordbModule::UpdatePublicMetaDataFromRemote
+// After the public metadata is initialized, initialize private metadata via code:CordbModule::UpdateInternalMetaData
+//
+void CordbModule::InitPublicMetaData(TargetBuffer buffer)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ INTERNAL_API_ENTRY(this->GetProcess());
+ LOG((LF_CORDB,LL_INFO100000, "CM::IPM: initing with remote buffer 0x%p length 0x%x\n",
+ CORDB_ADDRESS_TO_PTR(buffer.pAddress), buffer.cbSize));
+ ULONG nMetaDataSize = buffer.cbSize;
+
+ if (nMetaDataSize == 0)
+ {
+ // We should always have metadata, and if we don't, we want to know.
+ // @dbgtodo metadata - we know metadata from dynamic modules doesn't work in V3
+ // (non-shim) cases yet.
+ // But our caller should already have handled that case.
+ SIMPLIFYING_ASSUMPTION(!"Error: missing the metadata");
+ return;
+ }
+
+ HRESULT hr = S_OK;
+
+ // Get metadata Dispenser.
+ IMetaDataDispenserEx * pDisp = GetProcess()->GetDispenser();
+
+ // copy it over from the remote process
+
+ CoTaskMemHolder<VOID> pMetaDataCopy;
+ CopyRemoteMetaData(buffer, pMetaDataCopy.GetAddr());
+
+
+ //
+ // Setup our metadata import object, m_pIMImport
+ //
+
+ // Save the old mode for restoration
+ VARIANT valueOld;
+ hr = pDisp->GetOption(MetaDataSetUpdate, &valueOld);
+ SIMPLIFYING_ASSUMPTION(!FAILED(hr));
+
+ // Set R/W mode so that we can update the metadata when
+ // we do EnC operations.
+ VARIANT valueRW;
+ V_VT(&valueRW) = VT_UI4;
+ V_I4(&valueRW) = MDUpdateFull;
+ hr = pDisp->SetOption(MetaDataSetUpdate, &valueRW);
+ SIMPLIFYING_ASSUMPTION(!FAILED(hr));
+
+ hr = pDisp->OpenScopeOnMemory(pMetaDataCopy,
+ nMetaDataSize,
+ ofTakeOwnership,
+ IID_IMetaDataImport,
+ reinterpret_cast<IUnknown**>( &m_pIMImport ));
+
+ // MetaData has taken ownership -don't free the memory
+ pMetaDataCopy.SuppressRelease();
+
+ // Immediately restore the old setting.
+ HRESULT hrRestore = pDisp->SetOption(MetaDataSetUpdate, &valueOld);
+ SIMPLIFYING_ASSUMPTION(!FAILED(hrRestore));
+
+ // Throw on errors.
+ IfFailThrow(hr);
+ IfFailThrow(hrRestore);
+
+ // Done!
+}
+
+//---------------------------------------------------------------------------------------
+// Update public MetaData by copying it from the target and updating our IMetaDataImport object.
+//
+// Arguments:
+// buffer - buffer into target space containing metadata blob
+//
+// Notes:
+// Useful for additional class-loads into a dynamic module. A new class means new metadata
+// and so we need to update the RS metadata to stay in sync with the left-side.
+//
+// This will call code:CordbModule::CopyRemoteMetaData to copy the remote buffer locally, and then
+// it can OpenScopeOnMemory().
+//
+void CordbModule::UpdatePublicMetaDataFromRemote(TargetBuffer bufferRemoteMetaData)
+{
+ CONTRACTL
+ {
+ // @dbgtodo metadata - think about the error semantics here. These fails during dispatching an event; so
+ // address this during event pipeline.
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ if (bufferRemoteMetaData.IsEmpty())
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ INTERNAL_API_ENTRY(this->GetProcess()); //
+ LOG((LF_CORDB,LL_INFO100000, "CM::UPMFR: updating with remote buffer 0x%p length 0x%x\n",
+ CORDB_ADDRESS_TO_PTR(bufferRemoteMetaData.pAddress), bufferRemoteMetaData.cbSize));
+ // We're re-initializing existing metadata.
+ _ASSERTE(m_pIMImport != NULL);
+
+
+ HRESULT hr = S_OK;
+
+ ULONG dwMetaDataSize = bufferRemoteMetaData.cbSize;
+
+ // First copy it from the remote process
+ CoTaskMemHolder<VOID> pLocalMetaDataPtr;
+ CopyRemoteMetaData(bufferRemoteMetaData, pLocalMetaDataPtr.GetAddr());
+
+ IMetaDataDispenserEx * pDisp = GetProcess()->GetDispenser();
+ _ASSERTE(pDisp != NULL); // throws on error.
+
+ LOG((LF_CORDB,LL_INFO100000, "CM::RI: converting to new metadata\n"));
+
+ // now verify that the metadata is valid by opening a temporary scope on the memory
+ {
+ ReleaseHolder<IMetaDataImport> pIMImport;
+ hr = pDisp->OpenScopeOnMemory(pLocalMetaDataPtr,
+ dwMetaDataSize,
+ 0,
+ IID_IMetaDataImport,
+ (IUnknown**)&pIMImport);
+ IfFailThrow(hr);
+ }
+
+ // We reopen on an existing instance, not create a new instance.
+ _ASSERTE(m_pIMImport != NULL); //
+
+ // Now tell our current IMetaDataImport object to re-initialize by swapping in the new memory block.
+ // This allows us to keep manipulating metadata objects on other threads without crashing.
+ // This will also invalidate an existing associated Internal MetaData.
+ hr = ReOpenMetaDataWithMemoryEx(m_pIMImport, pLocalMetaDataPtr, dwMetaDataSize, ofTakeOwnership );
+ IfFailThrow(hr);
+
+ // Success. MetaData now owns the metadata memory
+ pLocalMetaDataPtr.SuppressRelease();
+}
+
+//---------------------------------------------------------------------------------------
+// Copy metadata memory from the remote process into a newly allocated local buffer.
+//
+// Arguments:
+// pRemoteMetaDataPtr - pointer to remote buffer
+// dwMetaDataSize - size of buffer.
+// pLocalBuffer - holder to get local buffer.
+//
+// Returns:
+// pLocalBuffer may be allocated.
+// Throws on error (pLocalBuffer may contain garbage).
+// Else if successful, pLocalBuffer contains local copy of metadata.
+//
+// Notes:
+// This can copy metadata out for the dynamic case or the normal case.
+// Uses an allocator (CoTaskMemHolder) that lets us hand off the memory to the metadata.
+void CordbModule::CopyRemoteMetaData(
+ TargetBuffer buffer,
+ CoTaskMemHolder<VOID> * pLocalBuffer)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(pLocalBuffer != NULL);
+ _ASSERTE(!buffer.IsEmpty());
+
+ // Allocate space for the local copy of the metadata
+ // No need to zero out the memory since we'll fill it all here.
+ LPVOID pRawBuffer = CoTaskMemAlloc(buffer.cbSize);
+ if (pRawBuffer == NULL)
+ {
+ ThrowOutOfMemory();
+ }
+
+ pLocalBuffer->Assign(pRawBuffer);
+
+
+
+ // Copy the metadata from the left side
+ GetProcess()->SafeReadBuffer(buffer, (BYTE *)pRawBuffer);
+
+ return;
+}
+
+HRESULT CordbModule::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugModule)
+ {
+ *pInterface = static_cast<ICorDebugModule*>(this);
+ }
+ else if (id == IID_ICorDebugModule2)
+ {
+ *pInterface = static_cast<ICorDebugModule2*>(this);
+ }
+ else if (id == IID_ICorDebugModule3)
+ {
+ *pInterface = static_cast<ICorDebugModule3*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugModule*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbModule::GetProcess(ICorDebugProcess **ppProcess)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppProcess, ICorDebugProcess **);
+
+ *ppProcess = static_cast<ICorDebugProcess*> (GetProcess());
+ GetProcess()->ExternalAddRef();
+
+ return S_OK;
+}
+
+HRESULT CordbModule::GetBaseAddress(CORDB_ADDRESS *pAddress)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pAddress, CORDB_ADDRESS *);
+
+ *pAddress = m_PEBuffer.pAddress;
+ return S_OK;
+}
+
+HRESULT CordbModule::GetAssembly(ICorDebugAssembly **ppAssembly)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppAssembly, ICorDebugAssembly **);
+
+ *ppAssembly = static_cast<ICorDebugAssembly *> (m_pAssembly);
+ if (m_pAssembly != NULL)
+ {
+ m_pAssembly->ExternalAddRef();
+ }
+
+ return S_OK;
+}
+
+// Public implementation of ICorDebugModule::GetName,
+// wrapper around code:GetNameWorker (which throws).
+HRESULT CordbModule::GetName(ULONG32 cchName, ULONG32 *pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this)
+ {
+ EX_TRY
+ {
+ hr = GetNameWorker(cchName, pcchName, szName);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // GetNameWorker can use metadata. If it fails due to missing metadata, or if we fail to find expected
+ // target memory (dump debugging) then we should fall back to getting the file name without metadata.
+ if ((hr == CORDBG_E_MISSING_METADATA) ||
+ (hr == CORDBG_E_READVIRTUAL_FAILURE) ||
+ (hr == HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY)))
+ {
+ DWORD dwImageTimeStamp = 0; // unused
+ DWORD dwImageSize = 0; // unused
+ bool isNGEN = false;
+ StringCopyHolder filePath;
+
+ _ASSERTE(!m_vmPEFile.IsNull());
+ if (this->GetProcess()->GetDAC()->GetMetaDataFileInfoFromPEFile(m_vmPEFile,
+ dwImageTimeStamp,
+ dwImageSize,
+ isNGEN,
+ &filePath))
+ {
+ _ASSERTE(filePath.IsSet());
+
+ // Unfortunately, metadata lookup preferentially takes the ngen image - so in this case,
+ // we need to go back and get the IL image's name instead.
+ if ((isNGEN) &&
+ (this->GetProcess()->GetDAC()->GetILImageInfoFromNgenPEFile(m_vmPEFile,
+ dwImageTimeStamp,
+ dwImageSize,
+ &filePath)))
+ {
+ _ASSERTE(filePath.IsSet());
+ }
+
+ hr = CopyOutString(filePath, cchName, pcchName, szName);
+ }
+ }
+ }
+ PUBLIC_API_END(hr);
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Gets the module pretty name (may be filename or faked up name)
+//
+// Arguments:
+// cchName - count of characters in the szName buffer on input.
+// *pcchName - Optional Out parameter, which gets set to the fully requested size
+// (not just how many characters are written).
+// szName - buffer to get name.
+//
+// Returns:
+// S_OK on success.
+// S_FALSE if we fabricate the name.
+// Return failing HR (on common errors) or Throw on exceptional errors.
+//
+// Note:
+// Filename isn't necessarily the same as the module name in the metadata.
+//
+HRESULT CordbModule::GetNameWorker(ULONG32 cchName, ULONG32 *pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+ HRESULT hr = S_OK;
+ const WCHAR * szTempName = NULL;
+
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ szTempName = GetModulePath();
+ );
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ // To support VS when debugging remotely we act like the Compact Framework and return the assembly name
+ // when asked for the name of an in-memory module.
+ if (szTempName == NULL)
+ {
+ IMetaDataAssemblyImport *pAssemblyImport = NULL;
+ if (SUCCEEDED(hr = GetMetaDataImporter()->QueryInterface(IID_IMetaDataAssemblyImport, (void**)&pAssemblyImport)))
+ {
+ mdAssembly mda = TokenFromRid(1, mdtAssembly);
+ hr = pAssemblyImport->GetAssemblyProps(mda, // [IN] The Assembly for which to get the properties.
+ NULL, // [OUT] Pointer to the Originator blob.
+ NULL, // [OUT] Count of bytes in the Originator Blob.
+ NULL, // [OUT] Hash Algorithm.
+ szName, // [OUT] Buffer to fill with name.
+ cchName, // [IN] Size of buffer in wide chars.
+ (ULONG*)pcchName, // [OUT] Actual # of wide chars in name.
+ NULL, // [OUT] Assembly MetaData.
+ NULL); // [OUT] Flags.
+
+ pAssemblyImport->Release();
+
+ return hr;
+ }
+
+ // reset hr
+ hr = S_OK;
+ }
+
+
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+
+ EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY
+ {
+ StringCopyHolder buffer;
+ // If the module has no file name, then we'll fabricate a fake name
+ if (!szTempName)
+ {
+ // On MiniDumpNormal, if the debugger can't find the module then there's no way we will
+ // find metadata.
+ hr = HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY);
+
+ // Tempting to use the metadata-scope name, but that's a regression from Whidbey. For manifest modules,
+ // the metadata scope name is not initialized with the string the user supplied to create the
+ // dynamic assembly. So we call into the runtime to use CLR heuristics to get a more accurate name.
+ m_pProcess->GetDAC()->GetModuleSimpleName(m_vmModule, &buffer);
+ _ASSERTE(buffer.IsSet());
+ szTempName = buffer;
+ // Note that we considered returning S_FALSE for fabricated names like this, but that's a breaking
+ // change from Whidbey that is known to trigger bugs in vS. If a debugger wants to differentiate
+ // real path names from fake simple names, we'll just have to add a new API with the right semantics.
+ }
+
+ hr = CopyOutString(szTempName, cchName, pcchName, szName);
+ }
+ EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Gets actual name of loaded module. (no faked names)
+//
+// Returns:
+// string for full path to module name. This is a file that can be opened.
+// NULL if name is not available (such as in some dynamic module cases)
+// Throws if failed accessing target
+//
+// Notes:
+// We avoid using the method name "GetModuleFileName" because winbase.h #defines that
+// token (along with many others) to have an A or W suffix.
+const WCHAR * CordbModule::GetModulePath()
+{
+ // Lazily initialize. Module filenames cannot change, and so once
+ // we've retrieved this successfully, it's stored for good.
+ if (!m_strModulePath.IsSet())
+ {
+ IDacDbiInterface * pDac = m_pProcess->GetDAC(); // throws
+ pDac->GetModulePath(m_vmModule, &m_strModulePath); // throws
+ _ASSERTE(m_strModulePath.IsSet());
+ }
+
+ if (m_strModulePath.IsEmpty())
+ {
+ return NULL; // module has no filename
+ }
+ return m_strModulePath;
+}
+
+//---------------------------------------------------------------------------------------
+// Get and caches ngen image path.
+//
+// Returns:
+// Null-terminated string to ngen image path.
+// NULL if there is no ngen filename (eg, file is not ngenned).
+// Throws on error (such as inability to read the path from the target).
+//
+// Notes:
+// This can be used to get the path to find metadata. For ngenned images,
+// the IL (and associated metadata) may not be loaded, so we may want to get the
+// metadata out of the ngen image.
+const WCHAR * CordbModule::GetNGenImagePath()
+{
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Lazily initialize. Module filenames cannot change, and so once
+ // we've retrieved this successfully, it's stored for good.
+ if (!m_strNGenImagePath.IsSet())
+ {
+ IDacDbiInterface * pDac = m_pProcess->GetDAC(); // throws
+ BOOL fNonEmpty = pDac->GetModuleNGenPath(m_vmModule, &m_strNGenImagePath); // throws
+ (void)fNonEmpty; //prevent "unused variable" error from GCC
+ _ASSERTE(m_strNGenImagePath.IsSet() && (m_strNGenImagePath.IsEmpty() == !fNonEmpty));
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (FAILED(hr) ||
+ m_strNGenImagePath == NULL ||
+ m_strNGenImagePath.IsEmpty())
+ {
+ return NULL; // module has no ngen filename
+ }
+ return m_strNGenImagePath;
+}
+
+// Implementation of ICorDebugModule::EnableJITDebugging
+// See also code:CordbModule::SetJITCompilerFlags
+HRESULT CordbModule::EnableJITDebugging(BOOL bTrackJITInfo, BOOL bAllowJitOpts)
+{
+ // Leftside will enforce that this is a valid time to change jit flags.
+ // V1.0 behavior allowed setting these in the middle of a module's lifetime, which meant
+ // that different methods throughout the module may have been jitted differently.
+ // Since V2, this has to be set when the module is first loaded, before anything is jitted.
+
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ DWORD dwFlags = CORDEBUG_JIT_DEFAULT;
+
+ // Since V2, bTrackJITInfo is the default and cannot be turned off.
+ if (!bAllowJitOpts)
+ {
+ dwFlags |= CORDEBUG_JIT_DISABLE_OPTIMIZATION;
+ }
+ return SetJITCompilerFlags(dwFlags);
+}
+
+HRESULT CordbModule::EnableClassLoadCallbacks(BOOL bClassLoadCallbacks)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_ALLOW_LIVE_DO_STOPGO(GetProcess());
+
+ // You must receive ClassLoad callbacks for dynamic modules so that we can keep the metadata up-to-date on the Right
+ // Side. Therefore, we refuse to turn them off for all dynamic modules (they were forced on when the module was
+ // loaded on the Left Side.)
+ if (m_fDynamic && !bClassLoadCallbacks)
+ return E_INVALIDARG;
+
+ if (m_vmDomainFile.IsNull())
+ return E_UNEXPECTED;
+
+ // Send a Set Class Load Flag event to the left side. There is no need to wait for a response, and this can be
+ // called whether or not the process is synchronized.
+ CordbProcess *pProcess = GetProcess();
+
+ DebuggerIPCEvent event;
+ pProcess->InitIPCEvent(&event,
+ DB_IPCE_SET_CLASS_LOAD_FLAG,
+ false,
+ (GetAppDomain()->GetADToken()));
+ event.SetClassLoad.vmDomainFile = this->m_vmDomainFile;
+ event.SetClassLoad.flag = (bClassLoadCallbacks == TRUE);
+
+ HRESULT hr = pProcess->m_cordb->SendIPCEvent(pProcess, &event,
+ sizeof(DebuggerIPCEvent));
+ hr = WORST_HR(hr, event.hr);
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Public implementation of ICorDebugModule::GetFunctionFromToken
+// Get the CordbFunction matches this token / module pair.
+// Each time a function is Enc-ed, it gets its own CordbFunction object.
+// This will return the latest EnC version of the function for this Module,Token pair.
+HRESULT CordbModule::GetFunctionFromToken(mdMethodDef token,
+ ICorDebugFunction **ppFunction)
+{
+ // This is not reentrant. DBI should call code:CordbModule::LookupOrCreateFunctionLatestVersion instead.
+ PUBLIC_API_ENTRY(this);
+ ATT_ALLOW_LIVE_DO_STOPGO(GetProcess()); // @todo - can this be RequiredStop?
+
+
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppFunction, ICorDebugFunction **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ // Check token is valid.
+ if ((token == mdMethodDefNil) ||
+ (!GetMetaDataImporter()->IsValidToken(token)))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ CordbFunction * pFunction = LookupOrCreateFunctionLatestVersion(token);
+
+ *ppFunction = static_cast<ICorDebugFunction*> (pFunction);
+ pFunction->ExternalAddRef();
+
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT CordbModule::GetFunctionFromRVA(CORDB_ADDRESS rva,
+ ICorDebugFunction **ppFunction)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppFunction, ICorDebugFunction **);
+
+ return E_NOTIMPL;
+}
+
+HRESULT CordbModule::LookupClassByToken(mdTypeDef token,
+ CordbClass **ppClass)
+{
+ INTERNAL_API_ENTRY(this->GetProcess()); //
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+ EX_TRY // @dbgtodo exceptions - push this up
+ {
+ *ppClass = NULL;
+
+ if ((token == mdTypeDefNil) || (TypeFromToken(token) != mdtTypeDef))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock()); // @dbgtodo synchronization - Push this up
+
+ CordbClass *pClass = m_classes.GetBase(token);
+ if (pClass == NULL)
+ {
+ // Validate the token.
+ if (!GetMetaDataImporter()->IsValidToken(token))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ RSInitHolder<CordbClass> pClassInit(new CordbClass(this, token));
+ pClass = pClassInit.TransferOwnershipToHash(&m_classes);
+ }
+
+ *ppClass = pClass;
+
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT CordbModule::GetClassFromToken(mdTypeDef token,
+ ICorDebugClass **ppClass)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_ALLOW_LIVE_DO_STOPGO(this->GetProcess()); // @todo - could this be RequiredStopped?
+ VALIDATE_POINTER_TO_OBJECT(ppClass, ICorDebugClass **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ CordbClass *pClass = NULL;
+ *ppClass = NULL;
+
+ // Validate the token.
+ if (!GetMetaDataImporter()->IsValidToken(token))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ hr = LookupClassByToken(token, &pClass);
+ IfFailThrow(hr);
+
+ *ppClass = static_cast<ICorDebugClass*> (pClass);
+ pClass->ExternalAddRef();
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT CordbModule::CreateBreakpoint(ICorDebugModuleBreakpoint **ppBreakpoint)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppBreakpoint, ICorDebugModuleBreakpoint **);
+
+ return E_NOTIMPL;
+}
+
+//
+// Return the token for the Module table entry for this object. The token
+// may then be passed to the meta data import api's.
+//
+HRESULT CordbModule::GetToken(mdModule *pToken)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pToken, mdModule *);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ hr = GetMetaDataImporter()->GetModuleFromScope(pToken);
+ IfFailThrow(hr);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+
+// public implementation for ICorDebugModule::GetMetaDataInterface
+// Return a meta data interface pointer that can be used to examine the
+// meta data for this module.
+HRESULT CordbModule::GetMetaDataInterface(REFIID riid, IUnknown **ppObj)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppObj, IUnknown **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // QI the importer that we already have and return the result.
+ hr = GetMetaDataImporter()->QueryInterface(riid, (void**)ppObj);
+ IfFailThrow(hr);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// LookupFunctionLatestVersion finds the latest cached version of an existing CordbFunction
+// in the given module. If the function doesn't exist, it returns NULL.
+//
+// Arguments:
+// funcMetaDataToken - methoddef token for function to lookup
+//
+//
+// Notes:
+// If no CordbFunction instance was cached, then this returns NULL.
+// use code:CordbModule::LookupOrCreateFunctionLatestVersion to do a lookup that will
+// populate the cache if needed.
+CordbFunction* CordbModule::LookupFunctionLatestVersion(mdMethodDef funcMetaDataToken)
+{
+ INTERNAL_API_ENTRY(this);
+ return m_functions.GetBase(funcMetaDataToken);
+}
+
+
+//-----------------------------------------------------------------------------
+// Lookup (or create) the CordbFunction for the latest EnC version.
+//
+// Arguments:
+// funcMetaDataToken - methoddef token for function to lookup
+//
+// Returns:
+// CordbFunction instance for that token. This will create an instance if needed, and so never returns null.
+// Throws on critical error.
+//
+// Notes:
+// This creates the latest EnC version. Use code:CordbModule::LookupOrCreateFunction to do an
+// enc-version aware function lookup.
+//
+CordbFunction* CordbModule::LookupOrCreateFunctionLatestVersion(mdMethodDef funcMetaDataToken)
+{
+ INTERNAL_API_ENTRY(this);
+ CordbFunction * pFunction = m_functions.GetBase(funcMetaDataToken);
+ if (pFunction != NULL)
+ {
+ return pFunction;
+ }
+
+ // EnC adds each version to the hash. So if the hash lookup fails, then it must not be an EnC case,
+ // and so we can use the default version number.
+ return CreateFunction(funcMetaDataToken, CorDB_DEFAULT_ENC_FUNCTION_VERSION);
+}
+
+//-----------------------------------------------------------------------------
+// LookupOrCreateFunction finds an existing version of CordbFunction in the given module.
+// If the function doesn't exist, it creates it.
+//
+// The outgoing function is not yet fully inititalized. For eg, the Class field is not set.
+// However, ICorDebugFunction::GetClass() will check that and lazily initialize the field.
+//
+// Throws on error.
+//
+CordbFunction * CordbModule::LookupOrCreateFunction(mdMethodDef funcMetaDataToken, SIZE_T enCVersion)
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ CordbFunction * pFunction = m_functions.GetBase(funcMetaDataToken);
+
+ // special case non-existance as need to add to the hash table too
+ if (pFunction == NULL)
+ {
+ // EnC adds each version to the hash. So if the hash lookup fails,
+ // then it must not be an EnC case.
+ return CreateFunction(funcMetaDataToken, enCVersion);
+ }
+
+ // linked list sorted with most recent version at front. Version numbers correspond
+ // to actual edit count against the module, so version numbers not necessarily contiguous.
+ // Any valid EnC version must already exist as we would have created it on the ApplyChanges
+ for (CordbFunction *pf=pFunction; pf != NULL; pf = pf->GetPrevVersion())
+ {
+ if (pf->GetEnCVersionNumber() == enCVersion)
+ {
+ return pf;
+ }
+ }
+
+ _ASSERTE(!"Couldn't find EnC version of function\n");
+ ThrowHR(E_FAIL);
+}
+
+HRESULT CordbModule::IsDynamic(BOOL *pDynamic)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pDynamic, BOOL *);
+
+ (*pDynamic) = m_fDynamic;
+
+ return S_OK;
+}
+
+BOOL CordbModule::IsDynamic()
+{
+ return m_fDynamic;
+}
+
+
+HRESULT CordbModule::IsInMemory(BOOL *pInMemory)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pInMemory, BOOL *);
+
+ (*pInMemory) = m_fInMemory;
+
+ return S_OK;
+}
+
+HRESULT CordbModule::GetGlobalVariableValue(mdFieldDef fieldDef,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this->GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+
+ if (m_pClass == NULL)
+ {
+ CordbClass * pGlobalClass = NULL;
+ hr = LookupClassByToken(COR_GLOBAL_PARENT_TOKEN, &pGlobalClass);
+ IfFailThrow(hr);
+
+ m_pClass.Assign(pGlobalClass);
+ _ASSERTE(m_pClass != NULL);
+ }
+
+ hr = m_pClass->GetStaticFieldValue(fieldDef, NULL, ppValue);
+ IfFailThrow(hr);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+
+
+//
+// CreateFunction creates a new function from the given information and
+// adds it to the module.
+//
+CordbFunction * CordbModule::CreateFunction(mdMethodDef funcMetaDataToken, SIZE_T enCVersion)
+{
+ INTERNAL_API_ENTRY(this);
+
+ // In EnC cases, the token may not yet be valid. We may be caching the CordbFunction
+ // for a token for an added method before the metadata is updated on the RS.
+ // We rely that our caller has done token validation.
+
+ // Create a new CordbFunction object or throw.
+ RSInitHolder<CordbFunction> pFunction(new CordbFunction(this, funcMetaDataToken, enCVersion)); // throws
+ CordbFunction * pCopy = pFunction.TransferOwnershipToHash(&m_functions);
+ return pCopy;
+}
+
+#ifdef EnC_SUPPORTED
+//---------------------------------------------------------------------------------------
+//
+// Creates a new CordbFunction object to represent this new version of a function and
+// updates the module's function collection to mark this as the latest version.
+//
+// Arguments:
+// funcMetaDataToken - the functions methodDef token in this module
+// enCVerison - The new version number of this function
+// ppFunction - Output param for the new instance - optional
+//
+// Assumptions:
+// Assumes the specified version of this function doesn't already exist (i.e. enCVersion
+// is newer than all existing versions).
+//
+HRESULT CordbModule::UpdateFunction(mdMethodDef funcMetaDataToken,
+ SIZE_T enCVersion,
+ CordbFunction** ppFunction)
+{
+ INTERNAL_API_ENTRY(this);
+ if (ppFunction)
+ *ppFunction = NULL;
+
+ _ASSERTE(funcMetaDataToken);
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ // pOldVersion is the 2nd newest version
+ CordbFunction* pOldVersion = LookupFunctionLatestVersion(funcMetaDataToken);
+
+ // if don't have an old version, then create a default versioned one as will most likely
+ // go looking for it later and easier to put it in now than have code to insert it later.
+ if (!pOldVersion)
+ {
+ LOG((LF_ENC, LL_INFO10000, "CM::UF: adding %8.8x with version %d\n", funcMetaDataToken, enCVersion));
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ pOldVersion = CreateFunction(funcMetaDataToken, CorDB_DEFAULT_ENC_FUNCTION_VERSION);
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ }
+
+ // This method should not be called for versions that already exist
+ _ASSERTE( enCVersion > pOldVersion->GetEnCVersionNumber());
+
+ LOG((LF_ENC, LL_INFO10000, "CM::UF: updating %8.8x with version %d\n", funcMetaDataToken, enCVersion));
+ // Create a new function object.
+ CordbFunction * pNewVersion = new (nothrow) CordbFunction(this, funcMetaDataToken, enCVersion);
+
+ if (pNewVersion == NULL)
+ return E_OUTOFMEMORY;
+
+ // Chain the 2nd most recent version onto this instance (this will internal addref).
+ pNewVersion->SetPrevVersion(pOldVersion);
+
+ // Add the function to the Module's hash of all functions.
+ HRESULT hr = m_functions.SwapBase(pOldVersion, pNewVersion);
+
+ if (FAILED(hr))
+ {
+ delete pNewVersion;
+ return hr;
+ }
+
+ // Do cleanup for function which is no longer the latest version
+ pNewVersion->GetPrevVersion()->MakeOld();
+
+ if (ppFunction)
+ *ppFunction = pNewVersion;
+
+ return hr;
+}
+#endif // EnC_SUPPORTED
+
+
+HRESULT CordbModule::LookupOrCreateClass(mdTypeDef classMetaDataToken,CordbClass** ppClass)
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock()); // @dbgtodo exceptions synchronization-
+ // Push this lock up, convert to exceptions.
+
+ HRESULT hr = S_OK;
+ *ppClass = LookupClass(classMetaDataToken);
+ if (*ppClass == NULL)
+ {
+ hr = CreateClass(classMetaDataToken,ppClass);
+ if (!SUCCEEDED(hr))
+ {
+ return hr;
+ }
+ _ASSERTE(*ppClass != NULL);
+ }
+ return hr;
+}
+
+//
+// LookupClass finds an existing CordbClass in the given module.
+// If the class doesn't exist, it returns NULL.
+//
+CordbClass* CordbModule::LookupClass(mdTypeDef classMetaDataToken)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+ return m_classes.GetBase(classMetaDataToken);
+}
+
+//
+// CreateClass creates a new class from the given information and
+// adds it to the module.
+//
+HRESULT CordbModule::CreateClass(mdTypeDef classMetaDataToken,
+ CordbClass** ppClass)
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ CordbClass* pClass = new (nothrow) CordbClass(this, classMetaDataToken);
+
+ if (pClass == NULL)
+ return E_OUTOFMEMORY;
+
+ HRESULT hr = m_classes.AddBase(pClass);
+
+ if (SUCCEEDED(hr))
+ *ppClass = pClass;
+ else
+ delete pClass;
+
+ if (classMetaDataToken == COR_GLOBAL_PARENT_TOKEN)
+ {
+ _ASSERTE( m_pClass == NULL ); //redundant create
+ m_pClass.Assign(pClass);
+ }
+
+ return hr;
+}
+
+
+// Resolve a type-ref from this module to a CordbClass
+//
+// Arguments:
+// token - a Type Ref in this module's scope.
+// ppClass - out parameter to get the class we resolve to.
+//
+// Returns:
+// S_OK on success.
+// CORDBG_E_CLASS_NOT_LOADED is the TypeRef is not yet resolved because the type it will refer
+// to is not yet loaded.
+//
+// Notes:
+// In general, a TypeRef refers to a type in another module. (Although as a corner case, it could
+// refer to this module too). This resolves a TypeRef within the current module's scope to a
+// (TypeDef, metadata scope), which is in turn encapsulated as a CordbClass.
+//
+// A TypeRef has a resolution scope (ModuleRef or AssemblyRef) and string name for the type
+// within that scope. Resolving means:
+// 1. Determining the actual metadata scope loaded for the resolution scope.
+// See also code:CordbModule::ResolveAssemblyInternal
+// If the resolved module hasn't been loaded yet, the resolution will fail.
+// 2. Doing a string lookup of the TypeRef's name within that resolved scope to find the TypeDef.
+// 3. Returning the (resolved scope, TypeDef) pair.
+//
+HRESULT CordbModule::ResolveTypeRef(mdTypeRef token, CordbClass **ppClass)
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+
+ CordbProcess * pProcess = GetProcess();
+
+ _ASSERTE((pProcess->GetShim() == NULL) || pProcess->GetSynchronized());
+
+
+ if ((token == mdTypeRefNil) || (TypeFromToken(token) != mdtTypeRef))
+ {
+ return E_INVALIDARG;
+ }
+
+ if (m_vmDomainFile.IsNull() || m_pAppDomain == NULL)
+ {
+ return E_UNEXPECTED;
+ }
+
+ HRESULT hr = S_OK;
+ *ppClass = NULL;
+ EX_TRY
+ {
+ TypeRefData inData = {m_vmDomainFile, token};
+ TypeRefData outData;
+
+ {
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ pProcess->GetDAC()->ResolveTypeReference(&inData, &outData);
+ }
+
+ CordbModule * pModule = m_pAppDomain->LookupOrCreateModule(outData.vmDomainFile);
+ IfFailThrow(pModule->LookupClassByToken(outData.typeToken, ppClass));
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+
+} // CordbModule::ResolveTypeRef
+
+// Resolve a type ref or def to a CordbClass
+//
+// Arguments:
+// token - a mdTypeDef or mdTypeRef in this module's scope to be resolved
+// ppClass - out parameter to get the CordbClass for this type
+//
+// Notes:
+// See code:CordbModule::ResolveTypeRef for more details.
+HRESULT CordbModule::ResolveTypeRefOrDef(mdToken token, CordbClass **ppClass)
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess()); //
+
+ if ((token == mdTypeRefNil) ||
+ (TypeFromToken(token) != mdtTypeRef && TypeFromToken(token) != mdtTypeDef))
+ return E_INVALIDARG;
+
+ if (TypeFromToken(token)==mdtTypeRef)
+ {
+ // It's a type-ref. That means the type is defined in another module.
+ // That other module is determined at runtime by Fusion / Loader policy. So we need to
+ // ultimately ask the runtime which module was actually loaded.
+ return ( ResolveTypeRef(token, ppClass) );
+ }
+ else
+ {
+ // It's a type-def. This is the easy case because the type is defined in this same module.
+ return ( LookupClassByToken(token, ppClass) );
+ }
+
+}
+
+//
+// GetSize returns the size of the module.
+//
+HRESULT CordbModule::GetSize(ULONG32 *pcBytes)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pcBytes, ULONG32 *);
+
+ *pcBytes = m_PEBuffer.cbSize;
+
+ return S_OK;
+}
+
+CordbAssembly *CordbModule::GetCordbAssembly()
+{
+ INTERNAL_API_ENTRY(this);
+ return m_pAssembly;
+}
+
+
+// This is legacy from the aborted V1 EnC attempt - not used in V2 EnC support
+HRESULT CordbModule::GetEditAndContinueSnapshot(
+ ICorDebugEditAndContinueSnapshot **ppEditAndContinueSnapshot)
+{
+ return E_NOTIMPL;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Requests that an edit be applied to the module for edit and continue and updates
+// the right-side state and metadata.
+//
+// Arguments:
+// cbMetaData - number of bytes in pbMetaData
+// pbMetaData - a delta metadata blob describing the metadata edits to be made
+// cbIL - number of bytes in pbIL
+// pbIL - a new method body stream containing all of the method body information
+// (IL, EH info, etc) for edited and added methods.
+//
+// Return Value:
+// S_OK on success, various errors on failure
+//
+// Notes:
+//
+//
+// This applies the same changes to the RS's copy of the metadata that the left-side will apply to
+// it's copy of the metadata. see code:EditAndContinueModule::ApplyEditAndContinue
+//
+HRESULT CordbModule::ApplyChanges(ULONG cbMetaData,
+ BYTE pbMetaData[],
+ ULONG cbIL,
+ BYTE pbIL[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+#ifdef EnC_SUPPORTED
+ // We enable EnC back in code:CordbModule::SetJITCompilerFlags.
+ // If EnC isn't enabled, then we'll fail in the LS when we try to ApplyChanges.
+ // We'd expect a well-behaved debugger to never actually land here.
+
+
+ LOG((LF_CORDB,LL_INFO10000, "CP::AC: applying changes"));
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pbMetaData,
+ BYTE,
+ cbMetaData,
+ true,
+ true);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pbIL,
+ BYTE,
+ cbIL,
+ true,
+ true);
+
+ HRESULT hr;
+ RSExtSmartPtr<IUnknown> pUnk;
+ RSExtSmartPtr<IMDInternalImport> pMDImport;
+ RSExtSmartPtr<IMDInternalImport> pMDImport2;
+
+ //
+ // Edit was successful - update the right-side state to reflect the edit
+ //
+
+ ++m_EnCCount;
+
+ // apply the changes to our copy of the metadata
+
+ _ASSERTE(m_pIMImport != NULL); // must have metadata at this point in EnC
+ IfFailGo(m_pIMImport->QueryInterface(IID_IUnknown, (void**)&pUnk));
+
+ IfFailGo(GetMDInternalInterfaceFromPublic(pUnk, IID_IMDInternalImport,
+ (void **)&pMDImport));
+
+ // The left-side will call this same method on its copy of the metadata.
+ hr = pMDImport->ApplyEditAndContinue(pbMetaData, cbMetaData, &pMDImport2);
+ if (pMDImport2 != NULL)
+ {
+ // ApplyEditAndContinue() expects IMDInternalImport**, but we give it RSExtSmartPtr<IMDInternalImport>
+ // Silent cast of RSExtSmartPtr to IMDInternalImport* leads to assignment of a raw pointer
+ // without calling AddRef(), thus we need to do it manually.
+
+ // @todo - ApplyEditAndContinue should probably AddRef the out parameter.
+ pMDImport2->AddRef();
+ }
+ IfFailGo(hr);
+
+
+ // We're about to get a new importer object, so release the old one.
+ m_pIMImport.Clear();
+ IfFailGo(GetMDPublicInterfaceFromInternal(pMDImport2, IID_IMetaDataImport, (void **)&m_pIMImport));
+ // set the new RVA value
+
+ // Send the delta over to the debugee and request that it apply the edit
+ IfFailGo( ApplyChangesInternal(cbMetaData, pbMetaData, cbIL, pbIL) );
+
+ EX_TRY
+ {
+
+ m_pInternalMetaDataImport.Clear();
+ UpdateInternalMetaData();
+ }
+ EX_CATCH_HRESULT(hr);
+ _ASSERTE(SUCCEEDED(hr));
+
+ErrExit:
+ // MetaData interface pointers will be automatically released via SmartPtr dtors.
+
+ // @todo : prevent further execution of program
+ return hr;
+#else
+ return E_NOTIMPL;
+#endif
+}
+
+
+
+
+//---------------------------------------------------------------------------------------
+//
+// Requests that an edit be applied to the module for edit and continue and updates
+// some right-side state, but does not update our copy of the metadata.
+//
+// Arguments:
+// cbMetaData - number of bytes in pbMetaData
+// pbMetaData - a delta metadata blob describing the metadata edits to be made
+// cbIL - number of bytes in pbIL
+// pbIL - a new method body stream containing all of the method body information
+// (IL, EH info, etc) for edited and added methods.
+//
+// Return Value:
+// S_OK on success, various errors on failure
+//
+HRESULT CordbModule::ApplyChangesInternal(ULONG cbMetaData,
+ BYTE pbMetaData[],
+ ULONG cbIL,
+ BYTE pbIL[])
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_ENC,LL_INFO100, "CordbProcess::ApplyChangesInternal\n"));
+
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess()); //
+
+ if (m_vmDomainFile.IsNull())
+ return E_UNEXPECTED;
+
+#ifdef EnC_SUPPORTED
+ HRESULT hr;
+
+ void * pRemoteBuf = NULL;
+
+ EX_TRY
+ {
+
+ // Create and initialize the event as synchronous
+ // We'll be sending a NULL appdomain pointer since the individual modules
+ // will contains pointers to their respective A.D.s
+ DebuggerIPCEvent event;
+ GetProcess()->InitIPCEvent(&event, DB_IPCE_APPLY_CHANGES, false, VMPTR_AppDomain::NullPtr());
+
+ event.ApplyChanges.vmDomainFile = this->m_vmDomainFile;
+
+ // Have the left-side create a buffer for us to store the delta into
+ ULONG cbSize = cbMetaData+cbIL;
+ TargetBuffer tbFull = GetProcess()->GetRemoteBuffer(cbSize);
+ pRemoteBuf = CORDB_ADDRESS_TO_PTR(tbFull.pAddress);
+
+ TargetBuffer tbMetaData = tbFull.SubBuffer(0, cbMetaData); // 1st half
+ TargetBuffer tbIL = tbFull.SubBuffer(cbMetaData); // 2nd half
+
+ // Copy the delta metadata over to the debugee
+
+ GetProcess()->SafeWriteBuffer(tbMetaData, pbMetaData); // throws
+ GetProcess()->SafeWriteBuffer(tbIL, pbIL); // throws
+
+ // Send a synchronous event requesting the debugee apply the edit
+ event.ApplyChanges.pDeltaMetadata = tbMetaData.pAddress;
+ event.ApplyChanges.cbDeltaMetadata = tbMetaData.cbSize;
+ event.ApplyChanges.pDeltaIL = tbIL.pAddress;
+ event.ApplyChanges.cbDeltaIL = tbIL.cbSize;
+
+ LOG((LF_ENC,LL_INFO100, "CordbProcess::ApplyChangesInternal sending event\n"));
+ hr = GetProcess()->SendIPCEvent(&event, sizeof(event));
+ hr = WORST_HR(hr, event.hr);
+ IfFailThrow(hr);
+
+ // Allocate space for the return event.
+ // We always copy over the whole buffer size which is bigger than sizeof(DebuggerIPCEvent)
+ // This seems ugly, in this case we know the exact size of the event we want to read
+ // why copy over all the extra data?
+ DebuggerIPCEvent *retEvent = (DebuggerIPCEvent *) _alloca(CorDBIPC_BUFFER_SIZE);
+
+ {
+ //
+ // Wait for events to return from the RC. We expect zero or more add field,
+ // add function or update function events and one completion event.
+ //
+ while (TRUE)
+ {
+ hr = GetProcess()->m_cordb->WaitForIPCEventFromProcess(GetProcess(),
+ GetAppDomain(),
+ retEvent);
+ IfFailThrow(hr);
+
+ if (retEvent->type == DB_IPCE_APPLY_CHANGES_RESULT)
+ {
+ // Done receiving update events
+ hr = retEvent->ApplyChangesResult.hr;
+ LOG((LF_CORDB, LL_INFO1000, "[%x] RCET::DRCE: EnC apply changes result %8.8x.\n", hr));
+ break;
+ }
+
+ _ASSERTE(retEvent->type == DB_IPCE_ENC_UPDATE_FUNCTION ||
+ retEvent->type == DB_IPCE_ENC_ADD_FUNCTION ||
+ retEvent->type == DB_IPCE_ENC_ADD_FIELD);
+ LOG((LF_CORDB, LL_INFO1000, "[%x] RCET::DRCE: EnC %s %8.8x to version %d.\n",
+ GetCurrentThreadId(),
+ retEvent->type == DB_IPCE_ENC_UPDATE_FUNCTION ? "Update function" :
+ retEvent->type == DB_IPCE_ENC_ADD_FUNCTION ? "Add function" : "Add field",
+ retEvent->EnCUpdate.memberMetadataToken, retEvent->EnCUpdate.newVersionNumber));
+
+ CordbAppDomain *pAppDomain = GetAppDomain();
+ _ASSERTE(NULL != pAppDomain);
+ CordbModule* pModule = NULL;
+
+
+ pModule = pAppDomain->LookupOrCreateModule(retEvent->EnCUpdate.vmDomainFile); // throws
+ _ASSERTE(pModule != NULL);
+
+ // update to the newest version
+
+ if (retEvent->type == DB_IPCE_ENC_UPDATE_FUNCTION ||
+ retEvent->type == DB_IPCE_ENC_ADD_FUNCTION)
+ {
+ // Update the function collection to reflect this edit
+ hr = pModule->UpdateFunction(retEvent->EnCUpdate.memberMetadataToken, retEvent->EnCUpdate.newVersionNumber, NULL);
+
+ }
+ // mark the class and relevant type as old so we update it next time we try to query it
+ if (retEvent->type == DB_IPCE_ENC_ADD_FUNCTION ||
+ retEvent->type == DB_IPCE_ENC_ADD_FIELD)
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock()); // @dbgtodo synchronization - push this up
+ CordbClass* pClass = pModule->LookupClass(retEvent->EnCUpdate.classMetadataToken);
+ // if don't find class, that is fine because it hasn't been loaded yet so doesn't
+ // need to be updated
+ if (pClass)
+ {
+ pClass->MakeOld();
+ }
+ }
+ }
+ }
+
+ LOG((LF_ENC,LL_INFO100, "CordbProcess::ApplyChangesInternal complete.\n"));
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // process may have gone away by the time we get here so don't assume is there.
+ CordbProcess *pProcess = GetProcess();
+ if (pProcess)
+ {
+ HRESULT hr2 = pProcess->ReleaseRemoteBuffer(&pRemoteBuf);
+ TESTANDRETURNHR(hr2);
+ }
+ return hr;
+#else // EnC_SUPPORTED
+ return E_NOTIMPL;
+#endif // EnC_SUPPORTED
+
+}
+
+// Set the JMC status for the entire module.
+// All methods specified in others[] will have jmc status !fIsUserCode
+// All other methods will have jmc status fIsUserCode.
+HRESULT CordbModule::SetJMCStatus(
+ BOOL fIsUserCode,
+ ULONG32 cOthers,
+ mdToken others[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (m_vmDomainFile.IsNull())
+ return E_UNEXPECTED;
+
+ // @todo -allow the other parameters. These are functions that have default status
+ // opposite of fIsUserCode.
+ if (cOthers != 0)
+ {
+ _ASSERTE(!"not yet impl for cOthers != 0");
+ return E_NOTIMPL;
+ }
+
+ // Send event to the LS.
+ CordbProcess* pProcess = this->GetProcess();
+ _ASSERTE(pProcess != NULL);
+
+
+ // Tell the LS that this module is/is not user code
+ DebuggerIPCEvent event;
+ pProcess->InitIPCEvent(&event, DB_IPCE_SET_MODULE_JMC_STATUS, true, this->GetAppDomain()->GetADToken());
+ event.SetJMCFunctionStatus.vmDomainFile = m_vmDomainFile;
+ event.SetJMCFunctionStatus.dwStatus = fIsUserCode;
+
+
+ // Note: two-way event here...
+ HRESULT hr = pProcess->m_cordb->SendIPCEvent(pProcess, &event, sizeof(DebuggerIPCEvent));
+
+ // Stop now if we can't even send the event.
+ if (!SUCCEEDED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO10, "CordbModule::SetJMCStatus failed 0x%08x...\n", hr));
+
+ return hr;
+ }
+
+ _ASSERTE(event.type == DB_IPCE_SET_MODULE_JMC_STATUS_RESULT);
+
+ LOG((LF_CORDB, LL_INFO10, "returning from CordbModule::SetJMCStatus 0x%08x...\n", hr));
+
+ return event.hr;
+}
+
+
+//
+// Resolve an assembly given an AssemblyRef token. Note that
+// this will not trigger the loading of assembly. If assembly is not yet loaded,
+// this will return an CORDBG_E_CANNOT_RESOLVE_ASSEMBLY error
+//
+HRESULT CordbModule::ResolveAssembly(mdToken tkAssemblyRef,
+ ICorDebugAssembly **ppAssembly)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this->GetProcess());
+
+ if(ppAssembly)
+ {
+ *ppAssembly = NULL;
+ }
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ CordbAssembly *pCordbAsm = ResolveAssemblyInternal(tkAssemblyRef);
+ if (pCordbAsm == NULL)
+ {
+ // Don't throw here. It's a common-case failure path and not exceptional.
+ hr = CORDBG_E_CANNOT_RESOLVE_ASSEMBLY;
+ }
+ else if(ppAssembly)
+ {
+ _ASSERTE(pCordbAsm != NULL);
+ *ppAssembly = pCordbAsm;
+ pCordbAsm->ExternalAddRef();
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Worker to resolve an assembly ref.
+//
+// Arguments:
+// tkAssemblyRef - token of assembly ref to resolve
+//
+// Returns:
+// Assembly that this token resolves to.
+// NULL if it's a valid token but the assembly has not yet been resolved.
+// (This is a non-exceptional error case).
+//
+// Notes:
+// MetaData has tokens to represent a reference to another assembly.
+// But Loader/Fusion policy ultimately decides which specific assembly is actually loaded
+// for that token.
+// This does the lookup of actual assembly and reports back to the debugger.
+
+CordbAssembly * CordbModule::ResolveAssemblyInternal(mdToken tkAssemblyRef)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+
+ if (TypeFromToken(tkAssemblyRef) != mdtAssemblyRef || tkAssemblyRef == mdAssemblyRefNil)
+ {
+ // Not a valid token
+ ThrowHR(E_INVALIDARG);
+ }
+
+ CordbAssembly * pAssembly = NULL;
+
+ if (!m_vmDomainFile.IsNull())
+ {
+ // Get DAC to do the real work to resolve the assembly
+ VMPTR_DomainAssembly vmDomainAssembly = GetProcess()->GetDAC()->ResolveAssembly(m_vmDomainFile, tkAssemblyRef);
+
+ // now find the ICorDebugAssembly corresponding to it
+ if (!vmDomainAssembly.IsNull() && m_pAppDomain != NULL)
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ // Don't throw here because if the lookup fails, we want to throw CORDBG_E_CANNOT_RESOLVE_ASSEMBLY.
+ pAssembly = m_pAppDomain->LookupOrCreateAssembly(vmDomainAssembly);
+ }
+ }
+
+ return pAssembly;
+}
+
+//
+// CreateReaderForInMemorySymbols - create an ISymUnmanagedReader object for symbols
+// which are loaded into memory in the CLR. See interface definition in cordebug.idl for
+// details.
+//
+HRESULT CordbModule::CreateReaderForInMemorySymbols(REFIID riid, void** ppObj)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ CordbProcess *pProcess = GetProcess();
+ ATT_REQUIRE_STOPPED_MAY_FAIL(pProcess);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Get the symbol memory in a stream to give to the reader.
+ ReleaseHolder<IStream> pStream;
+ IDacDbiInterface::SymbolFormat symFormat = GetInMemorySymbolStream(&pStream);
+
+ // First create the symbol binder corresponding to the format of the stream
+ ReleaseHolder<ISymUnmanagedBinder> pBinder;
+ if (symFormat == IDacDbiInterface::kSymbolFormatPDB)
+ {
+#ifndef FEATURE_PAL
+ // PDB format - use diasymreader.dll with COM activation
+ InlineSString<_MAX_PATH> ssBuf;
+ IfFailThrow(GetHModuleDirectory(GetModuleInst(), ssBuf));
+ IfFailThrow(FakeCoCreateInstanceEx(CLSID_CorSymBinder_SxS,
+ ssBuf.GetUnicode(),
+ IID_ISymUnmanagedBinder,
+ (void**)&pBinder,
+ NULL));
+#else
+ IfFailThrow(FakeCoCreateInstance(CLSID_CorSymBinder_SxS,
+ IID_ISymUnmanagedBinder,
+ (void**)&pBinder));
+#endif
+ }
+ else if (symFormat == IDacDbiInterface::kSymbolFormatILDB)
+ {
+ // ILDB format - use statically linked-in ildbsymlib
+ IfFailThrow(IldbSymbolsCreateInstance(CLSID_CorSymBinder_SxS,
+ IID_ISymUnmanagedBinder,
+ (void**)&pBinder));
+ }
+ else
+ {
+ // No in-memory symbols, return the appropriate error
+ _ASSERTE(symFormat == IDacDbiInterface::kSymbolFormatNone);
+ if (m_fDynamic || m_fInMemory)
+ {
+ // This is indeed an in-memory or dynamic module, we just don't have any symbols for it.
+ // This means the application didn't supply any, or they are not yet available. Symbols
+ // first become available at LoadClass time for dynamic modules and UpdateModuleSymbols
+ // time for non-dynamic in-memory modules.
+ ThrowHR(CORDBG_E_SYMBOLS_NOT_AVAILABLE);
+ }
+
+ // This module is on disk - the debugger should use it's normal symbol-loading logic.
+ ThrowHR(CORDBG_E_MODULE_LOADED_FROM_DISK);
+ }
+
+ // In the attach or dump case, if we attach or take the dump after we have defined a dynamic module, we may
+ // have already set the symbol format to "PDB" by the time we call CreateReaderForInMemorySymbols during initialization
+ // for loaded modules. (In the launch case, we do this initialization when the module is actually loaded, and before we
+ // set the symbol format.) When we call CreateReaderForInMemorySymbols, we can't assume the initialization was already
+ // performed or specifically, that we already have m_pIMImport initialized. We can't call into diasymreader with a NULL
+ // pointer as the value for m_pIMImport, so we need to check that here.
+ if (m_pIMImport == NULL)
+ {
+ ThrowHR(CORDBG_E_SYMBOLS_NOT_AVAILABLE);
+ }
+
+ // Now create the symbol reader from the data
+ ReleaseHolder<ISymUnmanagedReader> pReader;
+ IfFailThrow(pBinder->GetReaderFromStream(m_pIMImport, pStream, &pReader));
+
+ // Attempt to return the interface requested
+ // Note that this does an AddRef for our return value ppObj, so we don't suppress the release
+ // of the pReader holder.
+ IfFailThrow(pReader->QueryInterface(riid, ppObj));
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+/* ------------------------------------------------------------------------- *
+ * Class class
+ * ------------------------------------------------------------------------- */
+
+//---------------------------------------------------------------------------------------
+// Set the continue counter that marks when the module is in its Load event
+//
+// Notes:
+// Jit flags can only be changed in the real module Load event. We may
+// have multiple module load events on different threads coming at the
+// same time. So each module load tracks its continue counter.
+//
+// This can be used by code:CordbModule::EnsureModuleIsInLoadCallback to
+// properly return CORDBG_E_MUST_BE_IN_LOAD_MODULE
+void CordbModule::SetLoadEventContinueMarker()
+{
+ // Well behaved targets should only set this once.
+ GetProcess()->TargetConsistencyCheck(m_nLoadEventContinueCounter == 0);
+
+ m_nLoadEventContinueCounter = GetProcess()->m_continueCounter;
+}
+
+//---------------------------------------------------------------------------------------
+// Return CORDBG_E_MUST_BE_IN_LOAD_MODULE if the module is not in the load module callback.
+//
+// Notes:
+// The comparison is done via continue counters. The counter of the load
+// event is cached via code:CordbModule::SetLoadEventContinueMarker.
+//
+// This state is currently stored on the RS. Alternatively, it could likely be retreived from the LS state as
+// well. One disadvantage of the current model is that if we detach during the load-module callback and
+// then reattach, the RS state is flushed and we lose the fact that we can toggle the jit flags.
+HRESULT CordbModule::EnsureModuleIsInLoadCallback()
+{
+ if (this->m_nLoadEventContinueCounter < GetProcess()->m_continueCounter)
+ {
+ return CORDBG_E_MUST_BE_IN_LOAD_MODULE;
+ }
+ else
+ {
+ return S_OK;
+ }
+}
+
+// Implementation of ICorDebugModule2::SetJITCompilerFlags
+// See also code:CordbModule::EnableJITDebugging
+HRESULT CordbModule::SetJITCompilerFlags(DWORD dwFlags)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ CordbProcess *pProcess = GetProcess();
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(pProcess);
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ // can't have a subset of these, eg 0x101, so make sure we have an exact match
+ if ((dwFlags != CORDEBUG_JIT_DEFAULT) &&
+ (dwFlags != CORDEBUG_JIT_DISABLE_OPTIMIZATION) &&
+ (dwFlags != CORDEBUG_JIT_ENABLE_ENC))
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ BOOL fAllowJitOpts = ((dwFlags & CORDEBUG_JIT_DISABLE_OPTIMIZATION) != CORDEBUG_JIT_DISABLE_OPTIMIZATION);
+ BOOL fEnableEnC = ((dwFlags & CORDEBUG_JIT_ENABLE_ENC) == CORDEBUG_JIT_ENABLE_ENC);
+
+ // Can only change jit flags when module is first loaded and before there's any jitted code.
+ // This ensures all code in the module is jitted the same way.
+ hr = EnsureModuleIsInLoadCallback();
+
+ if (SUCCEEDED(hr))
+ {
+ // DD interface will check if it's a valid time to change the flags.
+ hr = pProcess->GetDAC()->SetCompilerFlags(GetRuntimeDomainFile(), fAllowJitOpts, fEnableEnC);
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // emulate v2 hresults
+ if (GetProcess()->GetShim() != NULL)
+ {
+ // Emulate Whidbey error hresults
+ hr = GetProcess()->GetShim()->FilterSetJitFlagsHresult(hr);
+ }
+ return hr;
+
+}
+
+// Implementation of ICorDebugModule2::GetJitCompilerFlags
+HRESULT CordbModule::GetJITCompilerFlags(DWORD *pdwFlags )
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pdwFlags, DWORD*);
+ *pdwFlags = CORDEBUG_JIT_DEFAULT;;
+
+ CordbProcess *pProcess = GetProcess();
+
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(pProcess);
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ BOOL fAllowJitOpts;
+ BOOL fEnableEnC;
+
+ pProcess->GetDAC()->GetCompilerFlags (
+ GetRuntimeDomainFile(),
+ &fAllowJitOpts,
+ &fEnableEnC);
+
+ if (fEnableEnC)
+ {
+ *pdwFlags = CORDEBUG_JIT_ENABLE_ENC;
+ }
+ else if (! fAllowJitOpts)
+ {
+ *pdwFlags = CORDEBUG_JIT_DISABLE_OPTIMIZATION;
+ }
+
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+BOOL CordbModule::IsWinMD()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ if (m_isIlWinMD == Uninitialized)
+ {
+ BOOL isWinRT;
+ HRESULT hr = E_FAIL;
+
+ {
+ RSLockHolder processLockHolder(GetProcess()->GetProcessLock());
+ hr = GetProcess()->GetDAC()->IsWinRTModule(m_vmModule, isWinRT);
+ }
+
+ _ASSERTE(SUCCEEDED(hr));
+ if (FAILED(hr))
+ ThrowHR(hr);
+
+ if (isWinRT)
+ m_isIlWinMD = True;
+ else
+ m_isIlWinMD = False;
+ }
+
+ return m_isIlWinMD == True;
+}
+
+/* ------------------------------------------------------------------------- *
+ * CordbCode class
+ * ------------------------------------------------------------------------- */
+//-----------------------------------------------------------------------------
+// CordbCode constructor
+// Arguments:
+// Input:
+// pFunction - CordbFunction instance for this function
+// encVersion - Edit and Continue version number for this code chunk
+// fIsIL - indicates whether the instance is a CordbILCode (as
+// opposed to a CordbNativeCode)
+// id - This is the hashtable key for CordbCode objects
+// - for native code, the code start address
+// - for IL code, 0
+// - for ReJit IL code, the remote pointer to the ReJitSharedInfo
+// Output:
+// fields of the CordbCode instance have been initialized
+//-----------------------------------------------------------------------------
+
+CordbCode::CordbCode(CordbFunction * pFunction, UINT_PTR id, SIZE_T encVersion, BOOL fIsIL)
+ : CordbBase(pFunction->GetProcess(), id, enumCordbCode),
+ m_fIsIL(fIsIL),
+ m_nVersion(encVersion),
+ m_rgbCode(NULL),
+ m_continueCounterLastSync(0),
+ m_pFunction(pFunction)
+{
+ _ASSERTE(pFunction != NULL);
+ _ASSERTE(m_nVersion >= CorDB_DEFAULT_ENC_FUNCTION_VERSION);
+} // CordbCode::CordbCode
+
+//-----------------------------------------------------------------------------
+// Destructor for CordbCode object
+//-----------------------------------------------------------------------------
+CordbCode::~CordbCode()
+{
+ _ASSERTE(IsNeutered());
+}
+
+//-----------------------------------------------------------------------------
+// Neutered by CordbFunction
+// See CordbBase::Neuter for neuter semantics.
+//-----------------------------------------------------------------------------
+void CordbCode::Neuter()
+{
+ m_pFunction = NULL;
+
+ delete [] m_rgbCode;
+ m_rgbCode = NULL;
+
+ CordbBase::Neuter();
+}
+
+//-----------------------------------------------------------------------------
+// Public method for IUnknown::QueryInterface.
+// Has standard QI semantics.
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::QueryInterface(REFIID id, void ** pInterface)
+{
+ if (id == IID_ICorDebugCode)
+ {
+ *pInterface = static_cast<ICorDebugCode*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugCode *>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// NOT IMPLEMENTED. Remap sequence points are entirely private to the LS,
+// and ICorDebug will dispatch a RemapOpportunity callback to notify the
+// debugger instead of letting the debugger query for the points.
+//
+// Returns: E_NOTIMPL
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::GetEnCRemapSequencePoints(ULONG32 cMap, ULONG32 * pcMap, ULONG32 offsets[])
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcMap, ULONG32*);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(offsets, ULONG32*, cMap, true, true);
+
+ //
+ // Old EnC interface - deprecated
+ //
+ return E_NOTIMPL;
+} // CordbCode::GetEnCRemapSequencePoints
+
+
+//-----------------------------------------------------------------------------
+// CordbCode::IsIL
+// Public method to determine if this Code object represents IL or native code.
+//
+// Parameters:
+// pbIL - OUT: on return, set to True if IL code, else False.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::IsIL(BOOL *pbIL)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pbIL, BOOL *);
+
+ *pbIL = IsIL();
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CordbCode::GetFunction
+// Public method to get the Function object associated with this Code object.
+// Function:Code = 1:1 for IL, and 1:n for Native. So there is always a single
+// unique Function object to return.
+//
+// Parameters:
+// ppFunction - OUT: returns the Function object for this Code.
+//
+// Returns:
+// S_OK - on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::GetFunction(ICorDebugFunction **ppFunction)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppFunction, ICorDebugFunction **);
+
+ *ppFunction = static_cast<ICorDebugFunction*> (m_pFunction);
+ m_pFunction->ExternalAddRef();
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CordbCode::GetSize
+// Get the size of the code in bytes. If this is IL code, it will be bytes of IL.
+// If this is native code, it will be bytes of native code.
+//
+// Parameters:
+// pcBytes - OUT: on return, set to the size of the code in bytes.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::GetSize(ULONG32 *pcBytes)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pcBytes, ULONG32 *);
+
+ *pcBytes = GetSize();
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CordbCode::CreateBreakpoint
+// public method to create a breakpoint in the code.
+//
+// Parameters:
+// offset - offset in bytes to set the breakpoint at. If this is a Native
+// code object (IsIl == false), then units are bytes of native code. If
+// this is an IL code object, then units are bytes of IL code.
+// ppBreakpoint- out-parameter to hold newly created breakpoint object.
+//
+// Return value:
+// S_OK iff *ppBreakpoint is set. Else some error.
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::CreateBreakpoint(ULONG32 offset,
+ ICorDebugFunctionBreakpoint **ppBreakpoint)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppBreakpoint, ICorDebugFunctionBreakpoint **);
+
+ HRESULT hr;
+ ULONG32 size = GetSize();
+ LOG((LF_CORDB, LL_INFO10000, "CCode::CreateBreakpoint, offset=%d, size=%d, IsIl=%d, this=0x%p\n",
+ offset, size, m_fIsIL, this));
+
+ // Make sure the offset is within range of the method.
+ // If we're native code, then both offset & total code size are bytes of native code,
+ // else they're both bytes of IL.
+ if (offset >= size)
+ {
+ return CORDBG_E_UNABLE_TO_SET_BREAKPOINT;
+ }
+
+ CordbFunctionBreakpoint *bp = new (nothrow) CordbFunctionBreakpoint(this, offset);
+
+ if (bp == NULL)
+ return E_OUTOFMEMORY;
+
+ hr = bp->Activate(TRUE);
+ if (SUCCEEDED(hr))
+ {
+ *ppBreakpoint = static_cast<ICorDebugFunctionBreakpoint*> (bp);
+ bp->ExternalAddRef();
+ return S_OK;
+ }
+ else
+ {
+ delete bp;
+ return hr;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// CordbCode::GetCode
+// Public method to get the code-bytes for this Code object. For an IL-code
+// object, this will be bytes of IL. For a native-code object, this will be
+// bytes of native opcodes.
+// The units of the offsets are the same as the units on the CordbCode object.
+// (eg, IL offsets for an IL code object, and native offsets for a native code object)
+// This will glue together hot + cold regions into a single blob.
+//
+// Units are also logical (aka linear) values, which
+// Parameters:
+// startOffset - linear offset in Code to start copying from.
+// endOffset - linear offset in Code to end copying from. Total bytes copied would be (endOffset - startOffset)
+// cBufferAlloc - number of bytes in the buffer supplied by the buffer[] parameter.
+// buffer - caller allocated storage to copy bytes into.
+// pcBufferSize - required out-parameter, holds number of bytes copied into buffer.
+//
+// Returns:
+// S_OK if copy successful. Else error.
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::GetCode(ULONG32 startOffset,
+ ULONG32 endOffset,
+ ULONG32 cBufferAlloc,
+ BYTE buffer[],
+ ULONG32 *pcBufferSize)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(buffer, BYTE, cBufferAlloc, true, true);
+ VALIDATE_POINTER_TO_OBJECT(pcBufferSize, ULONG32 *);
+
+ LOG((LF_CORDB,LL_EVERYTHING, "CC::GC: for token:0x%x\n", m_pFunction->GetMetadataToken()));
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ *pcBufferSize = 0;
+
+ // Check ranges.
+ ULONG32 totalSize = GetSize();
+
+ if (cBufferAlloc < endOffset - startOffset)
+ endOffset = startOffset + cBufferAlloc;
+
+ if (endOffset > totalSize)
+ endOffset = totalSize;
+
+ if (startOffset > totalSize)
+ startOffset = totalSize;
+
+ // Check the continue counter since WriteMemory bumps it up.
+ if ((m_rgbCode == NULL) ||
+ (m_continueCounterLastSync < GetProcess()->m_continueCounter))
+ {
+ ReadCodeBytes();
+ m_continueCounterLastSync = GetProcess()->m_continueCounter;
+ }
+
+ // if we just got the code, we'll have to copy it over
+ if (*pcBufferSize == 0 && m_rgbCode != NULL)
+ {
+ memcpy(buffer,
+ m_rgbCode+startOffset,
+ endOffset - startOffset);
+ *pcBufferSize = endOffset - startOffset;
+ }
+ return hr;
+
+} // CordbCode::GetCode
+
+#include "dbgipcevents.h"
+
+//-----------------------------------------------------------------------------
+// CordbCode::GetVersionNumber
+// Public method to get the EnC version number of the code.
+//
+// Parameters:
+// nVersion - OUT: on return, set to the version number.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::GetVersionNumber( ULONG32 *nVersion)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(nVersion, ULONG32 *);
+
+ LOG((LF_CORDB,LL_INFO10000,"R:CC:GVN:Returning 0x%x "
+ "as version\n",m_nVersion));
+
+ *nVersion = (ULONG32)m_nVersion;
+
+#ifndef EnC_SUPPORTED
+ _ASSERTE(*nVersion == 1);
+#endif // EnC_SUPPORTED
+
+ return S_OK;
+}
+
+// get the CordbFunction instance for this code object
+CordbFunction * CordbCode::GetFunction()
+{
+ _ASSERTE(m_pFunction != NULL);
+ return m_pFunction;
+}
+
+/* ------------------------------------------------------------------------- *
+ * CordbILCode class
+ * ------------------------------------------------------------------------- */
+
+//-----------------------------------------------------------------------------
+// CordbILCode ctor to make IL code.
+// Arguments:
+// Input:
+// pFunction - pointer to the CordbFunction instance for this function
+// codeRegionInfo - starting address and size in bytes of IL code blob
+// nVersion - EnC version number for this IL code blob
+// localVarSigToken - LocalVarSig for this IL blob
+// id - the key when using ILCode in a CordbHashTable
+// Output:
+// fields of this instance of CordbILCode have been initialized
+//-----------------------------------------------------------------------------
+CordbILCode::CordbILCode(CordbFunction * pFunction,
+ TargetBuffer codeRegionInfo,
+ SIZE_T nVersion,
+ mdSignature localVarSigToken,
+ UINT_PTR id)
+ : CordbCode(pFunction, id, nVersion, TRUE),
+#ifdef EnC_SUPPORTED
+ m_fIsOld(FALSE),
+#endif
+ m_codeRegionInfo(codeRegionInfo),
+ m_localVarSigToken(localVarSigToken)
+{
+} // CordbILCode::CordbILCode
+
+
+#ifdef EnC_SUPPORTED
+//-----------------------------------------------------------------------------
+// CordbILCode::MakeOld
+// Internal method to perform any cleanup necessary when a code blob is no longer
+// the most current.
+//-----------------------------------------------------------------------------
+void CordbILCode::MakeOld()
+{
+ m_fIsOld = TRUE;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// CordbILCode::GetAddress
+// Public method to get the Entry address for the code. This is the address
+// where the method first starts executing.
+//
+// Parameters:
+// pStart - out-parameter to hold start address.
+//
+// Returns:
+// S_OK if *pStart is properly updated.
+//-----------------------------------------------------------------------------
+HRESULT CordbILCode::GetAddress(CORDB_ADDRESS * pStart)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pStart, CORDB_ADDRESS *);
+
+
+ _ASSERTE(this != NULL);
+ _ASSERTE(this->GetFunction() != NULL);
+ _ASSERTE(this->GetFunction()->GetModule() != NULL);
+ _ASSERTE(this->GetFunction()->GetModule()->GetProcess() == GetProcess());
+
+ *pStart = (m_codeRegionInfo.pAddress);
+
+ return S_OK;
+} // CordbILCode::GetAddress
+
+//-----------------------------------------------------------------------------
+// CordbILCode::ReadCodeBytes
+// Reads the actual bytes of IL code into the data member m_rgbCode
+// Arguments:
+// none (uses data members)
+// Return value:
+// standard HRESULT values
+// also allocates and initializes m_rgbCode
+// Notes: assumes that the caller has checked to ensure that m_rgbCode doesn't
+// hold valid data
+//-----------------------------------------------------------------------------
+HRESULT CordbILCode::ReadCodeBytes()
+{
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // We have an address & size, so we'll just call ReadMemory.
+ // This will conveniently strip out any patches too.
+ CORDB_ADDRESS pStart = m_codeRegionInfo.pAddress;
+ ULONG32 cbSize = (ULONG32) m_codeRegionInfo.cbSize;
+
+ delete [] m_rgbCode;
+ m_rgbCode = new BYTE[cbSize]; // throws
+
+ SIZE_T cbRead;
+ hr = GetProcess()->ReadMemory(pStart, cbSize, m_rgbCode, &cbRead);
+ IfFailThrow(hr);
+
+ SIMPLIFYING_ASSUMPTION(cbRead == cbSize);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbILCode::ReadCodeBytes
+
+//-----------------------------------------------------------------------------
+// CordbILCode::GetILToNativeMapping
+// Public method (implements ICorDebugCode) to get the IL-->{ Native Start, Native End} mapping.
+// Since 1 CordbILCode can map to multiple CordbNativeCode due to generics, we cannot reliably return the
+// mapping information in all cases. So we always fail with CORDBG_E_NON_NATIVE_FRAME. The caller should
+// call code:CordbNativeCode::GetILToNativeMapping instead.
+//
+// Parameters:
+// cMap - size of incoming map[] array (in elements).
+// pcMap - OUT: full size of IL-->Native map (in elements).
+// map - caller allocated array to be filled in.
+//
+// Returns:
+// CORDBG_E_NON_NATIVE_FRAME in all cases
+//-----------------------------------------------------------------------------
+HRESULT CordbILCode::GetILToNativeMapping(ULONG32 cMap,
+ ULONG32 * pcMap,
+ COR_DEBUG_IL_TO_NATIVE_MAP map[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcMap, ULONG32 *);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(map, COR_DEBUG_IL_TO_NATIVE_MAP *, cMap, true, true);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return CORDBG_E_NON_NATIVE_FRAME;
+} // CordbILCode::GetILToNativeMapping
+
+
+/*
+* CordbILCode::GetLocalVarSig
+*
+* Get the method's local variable metadata signature. This may be cached, but for dynamic modules we'll always
+* read it from the metadata. This function also returns the count of local variables in the method.
+*
+* Parameters:
+* pLocalSigParser - OUT: the local variable signature for the method.
+* pLocalCount - OUT: the number of locals the method has.
+*
+* Returns:
+* HRESULT for success or failure.
+*
+*/
+HRESULT CordbILCode::GetLocalVarSig(SigParser *pLocalSigParser,
+ ULONG *pLocalVarCount)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ CONTRACTL // @dbgtodo exceptions - convert to throws...
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ FAIL_IF_NEUTERED(this);
+ HRESULT hr = S_OK;
+
+ // A function will not have a local var sig if it has no locals!
+ if (m_localVarSigToken != mdSignatureNil)
+ {
+ PCCOR_SIGNATURE localSignature;
+ ULONG size;
+ ULONG localCount;
+
+ EX_TRY // // @dbgtodo exceptions - push this up
+ {
+ GetFunction()->GetModule()->UpdateMetaDataCacheIfNeeded(m_localVarSigToken);
+ hr = GetFunction()->GetModule()->GetMetaDataImporter()->GetSigFromToken(m_localVarSigToken,
+ &localSignature,
+ &size);
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_WARNING, "CICF::GLVS caught hr=0x%x\n", hr));
+ }
+ IfFailRet(hr);
+
+ LOG((LF_CORDB, LL_INFO100000, "CIC::GLVS creating sig parser sig=0x%x size=0x%x\n", localSignature, size));
+ SigParser sigParser = SigParser(localSignature, size);
+
+ ULONG data;
+
+ IfFailRet(sigParser.GetCallingConvInfo(&data));
+
+ _ASSERTE(data == IMAGE_CEE_CS_CALLCONV_LOCAL_SIG);
+
+ // Snagg the count of locals in the sig.
+ IfFailRet(sigParser.GetData(&localCount));
+ LOG((LF_CORDB, LL_INFO100000, "CIC::GLVS localCount=0x%x\n", localCount));
+ if (pLocalSigParser != NULL)
+ {
+ *pLocalSigParser = sigParser;
+ }
+ if (pLocalVarCount != NULL)
+ {
+ *pLocalVarCount = localCount;
+ }
+ }
+ else
+ {
+ //
+ // Signature is Nil, so fill in everything with NULLs and zeros
+ //
+ if (pLocalSigParser != NULL)
+ {
+ *pLocalSigParser = SigParser(NULL, 0);
+ }
+
+ if (pLocalVarCount != NULL)
+ {
+ *pLocalVarCount = 0;
+ }
+ }
+ LOG((LF_CORDB, LL_INFO100000, "CIC::GLVS returning hr=0x%x\n", hr));
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// CordbILCode::GetLocalVariableType
+// Internal method. Return the type of an IL local, specified by 0-based index.
+//
+// Parameters:
+// dwIndex - 0-based index for IL local number.
+// inst - instantiation information if this is a generic function. Eg,
+// if function is List<T>, inst describes T.
+// res - out parameter, yields to CordbType of the local.
+//
+// Return:
+// S_OK on success.
+//
+HRESULT CordbILCode::GetLocalVariableType(DWORD dwIndex,
+ const Instantiation * pInst,
+ CordbType ** ppResultType)
+{
+ ATT_ALLOW_LIVE_DO_STOPGO(GetProcess());
+ LOG((LF_CORDB, LL_INFO10000, "CIC::GLVT dwIndex=0x%x pInst=0x%p\n", dwIndex, pInst));
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ // Get the local variable signature.
+ SigParser sigParser;
+ ULONG cLocals;
+
+ IfFailThrow(GetLocalVarSig(&sigParser, &cLocals));
+
+ // Check the index.
+ if (dwIndex >= cLocals)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ // Run the signature and find the required argument.
+ for (unsigned int i = 0; i < dwIndex; i++)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CIC::GLVT scanning index 0x%x\n", dwIndex));
+ IfFailThrow(sigParser.SkipExactlyOne());
+ }
+
+ hr = CordbType::SigToType(GetFunction()->GetModule(), &sigParser, pInst, ppResultType);
+ LOG((LF_CORDB, LL_INFO10000, "CIC::GLVT CT::SigToType returned hr=0x%x\n", hr));
+ IfFailThrow(hr);
+
+ } EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+mdSignature CordbILCode::GetLocalVarSigToken()
+{
+ return m_localVarSigToken;
+}
+
+CordbReJitILCode::CordbReJitILCode(CordbFunction *pFunction, SIZE_T encVersion, VMPTR_SharedReJitInfo vmSharedReJitInfo) :
+CordbILCode(pFunction, TargetBuffer(), encVersion, mdSignatureNil, VmPtrToCookie(vmSharedReJitInfo)),
+m_cClauses(0),
+m_cbLocalIL(0),
+m_cILMap(0)
+{
+ _ASSERTE(!vmSharedReJitInfo.IsNull());
+ DacSharedReJitInfo data = { 0 };
+ IfFailThrow(GetProcess()->GetDAC()->GetSharedReJitInfoData(vmSharedReJitInfo, &data));
+ IfFailThrow(Init(&data));
+}
+
+//-----------------------------------------------------------------------------
+// CordbReJitILCode::Init
+//
+// Returns:
+// S_OK if all fields are inited. Else error.
+HRESULT CordbReJitILCode::Init(DacSharedReJitInfo* pSharedReJitInfo)
+{
+ HRESULT hr = S_OK;
+
+ // Instrumented IL map
+ if (pSharedReJitInfo->m_cInstrumentedMapEntries)
+ {
+ if (pSharedReJitInfo->m_cInstrumentedMapEntries > 100000)
+ return CORDBG_E_TARGET_INCONSISTENT;
+ m_cILMap = pSharedReJitInfo->m_cInstrumentedMapEntries;
+ m_pILMap = new (nothrow)COR_IL_MAP[m_cILMap];
+ TargetBuffer mapBuffer(pSharedReJitInfo->m_rgInstrumentedMapEntries, m_cILMap*sizeof(COR_IL_MAP));
+ IfFailRet(GetProcess()->SafeReadBuffer(mapBuffer, (BYTE*)m_pILMap.GetValue(), FALSE /* bThrowOnError */));
+ }
+
+ // Read the method's IL header
+ CORDB_ADDRESS pIlHeader = pSharedReJitInfo->m_pbIL;
+ IMAGE_COR_ILMETHOD_FAT header = { 0 };
+ bool headerMustBeTiny = false;
+ ULONG32 headerSize = 0;
+ hr = GetProcess()->SafeReadStruct(pIlHeader, &header);
+ if (hr != S_OK)
+ {
+ // Its possible the header is tiny and there isn't enough memory to read a complete
+ // FAT header
+ headerMustBeTiny = true;
+ IfFailRet(GetProcess()->SafeReadStruct(pIlHeader, (IMAGE_COR_ILMETHOD_TINY *)&header));
+ }
+
+ // Read the ILCodeSize and LocalVarSigTok from header
+ ULONG32 ilCodeSize = 0;
+ IMAGE_COR_ILMETHOD_TINY *pMethodTinyHeader = (IMAGE_COR_ILMETHOD_TINY *)&header;
+ bool isTinyHeader = ((pMethodTinyHeader->Flags_CodeSize & (CorILMethod_FormatMask >> 1)) == CorILMethod_TinyFormat);
+ if (isTinyHeader)
+ {
+ ilCodeSize = (((unsigned)pMethodTinyHeader->Flags_CodeSize) >> (CorILMethod_FormatShift - 1));
+ headerSize = sizeof(IMAGE_COR_ILMETHOD_TINY);
+ m_localVarSigToken = mdSignatureNil;
+ }
+ else if (headerMustBeTiny)
+ {
+ // header was not CorILMethod_TinyFormat
+ // this is not possible, must be an error when reading from data target
+ return CORDBG_E_READVIRTUAL_FAILURE;
+ }
+ else
+ {
+ ilCodeSize = header.CodeSize;
+ headerSize = header.Size * 4;
+ m_localVarSigToken = header.LocalVarSigTok;
+ }
+ if (ilCodeSize == 0 || ilCodeSize > 100000)
+ {
+ return CORDBG_E_TARGET_INCONSISTENT;
+ }
+
+ m_codeRegionInfo.Init(pIlHeader + headerSize, ilCodeSize);
+ m_pLocalIL = new (nothrow) BYTE[ilCodeSize];
+ if (m_pLocalIL == NULL)
+ return E_OUTOFMEMORY;
+ m_cbLocalIL = ilCodeSize;
+ IfFailRet(GetProcess()->SafeReadBuffer(m_codeRegionInfo, m_pLocalIL, FALSE /*throwOnError*/));
+
+ // Check if this il code has exception clauses
+ if ((pMethodTinyHeader->Flags_CodeSize & CorILMethod_MoreSects) == 0)
+ {
+ return S_OK; // no EH, done initing
+ }
+
+ // EH section starts at the 4 byte aligned address after the code
+ CORDB_ADDRESS ehClauseHeader = ((pIlHeader + headerSize + ilCodeSize - 1) & ~3) + 4;
+ BYTE kind = 0;
+ IfFailRet(GetProcess()->SafeReadStruct(ehClauseHeader, &kind));
+ if ((kind & CorILMethod_Sect_KindMask) != CorILMethod_Sect_EHTable)
+ {
+ return S_OK;
+ }
+ if (kind & CorILMethod_Sect_FatFormat)
+ {
+ // Read the section header to see how many clauses there are
+ IMAGE_COR_ILMETHOD_SECT_FAT sectionHeader = { 0 };
+ IfFailRet(GetProcess()->SafeReadStruct(ehClauseHeader, &sectionHeader));
+ m_cClauses = (sectionHeader.DataSize - 4) / sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT);
+ if (m_cClauses > 10000) // sanity check the data before allocating
+ {
+ return CORDBG_E_TARGET_INCONSISTENT;
+ }
+
+ // Read in the clauses
+ TargetBuffer buffer(ehClauseHeader + sizeof(IMAGE_COR_ILMETHOD_SECT_FAT), m_cClauses*sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT));
+ NewArrayHolder<IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT> pClauses = new (nothrow)IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT[m_cClauses];
+ if (pClauses == NULL)
+ return E_OUTOFMEMORY;
+ IfFailRet(GetProcess()->SafeReadBuffer(buffer, (BYTE*)pClauses.GetValue(), FALSE /*throwOnError*/));
+
+ // convert clauses
+ m_pClauses = new (nothrow)CorDebugEHClause[m_cClauses];
+ if (m_pClauses == NULL)
+ return E_OUTOFMEMORY;
+ for (ULONG32 i = 0; i < m_cClauses; i++)
+ {
+ BOOL isFilter = ((pClauses[i].Flags & COR_ILEXCEPTION_CLAUSE_FILTER) != 0);
+ m_pClauses[i].Flags = pClauses[i].Flags;
+ m_pClauses[i].TryOffset = pClauses[i].TryOffset;
+ m_pClauses[i].TryLength = pClauses[i].TryLength;
+ m_pClauses[i].HandlerOffset = pClauses[i].HandlerOffset;
+ m_pClauses[i].HandlerLength = pClauses[i].HandlerLength;
+ // these two fields are a union in the image, but are seperate in the struct ICorDebug returns
+ m_pClauses[i].ClassToken = isFilter ? 0 : pClauses[i].ClassToken;
+ m_pClauses[i].FilterOffset = isFilter ? pClauses[i].FilterOffset : 0;
+ }
+ }
+ else
+ {
+ // Read in the section header to see how many small clauses there are
+ IMAGE_COR_ILMETHOD_SECT_SMALL sectionHeader = { 0 };
+ IfFailRet(GetProcess()->SafeReadStruct(ehClauseHeader, &sectionHeader));
+ ULONG32 m_cClauses = (sectionHeader.DataSize - 4) / sizeof(IMAGE_COR_ILMETHOD_SECT_SMALL);
+ if (m_cClauses > 10000) // sanity check the data before allocating
+ {
+ return CORDBG_E_TARGET_INCONSISTENT;
+ }
+
+ // Read in the clauses
+ TargetBuffer buffer(ehClauseHeader + sizeof(IMAGE_COR_ILMETHOD_SECT_SMALL), m_cClauses*sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_SMALL));
+ NewArrayHolder<IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_SMALL> pClauses = new (nothrow)IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_SMALL[m_cClauses];
+ if (pClauses == NULL)
+ return E_OUTOFMEMORY;
+ IfFailRet(GetProcess()->SafeReadBuffer(buffer, (BYTE*)pClauses.GetValue(), FALSE /*throwOnError*/));
+
+ // convert clauses
+ m_pClauses = new (nothrow)CorDebugEHClause[m_cClauses];
+ if (m_pClauses == NULL)
+ return E_OUTOFMEMORY;
+ for (ULONG32 i = 0; i < m_cClauses; i++)
+ {
+ BOOL isFilter = ((pClauses[i].Flags & COR_ILEXCEPTION_CLAUSE_FILTER) != 0);
+ m_pClauses[i].Flags = pClauses[i].Flags;
+ m_pClauses[i].TryOffset = pClauses[i].TryOffset;
+ m_pClauses[i].TryLength = pClauses[i].TryLength;
+ m_pClauses[i].HandlerOffset = pClauses[i].HandlerOffset;
+ m_pClauses[i].HandlerLength = pClauses[i].HandlerLength;
+ // these two fields are a union in the image, but are seperate in the struct ICorDebug returns
+ m_pClauses[i].ClassToken = isFilter ? 0 : pClauses[i].ClassToken;
+ m_pClauses[i].FilterOffset = isFilter ? pClauses[i].FilterOffset : 0;
+ }
+ }
+ return S_OK;
+}
+
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+//-----------------------------------------------------------------------------
+// CordbReJitILCode::GetEHClauses
+// Public method to get the EH clauses for IL code
+//
+// Parameters:
+// cClauses - size of incoming clauses array (in elements).
+// pcClauses - OUT param: cClauses>0 -> the number of elements written to in the clauses array.
+// cClauses=0 -> the number of EH clauses this IL code has
+// clauses - caller allocated storage to hold the EH clauses.
+//
+// Returns:
+// S_OK if successfully copied elements to clauses array.
+HRESULT CordbReJitILCode::GetEHClauses(ULONG32 cClauses, ULONG32 * pcClauses, CorDebugEHClause clauses[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcClauses, ULONG32 *);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(clauses, CorDebugEHClause *, cClauses, true, true);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (cClauses != 0 && clauses == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ if (pcClauses != NULL)
+ {
+ if (cClauses == 0)
+ {
+ *pcClauses = m_cClauses;
+ }
+ else
+ {
+ *pcClauses = MIN(cClauses, m_cClauses);
+ }
+ }
+
+ if (clauses != NULL)
+ {
+ memcpy_s(clauses, sizeof(CorDebugEHClause)*cClauses, m_pClauses, sizeof(CorDebugEHClause)*MIN(cClauses, m_cClauses));
+ }
+ return S_OK;
+}
+
+ULONG CordbReJitILCode::AddRef()
+{
+ return CordbCode::AddRef();
+}
+ULONG CordbReJitILCode::Release()
+{
+ return CordbCode::Release();
+}
+
+HRESULT CordbReJitILCode::QueryInterface(REFIID riid, void** ppInterface)
+{
+ if (riid == IID_ICorDebugILCode)
+ {
+ *ppInterface = static_cast<ICorDebugILCode*>(this);
+ }
+ else if (riid == IID_ICorDebugILCode2)
+ {
+ *ppInterface = static_cast<ICorDebugILCode2*>(this);
+ }
+ else
+ {
+ return CordbILCode::QueryInterface(riid, ppInterface);
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+HRESULT CordbReJitILCode::GetLocalVarSigToken(mdSignature *pmdSig)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pmdSig, mdSignature *);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ *pmdSig = m_localVarSigToken;
+ return S_OK;
+}
+
+HRESULT CordbReJitILCode::GetInstrumentedILMap(ULONG32 cMap, ULONG32 *pcMap, COR_IL_MAP map[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcClauses, ULONG32 *);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(map, COR_IL_MAP *, cMap, true, true);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (cMap != 0 && map == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ if (pcMap != NULL)
+ {
+ if (cMap == 0)
+ {
+ *pcMap = m_cILMap;
+ }
+ else
+ {
+ *pcMap = MIN(cMap, m_cILMap);
+ }
+ }
+
+ if (map != NULL)
+ {
+ memcpy_s(map, sizeof(COR_IL_MAP)*cMap, m_pILMap, sizeof(COR_IL_MAP)*MIN(cMap, m_cILMap));
+ }
+ return S_OK;
+}
+
+// FindNativeInfoInILVariableArray
+// Linear search through an array of NativeVarInfos, to find the variable of index dwIndex, valid
+// at the given ip. Returns CORDBG_E_IL_VAR_NOT_AVAILABLE if the variable isn't valid at the given ip.
+// Arguments:
+// input: dwIndex - variable number
+// ip - IP
+// nativeInfoList - list of instances of NativeVarInfo
+// output: ppNativeInfo - the element of nativeInfoList that corresponds to the IP and variable number
+// if we find such an element or NULL otherwise
+// Return value: HRESULT: returns S_OK or CORDBG_E_IL_VAR_NOT_AVAILABLE if the variable isn't found
+//
+HRESULT FindNativeInfoInILVariableArray(DWORD dwIndex,
+ SIZE_T ip,
+ const DacDbiArrayList<ICorDebugInfo::NativeVarInfo> * nativeInfoList,
+ const ICorDebugInfo::NativeVarInfo ** ppNativeInfo)
+{
+ _ASSERTE(ppNativeInfo != NULL);
+ *ppNativeInfo = NULL;
+
+ // A few words about this search: it must be linear, and the
+ // comparison of startOffset and endOffset to ip must be
+ // <=/>. startOffset points to the first instruction that will
+ // make the variable's home valid. endOffset points to the first
+ // instruction at which the variable's home invalid.
+ int lastGoodOne = -1;
+ for (unsigned int i = 0; i < (unsigned)nativeInfoList->Count(); i++)
+ {
+ if ((*nativeInfoList)[i].varNumber == dwIndex)
+ {
+ if ( (lastGoodOne == -1) ||
+ ((*nativeInfoList)[lastGoodOne].startOffset < (*nativeInfoList)[i].startOffset) )
+ {
+ lastGoodOne = i;
+ }
+
+ if (((*nativeInfoList)[i].startOffset <= ip) &&
+ ((*nativeInfoList)[i].endOffset > ip))
+ {
+ *ppNativeInfo = &((*nativeInfoList)[i]);
+
+ return S_OK;
+ }
+ }
+ }
+
+ // workaround:
+ //
+ // We didn't find the variable. Was the endOffset of the last range for this variable
+ // equal to the current IP? If so, go ahead and "lie" and report that as the
+ // variable's home for now.
+ //
+ // Rationale:
+ //
+ // * See TODO comment in code:Compiler::siUpdate (jit\scopeinfo.cpp). In optimized
+ // code, the JIT can report var lifetimes as being one instruction too short.
+ // This workaround makes up for that. Example code:
+ //
+ // static void foo(int x)
+ // {
+ // int b = x; // Value of "x" would not be reported in optimized code without the workaround
+ // bar(ref b);
+ // }
+ //
+ // * Since this is the first instruction after the last range a variable was alive,
+ // we're essentially assuming that since that instruction hasn't been executed
+ // yet, and since there isn't a new home for the variable, that the last home is
+ // still good. This actually turns out to be true 99.9% of the time, so we'll go
+ // with it for now.
+ // * We've been lying like this since 1999, so surely it's safe.
+ if ((lastGoodOne > -1) && ((*nativeInfoList)[lastGoodOne].endOffset == ip))
+ {
+ *ppNativeInfo = &((*nativeInfoList)[lastGoodOne]);
+ return S_OK;
+ }
+
+ return CORDBG_E_IL_VAR_NOT_AVAILABLE;
+} // FindNativeInfoInILVariableArray
+
+//* ------------------------------------------------------------------------- *
+// * Native Code class
+// * ------------------------------------------------------------------------- */
+
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode ctor to make Native code.
+// Arguments:
+// Input:
+// pFunction - the function for which this is the native code object
+// pJitData - the information about this code object retrieved from the DAC
+// fIsInstantiatedGeneric - indicates whether this code object is an instantiated
+// generic
+// Output:
+// fields of this instance of CordbNativeCode have been initialized
+//-----------------------------------------------------------------------------
+CordbNativeCode::CordbNativeCode(CordbFunction * pFunction,
+ const NativeCodeFunctionData * pJitData,
+ BOOL fIsInstantiatedGeneric)
+ : CordbCode(pFunction, (UINT_PTR)pJitData->m_rgCodeRegions[kHot].pAddress, pJitData->encVersion, FALSE),
+ m_vmNativeCodeMethodDescToken(pJitData->vmNativeCodeMethodDescToken),
+ m_fCodeAvailable(TRUE),
+ m_fIsInstantiatedGeneric(fIsInstantiatedGeneric != FALSE)
+{
+ _ASSERTE(GetVersion() >= CorDB_DEFAULT_ENC_FUNCTION_VERSION);
+
+ for (CodeBlobRegion region = kHot; region < MAX_REGIONS; ++region)
+ {
+ m_rgCodeRegions[region] = pJitData->m_rgCodeRegions[region];
+ }
+} //CordbNativeCode::CordbNativeCode
+
+//-----------------------------------------------------------------------------
+// Public method for IUnknown::QueryInterface.
+// Has standard QI semantics.
+//-----------------------------------------------------------------------------
+HRESULT CordbNativeCode::QueryInterface(REFIID id, void ** pInterface)
+{
+ if (id == IID_ICorDebugCode)
+ {
+ *pInterface = static_cast<ICorDebugCode *>(this);
+ }
+ else if (id == IID_ICorDebugCode2)
+ {
+ *pInterface = static_cast<ICorDebugCode2 *>(this);
+ }
+ else if (id == IID_ICorDebugCode3)
+ {
+ *pInterface = static_cast<ICorDebugCode3 *>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugCode *>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode::GetAddress
+// Public method to get the Entry address for the code. This is the address
+// where the method first starts executing.
+//
+// Parameters:
+// pStart - out-parameter to hold start address.
+//
+// Returns:
+// S_OK if *pStart is properly updated.
+//-----------------------------------------------------------------------------
+HRESULT CordbNativeCode::GetAddress(CORDB_ADDRESS * pStart)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pStart, CORDB_ADDRESS *);
+
+
+ _ASSERTE(this != NULL);
+ _ASSERTE(this->GetFunction() != NULL);
+ _ASSERTE(this->GetFunction()->GetModule() != NULL);
+ _ASSERTE(this->GetFunction()->GetModule()->GetProcess() == GetProcess());
+
+ // Since we don't do code-pitching, the address points directly to the code.
+ *pStart = (m_rgCodeRegions[kHot].pAddress);
+
+ if (*pStart == NULL)
+ {
+ return CORDBG_E_CODE_NOT_AVAILABLE;
+ }
+ return S_OK;
+} // CordbNativeCode::GetAddress
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode::ReadCodeBytes
+// Reads the actual bytes of native code from both the hot and cold regions
+// into the data member m_rgbCode
+// Arguments:
+// none (uses data members)
+// Return value:
+// standard HRESULT values
+// also allocates and initializes m_rgbCode
+// Notes: assumes that the caller has checked to ensure that m_rgbCode doesn't
+// hold valid data
+//-----------------------------------------------------------------------------
+HRESULT CordbNativeCode::ReadCodeBytes()
+{
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ // We have an address & size, so we'll just call ReadMemory.
+ // This will conveniently strip out any patches too.
+ CORDB_ADDRESS pHotStart = m_rgCodeRegions[kHot].pAddress;
+ CORDB_ADDRESS pColdStart = m_rgCodeRegions[kCold].pAddress;
+ ULONG32 cbHotSize = (ULONG32) m_rgCodeRegions[kHot].cbSize;
+ ULONG32 cbColdSize = GetColdSize();
+
+ delete [] m_rgbCode;
+ m_rgbCode = new BYTE[cbHotSize + cbColdSize];
+
+ SIZE_T cbRead;
+ hr = GetProcess()->ReadMemory(pHotStart, cbHotSize, m_rgbCode, &cbRead);
+ IfFailThrow(hr);
+
+ SIMPLIFYING_ASSUMPTION(cbRead == cbHotSize);
+
+ if (HasColdRegion())
+ {
+ hr = GetProcess()->ReadMemory(pColdStart, cbColdSize, (BYTE *) m_rgbCode + cbHotSize, &cbRead);
+ IfFailThrow(hr);
+
+ SIMPLIFYING_ASSUMPTION(cbRead == cbColdSize);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+
+} // CordbNativeCode::ReadCodeBytes
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode::GetColdSize
+// Get the size of the cold regions in bytes.
+//
+// Parameters:
+// none--uses data member m_rgCodeRegions to compute total size.
+//
+// Returns:
+// the size of the code in bytes.
+//-----------------------------------------------------------------------------
+ULONG32 CordbNativeCode::GetColdSize()
+{
+ ULONG32 pcBytes = 0;
+ for (CodeBlobRegion index = kCold; index < MAX_REGIONS; ++index)
+ {
+ pcBytes += m_rgCodeRegions[index].cbSize;
+ }
+ return pcBytes;
+} // CordbNativeCode::GetColdSize
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode::GetSize
+// Get the size of the code in bytes.
+//
+// Parameters:
+// none--uses data member m_rgCodeRegions to compute total size.
+//
+// Returns:
+// the size of the code in bytes.
+//-----------------------------------------------------------------------------
+ULONG32 CordbNativeCode::GetSize()
+{
+ ULONG32 pcBytes = 0;
+ for (CodeBlobRegion index = kHot; index < MAX_REGIONS; ++index)
+ {
+ pcBytes += m_rgCodeRegions[index].cbSize;
+ }
+ return pcBytes;
+} // CordbNativeCode::GetSize
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode::GetILToNativeMapping
+// Public method (implements ICorDebugCode) to get the IL-->{ Native Start, Native End} mapping.
+// This can only be retrieved for native code.
+// This will copy as much of the map as can fit in the incoming buffer.
+//
+// Parameters:
+// cMap - size of incoming map[] array (in elements).
+// pcMap - OUT: full size of IL-->Native map (in elements).
+// map - caller allocated array to be filled in.
+//
+// Returns:
+// S_OK on successful copying.
+//-----------------------------------------------------------------------------
+HRESULT CordbNativeCode::GetILToNativeMapping(ULONG32 cMap,
+ ULONG32 * pcMap,
+ COR_DEBUG_IL_TO_NATIVE_MAP map[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcMap, ULONG32 *);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(map, COR_DEBUG_IL_TO_NATIVE_MAP *,cMap,true,true);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ LoadNativeInfo();
+
+ SequencePoints * pSeqPts = GetSequencePoints();
+ DebuggerILToNativeMap * rgMapInt = pSeqPts->GetMapAddr();
+ ULONG32 cMapIntCount = pSeqPts->GetEntryCount();
+
+ // If they gave us space to copy into...
+ if (map != NULL)
+ {
+ // Only copy as much as either they gave us or we have to copy.
+ ULONG32 cMapToCopy = min(cMap, cMapIntCount);
+
+ // Remember that we need to translate between our internal DebuggerILToNativeMap and the external
+ // COR_DEBUG_IL_TO_NATIVE_MAP!
+ ULONG32 size = GetSize();
+ ExportILToNativeMap(cMapToCopy, map, rgMapInt, size);
+ }
+
+ // return the full count of map entries
+ if (pcMap)
+ {
+ *pcMap = cMapIntCount;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbNativeCode::GetILToNativeMapping
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode::GetCodeChunks
+// Public method to get the code regions of code. If the code
+// is broken into discontinuous regions (hot + cold), this lets a debugger
+// find the number of regions, and (start,size) of each.
+//
+// Parameters:
+// cbufSize - size of incoming chunks array (in elements).
+// pcnumChunks - OUT param: the number of elements written to in the chunk array.//
+// chunks - caller allocated storage to hold the code chunks.
+//
+// Returns:
+// S_OK if successfully copied elements to Chunk array.
+//-----------------------------------------------------------------------------
+HRESULT CordbNativeCode::GetCodeChunks(
+ ULONG32 cbufSize,
+ ULONG32 * pcnumChunks,
+ CodeChunkInfo chunks[]
+)
+{
+ PUBLIC_API_ENTRY(this);
+
+ if (pcnumChunks == NULL)
+ {
+ return E_INVALIDARG;
+ }
+ if ((chunks == NULL) != (cbufSize == 0))
+ {
+ return E_INVALIDARG;
+ }
+
+ // Current V2.0 implementation has at most 2 possible chunks right now (1 hot, and 1 cold).
+ ULONG32 cActualChunks = HasColdRegion() ? 2 : 1;
+
+ // If no buf size, then we're querying the total number of chunks.
+ if (cbufSize == 0)
+ {
+ *pcnumChunks = cActualChunks;
+ return S_OK;
+ }
+
+ // Else give them as many as they asked for.
+ for (CodeBlobRegion index = kHot; (index < MAX_REGIONS) && ((int)cbufSize > index); ++index)
+ {
+ // Fill in the region information
+ chunks[index].startAddr = m_rgCodeRegions[index].pAddress;
+ chunks[index].length = (ULONG32) (m_rgCodeRegions[index].cbSize);
+ *pcnumChunks = cbufSize;
+ }
+
+ return S_OK;
+} // CordbNativeCode::GetCodeChunks
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode::GetCompilerFlags
+// Public entry point to get code flags for this Code object.
+// Originally, ICDCode had this method implemented independently from the
+// ICDModule method GetJitCompilerFlags. This was because it was considered that
+// the flags would be per function, rather than per module.
+// In addition, GetCompilerFlags did two different things depending on whether
+// the code had a native image. It turned out that was the wrong thing to do
+// .
+//
+// Parameters:
+// pdwFlags - OUT: code gen flags (see CorDebugJITCompilerFlags)
+//
+// Return value:
+// S_OK if pdwFlags is set properly.
+//-----------------------------------------------------------------------------
+HRESULT CordbNativeCode::GetCompilerFlags(DWORD * pdwFlags)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pdwFlags, DWORD *);
+ *pdwFlags = 0;
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return GetFunction()->GetModule()->GetJITCompilerFlags(pdwFlags);
+
+} // CordbNativeCode::GetCompilerFlags
+
+//-----------------------------------------------------------------------------
+// Given an IL local variable number and a native IP offset, return the
+// location of the variable in jitted code.
+//-----------------------------------------------------------------------------
+HRESULT CordbNativeCode::ILVariableToNative(DWORD dwIndex,
+ SIZE_T ip,
+ const ICorDebugInfo::NativeVarInfo ** ppNativeInfo)
+{
+ _ASSERTE(m_nativeVarData.IsInitialized());
+
+ return FindNativeInfoInILVariableArray(dwIndex,
+ ip,
+ m_nativeVarData.GetOffsetInfoList(),
+ ppNativeInfo);
+} // CordbNativeCode::ILVariableToNative
+
+
+HRESULT CordbNativeCode::GetReturnValueLiveOffset(ULONG32 ILoffset, ULONG32 bufferSize, ULONG32 *pFetched, ULONG32 *pOffsets)
+{
+ HRESULT hr = S_OK;
+
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ VALIDATE_POINTER_TO_OBJECT(pFetched, ULONG32 *);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ EX_TRY
+ {
+ hr = GetReturnValueLiveOffsetImpl(NULL, ILoffset, bufferSize, pFetched, pOffsets);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+int CordbNativeCode::GetCallInstructionLength(BYTE *ip, ULONG32 count)
+{
+#if defined(DBG_TARGET_ARM)
+ return MAX_INSTRUCTION_LENGTH;
+#elif defined(DBG_TARGET_ARM64)
+ return MAX_INSTRUCTION_LENGTH;
+#elif defined(DBG_TARGET_X86)
+ if (count < 2)
+ return -1;
+
+ // Skip instruction prefixes
+ do
+ {
+ switch (*ip)
+ {
+ // Segment overrides
+ case 0x26: // ES
+ case 0x2E: // CS
+ case 0x36: // SS
+ case 0x3E: // DS
+ case 0x64: // FS
+ case 0x65: // GS
+
+ // Size overrides
+ case 0x66: // Operand-Size
+ case 0x67: // Address-Size
+
+ // Lock
+ case 0xf0:
+
+ // String REP prefixes
+ case 0xf1:
+ case 0xf2: // REPNE/REPNZ
+ case 0xf3:
+ ip++;
+ count--;
+ continue;
+
+ default:
+ break;
+ }
+ } while (0);
+
+ // Read the opcode
+ BYTE opcode = *ip++;
+ if (opcode == 0xcc)
+ {
+ // todo: Can we actually get this result? Doesn't ICorDebug hand out un-patched assembly?
+ _ASSERTE(!"Hit break opcode!");
+ return -1;
+ }
+
+ // Analyze what we can of the opcode
+ switch (opcode)
+ {
+ case 0xff:
+ {
+ // Count may have been decremented by prefixes.
+ if (count < 2)
+ return -1;
+
+ BYTE modrm = *ip++;
+ BYTE mod = (modrm & 0xC0) >> 6;
+ BYTE reg = (modrm & 0x38) >> 3;
+ BYTE rm = (modrm & 0x07);
+
+ int displace = -1;
+
+ if ((reg != 2) && (reg != 3) && (reg != 4) && (reg != 5))
+ {
+ //
+ // This is not a CALL or JMP instruction, return, unknown.
+ //
+ _ASSERTE(!"Unhandled opcode!");
+ return -1;
+ }
+
+
+ // Only try to decode registers if we actually have reg sets.
+ switch (mod)
+ {
+ case 0:
+ case 1:
+ case 2:
+
+ if (rm == 4)
+ {
+ if (count < 3)
+ return -1;
+
+ //
+ // Get values from the SIB byte
+ //
+ BYTE ss = (*ip & 0xC0) >> 6;
+ BYTE index = (*ip & 0x38) >> 3;
+ BYTE base = (*ip & 0x7);
+
+ //
+ // Finally add in the offset
+ //
+ if (mod == 0)
+ {
+ if (base == 5)
+ displace = 7;
+ else
+ displace = 3;
+ }
+ else if (mod == 1)
+ {
+ displace = 4;
+ }
+ else
+ {
+ displace = 7;
+ }
+ }
+ else
+ {
+ if (mod == 0)
+ {
+ if (rm == 5)
+ displace = 6;
+ else
+ displace = 2;
+ }
+ else if (mod == 1)
+ {
+ displace = 3;
+ }
+ else
+ {
+ displace = 6;
+ }
+ }
+ break;
+
+ case 3:
+ default:
+ displace = 2;
+ break;
+ }
+
+ return displace;
+ } // end of 0xFF case
+
+ case 0xe8:
+ return 5;
+
+
+ default:
+ break;
+ }
+
+
+ _ASSERTE(!"Unhandled opcode!");
+ return -1;
+
+#elif defined(DBG_TARGET_AMD64)
+ BYTE rex = NULL;
+ BYTE prefix = *ip;
+ BOOL fContainsPrefix = FALSE;
+
+ // Should not happen.
+ if (prefix == 0xcc)
+ return -1;
+
+ // Skip instruction prefixes
+ //@TODO by euzem:
+ //This "loop" can't be really executed more than once so if CALL can really have more than one prefix we'll crash.
+ //Some of these prefixes are not allowed for CALL instruction and we should treat them as invalid code.
+ //It appears that this code was mostly copy/pasted from \NDP\clr\src\Debug\EE\amd64\amd64walker.cpp
+ //with very minimum fixes.
+ do
+ {
+ switch (prefix)
+ {
+ // Segment overrides
+ case 0x26: // ES
+ case 0x2E: // CS
+ case 0x36: // SS
+ case 0x3E: // DS
+ case 0x64: // FS
+ case 0x65: // GS
+
+ // Size overrides
+ case 0x66: // Operand-Size
+ case 0x67: // Address-Size
+
+ // Lock
+ case 0xf0:
+
+ // String REP prefixes
+ case 0xf2: // REPNE/REPNZ
+ case 0xf3:
+ ip++;
+ fContainsPrefix = TRUE;
+ continue;
+
+ // REX register extension prefixes
+ case 0x40:
+ case 0x41:
+ case 0x42:
+ case 0x43:
+ case 0x44:
+ case 0x45:
+ case 0x46:
+ case 0x47:
+ case 0x48:
+ case 0x49:
+ case 0x4a:
+ case 0x4b:
+ case 0x4c:
+ case 0x4d:
+ case 0x4e:
+ case 0x4f:
+ // make sure to set rex to prefix, not *ip because *ip still represents the
+ // codestream which has a 0xcc in it.
+ rex = prefix;
+ ip++;
+ fContainsPrefix = TRUE;
+ continue;
+
+ default:
+ break;
+ }
+ } while (0);
+
+ // Read the opcode
+ BYTE opcode = *ip++;
+
+ // Should not happen.
+ if (opcode == 0xcc)
+ return -1;
+
+
+ // Setup rex bits if needed
+ BYTE rex_b = 0;
+ BYTE rex_x = 0;
+ BYTE rex_r = 0;
+
+ if (rex != NULL)
+ {
+ rex_b = (rex & 0x1); // high bit to modrm r/m field or SIB base field or OPCODE reg field -- Hmm, when which?
+ rex_x = (rex & 0x2) >> 1; // high bit to sib index field
+ rex_r = (rex & 0x4) >> 2; // high bit to modrm reg field
+ }
+
+ // Analyze what we can of the opcode
+ switch (opcode)
+ {
+ case 0xff:
+ {
+ BYTE modrm = *ip++;
+
+ _ASSERT(modrm != NULL);
+
+ BYTE mod = (modrm & 0xC0) >> 6;
+ BYTE reg = (modrm & 0x38) >> 3;
+ BYTE rm = (modrm & 0x07);
+
+ reg |= (rex_r << 3);
+ rm |= (rex_b << 3);
+
+ if ((reg < 2) || (reg > 5 && reg < 8) || (reg > 15)) {
+ // not a valid register for a CALL or BRANCH
+ _ASSERTE(!"Invalid opcode!");
+ return -1;
+ }
+
+ BYTE *result;
+ WORD displace = -1;
+
+ // See: Tables A-15,16,17 in AMD Dev Manual 3 for information
+ // about how the ModRM/SIB/REX bytes interact.
+
+ switch (mod)
+ {
+ case 0:
+ case 1:
+ case 2:
+ if ((rm & 0x07) == 4) // we have an SIB byte following
+ {
+ //
+ // Get values from the SIB byte
+ //
+ BYTE sib = *ip;
+ _ASSERT(sib != NULL);
+
+ BYTE base = (sib & 0x07);
+ base |= (rex_b << 3);
+
+ ip++;
+
+ //
+ // Finally add in the offset
+ //
+ if (mod == 0)
+ {
+ if ((base & 0x07) == 5)
+ displace = 7;
+ else
+ displace = 3;
+ }
+ else if (mod == 1)
+ {
+ displace = 4;
+ }
+ else // mod == 2
+ {
+ displace = 7;
+ }
+ }
+ else
+ {
+ //
+ // Get the value we need from the register.
+ //
+
+ // Check for RIP-relative addressing mode.
+ if ((mod == 0) && ((rm & 0x07) == 5))
+ {
+ displace = 6; // 1 byte opcode + 1 byte modrm + 4 byte displacement (signed)
+ }
+ else
+ {
+ if (mod == 0)
+ displace = 2;
+ else if (mod == 1)
+ displace = 3;
+ else // mod == 2
+ displace = 6;
+ }
+ }
+
+ break;
+
+ case 3:
+ default:
+ displace = 2;
+ }
+
+ // Displace should be set by one of the cases above
+ if (displace == -1)
+ {
+ _ASSERTE(!"GetCallInstructionLength() encountered unexpected call instruction");
+ return -1;
+ }
+
+ // Account for the 1 byte prefix (REX or otherwise)
+ if (fContainsPrefix)
+ displace++;
+
+ // reg == 4 or 5 means that it is not a CALL, but JMP instruction
+ // so we will fall back to ASSERT after break
+ if ((reg != 4) && (reg != 5))
+ return displace;
+ break;
+ }
+ case 0xe8:
+ {
+ //Near call with the target specified by a 32-bit relative displacement.
+ //[maybe 1 byte prefix] + [1 byte opcode E8h] + [4 bytes offset]
+ return 5 + (fContainsPrefix ? 1 : 0);
+ }
+ default:
+ break;
+ }
+
+ _ASSERTE(!"Invalid opcode!");
+ return -1;
+#else
+#error Platform not implemented
+#endif
+}
+
+HRESULT CordbNativeCode::GetSigParserFromFunction(mdToken mdFunction, mdToken *pClass, SigParser &parser, SigParser &methodGenerics)
+{
+ // mdFunction may be a MemberRef, a MethodDef, or a MethodSpec. We must handle all three cases.
+ HRESULT hr = S_OK;
+ IMetaDataImport* pImport = m_pFunction->GetModule()->GetMetaDataImporter();
+ RSExtSmartPtr<IMetaDataImport2> pImport2;
+ IfFailRet(pImport->QueryInterface(IID_IMetaDataImport2, (void**)&pImport2));
+
+ if (TypeFromToken(mdFunction) == mdtMemberRef)
+ {
+ PCCOR_SIGNATURE sig = 0;
+ ULONG sigSize = 0;
+ IfFailRet(pImport->GetMemberRefProps(mdFunction, pClass, NULL, 0, 0, &sig, &sigSize));
+ parser = SigParser(sig, sigSize);
+ }
+ else if (TypeFromToken(mdFunction) == mdtMethodDef)
+ {
+ PCCOR_SIGNATURE sig = 0;
+ ULONG sigSize = 0;
+ IfFailRet(pImport->GetMethodProps(mdFunction, pClass, NULL, 0, NULL, NULL, &sig, &sigSize, NULL, NULL));
+ parser = SigParser(sig, sigSize);
+ }
+ else if (TypeFromToken(mdFunction) == mdtMethodSpec)
+ {
+ // For a method spec, we use GetMethodSpecProps to get the generic singature and the parent token
+ // (which is a MethodDef token). We'll recurse to get the other properties from the parent token.
+
+ PCCOR_SIGNATURE sig = 0;
+ ULONG sigSize = 0;
+ mdToken parentToken = 0;
+ IfFailRet(pImport2->GetMethodSpecProps(mdFunction, &parentToken, &sig, &sigSize));
+ methodGenerics = SigParser(sig, sigSize);
+
+ if (pClass)
+ *pClass = parentToken;
+
+ return GetSigParserFromFunction(parentToken, pClass, parser, methodGenerics);
+ }
+ else
+ {
+ // According to ECMA III.3.19, this can never happen.
+ return E_UNEXPECTED;
+ }
+
+ return S_OK;
+}
+
+HRESULT CordbNativeCode::EnsureReturnValueAllowed(Instantiation *currentInstantiation, mdToken targetClass, SigParser &parser, SigParser &methodGenerics)
+{
+ HRESULT hr = S_OK;
+ ULONG genCount = 0;
+ IfFailRet(SkipToReturn(parser, &genCount));
+
+ return EnsureReturnValueAllowedWorker(currentInstantiation, targetClass, parser, methodGenerics, genCount);
+}
+
+HRESULT CordbNativeCode::EnsureReturnValueAllowedWorker(Instantiation *currentInstantiation, mdToken targetClass, SigParser &parser, SigParser &methodGenerics, ULONG genCount)
+{
+ // There are a few considerations here:
+ // 1. Generic instantiations. This is a "Foo<T>", and we need to check if that "Foo"
+ // fits one of the categories we disallow (such as a struct).
+ // 2. Void return.
+ // 3. ValueType - Unsupported this release.
+ // 4. MVAR - Method generics. We need to get the actual generic type and recursively
+ // check if we allow that.
+ // 5. VAR - Class generics. We need to get the actual generic type and recurse.
+
+ SigParser original(parser);
+ HRESULT hr = S_OK;
+ CorElementType returnType;
+ IfFailRet(parser.GetElemType(&returnType));
+ if (returnType == ELEMENT_TYPE_GENERICINST)
+ {
+ IfFailRet(parser.GetElemType(&returnType));
+
+ if (returnType == ELEMENT_TYPE_CLASS)
+ return S_OK;
+
+ if (returnType != ELEMENT_TYPE_VALUETYPE)
+ return META_E_BAD_SIGNATURE;
+
+ if (currentInstantiation == NULL)
+ return S_OK; // We will check again when we have the instantiation.
+
+ NewArrayHolder<CordbType*> types;
+ Instantiation inst;
+ IfFailRet(CordbJITILFrame::BuildInstantiationForCallsite(GetModule(), types, inst, currentInstantiation, targetClass, SigParser(methodGenerics)));
+
+ CordbType *pType = 0;
+ IfFailRet(CordbType::SigToType(GetModule(), &original, &inst, &pType));
+
+
+ IfFailRet(hr = pType->ReturnedByValue());
+ if (hr == S_OK) // not S_FALSE
+ return S_OK;
+
+ return CORDBG_E_UNSUPPORTED;
+ }
+
+ if (returnType == ELEMENT_TYPE_VALUETYPE)
+ {
+ Instantiation inst;
+ CordbType *pType = 0;
+ IfFailRet(CordbType::SigToType(GetModule(), &original, &inst, &pType));
+
+ IfFailRet(hr = pType->ReturnedByValue());
+ if (hr == S_OK) // not S_FALSE
+ return S_OK;
+
+ return CORDBG_E_UNSUPPORTED;
+ }
+
+ if (returnType == ELEMENT_TYPE_TYPEDBYREF)
+ return CORDBG_E_UNSUPPORTED;
+
+ if (returnType == ELEMENT_TYPE_VOID)
+ return E_UNEXPECTED;
+
+ if (returnType == ELEMENT_TYPE_MVAR)
+ {
+ // Get which generic parameter is referenced.
+ ULONG genParam = 0;
+ IfFailRet(parser.GetData(&genParam));
+
+ // Grab the calling convention of the method, ensure it's GENERICINST.
+ ULONG callingConv = 0;
+ IfFailRet(methodGenerics.GetCallingConvInfo(&callingConv));
+ if (callingConv != IMAGE_CEE_CS_CALLCONV_GENERICINST)
+ return META_E_BAD_SIGNATURE;
+
+ // Ensure sensible bounds.
+ SigParser generics(methodGenerics); // Make a copy since operations are destructive.
+ ULONG maxCount = 0;
+ IfFailRet(generics.GetData(&maxCount));
+ if (maxCount <= genParam || genParam > 1024)
+ return META_E_BAD_SIGNATURE;
+
+ // Walk to the parameter referenced.
+ while (genParam--)
+ IfFailRet(generics.SkipExactlyOne());
+
+ // Now recurse with "generics" at the location to continue parsing.
+ return EnsureReturnValueAllowedWorker(currentInstantiation, targetClass, generics, methodGenerics, genCount);
+ }
+
+
+ if (returnType == ELEMENT_TYPE_VAR)
+ {
+ // Get which type parameter is reference.
+ ULONG typeParam = 0;
+ parser.GetData(&typeParam);
+
+ // Ensure something reasonable.
+ if (typeParam > 1024)
+ return META_E_BAD_SIGNATURE;
+
+ // Lookup the containing class's signature so we can get the referenced generic parameter.
+ IMetaDataImport *pImport = m_pFunction->GetModule()->GetMetaDataImporter();
+ PCCOR_SIGNATURE sig;
+ ULONG countSig;
+ IfFailRet(pImport->GetTypeSpecFromToken(targetClass, &sig, &countSig));
+
+ // Enusre the type's typespec is GENERICINST.
+ SigParser typeParser(sig, countSig);
+ CorElementType et;
+ IfFailRet(typeParser.GetElemType(&et));
+ if (et != ELEMENT_TYPE_GENERICINST)
+ return META_E_BAD_SIGNATURE;
+
+ // Move to the correct location.
+ IfFailRet(typeParser.GetElemType(&et));
+ if (et != ELEMENT_TYPE_VALUETYPE && et != ELEMENT_TYPE_CLASS)
+ return META_E_BAD_SIGNATURE;
+
+ IfFailRet(typeParser.GetToken(NULL));
+
+ ULONG totalTypeCount = 0;
+ IfFailRet(typeParser.GetData(&totalTypeCount));
+ if (totalTypeCount < typeParam)
+ return META_E_BAD_SIGNATURE;
+
+ while (typeParam--)
+ IfFailRet(typeParser.SkipExactlyOne());
+
+ // This is a temporary workaround for an infinite recursion here. ALL of this code will
+ // go away when we allow struct return values, but in the mean time this avoids a corner
+ // case in the type system we haven't solved yet.
+ IfFailRet(typeParser.PeekElemType(&et));
+ if (et == ELEMENT_TYPE_VAR)
+ return E_FAIL;
+
+ // Now that typeParser is at the location of the correct generic parameter, recurse.
+ return EnsureReturnValueAllowedWorker(currentInstantiation, targetClass, typeParser, methodGenerics, genCount);
+ }
+
+ // Everything else supported
+ return S_OK;
+}
+
+HRESULT CordbNativeCode::SkipToReturn(SigParser &parser, ULONG *genCount)
+{
+ // Takes a method signature parser (at the beginning of a signature) and skips to the
+ // return value.
+ HRESULT hr = S_OK;
+
+ // Skip calling convention
+ ULONG uCallConv;
+ IfFailRet(parser.GetCallingConvInfo(&uCallConv));
+ if ((uCallConv == IMAGE_CEE_CS_CALLCONV_FIELD) || (uCallConv == IMAGE_CEE_CS_CALLCONV_LOCAL_SIG))
+ return META_E_BAD_SIGNATURE;
+
+ // Skip type parameter count if function is generic
+ if (uCallConv & IMAGE_CEE_CS_CALLCONV_GENERIC)
+ IfFailRet(parser.GetData(genCount));
+
+ // Skip argument count
+ IfFailRet(parser.GetData(NULL));
+
+ return S_OK;
+}
+
+HRESULT CordbNativeCode::GetCallSignature(ULONG32 ILoffset, mdToken *pClass, mdToken *pFunction, SigParser &parser, SigParser &generics)
+{
+ // check if specified IL offset is at a call instruction
+ CordbILCode *pCode = this->m_pFunction->GetILCode();
+ BYTE buffer[3];
+ ULONG32 fetched = 0;
+ HRESULT hr = pCode->GetCode(ILoffset, ILoffset+_countof(buffer), _countof(buffer), buffer, &fetched);
+
+ if (FAILED(hr))
+ return hr;
+ else if (fetched != _countof(buffer))
+ return CORDBG_E_INVALID_OPCODE;
+
+ // tail. - fe 14 (ECMA III.2.4)
+ BYTE instruction = buffer[0];
+ if (buffer[0] == 0xfe && buffer[1] == 0x14)
+ {
+ // tail call case. We don't allow managed return values for tailcalls.
+ return CORDBG_E_INVALID_OPCODE;
+ }
+
+ // call - 28 (ECMA III.3.19)
+ // callvirt - 6f (ECMA III.4.2)
+ if (instruction != 0x28 && instruction != 0x6f)
+ return CORDBG_E_INVALID_OPCODE;
+
+ // Now grab the MD token of the call
+ mdToken mdFunction = 0;
+ const ULONG32 offset = ILoffset + 1;
+ hr = pCode->GetCode(offset, offset+sizeof(mdToken), sizeof(mdToken), (BYTE*)&mdFunction, &fetched);
+ if (FAILED(hr) || fetched != sizeof(mdToken))
+ return CORDBG_E_INVALID_OPCODE;
+
+ if (pFunction)
+ *pFunction = mdFunction;
+
+ // Convert to a signature parser
+ return GetSigParserFromFunction(mdFunction, pClass, parser, generics);
+}
+
+HRESULT CordbNativeCode::GetReturnValueLiveOffsetImpl(Instantiation *currentInstantiation, ULONG32 ILoffset, ULONG32 bufferSize, ULONG32 *pFetched, ULONG32 *pOffsets)
+{
+ if (pFetched == NULL)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+ ULONG32 found = 0;
+
+ // verify that the call target actually returns something we allow
+ SigParser signature, generics;
+ mdToken mdClass = 0;
+ IfFailRet(GetCallSignature(ILoffset, &mdClass, NULL, signature, generics));
+ IfFailRet(EnsureReturnValueAllowed(currentInstantiation, mdClass, signature, generics));
+
+ // now find the native offset
+ SequencePoints *pSP = GetSequencePoints();
+ DebuggerILToNativeMap *pMap = pSP->GetCallsiteMapAddr();
+
+ for (ULONG32 i = 0; i < pSP->GetCallsiteEntryCount() && pMap; ++i, pMap++)
+ {
+ if (pMap->ilOffset == ILoffset && (pMap->source & ICorDebugInfo::CALL_INSTRUCTION) == ICorDebugInfo::CALL_INSTRUCTION)
+ {
+ // if we have a buffer, fill it in.
+ if (pOffsets && found < bufferSize)
+ {
+ // Fetch the actual assembly instructions
+ BYTE nativeBuffer[8];
+
+ ULONG32 fetched = 0;
+ IfFailRet(GetCode(pMap->nativeStartOffset, pMap->nativeStartOffset+_countof(nativeBuffer), _countof(nativeBuffer), nativeBuffer, &fetched));
+
+ int skipBytes = 0;
+
+#if defined(DBG_TARGET_X86) && defined(FEATURE_CORESYSTEM)
+ // Skip nop sleds on x86 coresystem. The JIT adds these instructions as a security measure,
+ // and incorrectly reports to us the wrong offset of the call instruction.
+ const BYTE nop_opcode = 0x90;
+ while (fetched && nativeBuffer[0] == nop_opcode)
+ {
+ skipBytes++;
+
+ for (int j = 1; j < _countof(nativeBuffer) && nativeBuffer[j] == nop_opcode; ++j)
+ skipBytes++;
+
+ // We must have at least one skip byte since the outer while ensures it. Thus we always need to reread
+ // the buffer at the end of this loop.
+ IfFailRet(GetCode(pMap->nativeStartOffset+skipBytes, pMap->nativeStartOffset+skipBytes+_countof(nativeBuffer), _countof(nativeBuffer), nativeBuffer, &fetched));
+ }
+#endif
+
+ // Get the length of the call instruction.
+ int offset = GetCallInstructionLength(nativeBuffer, fetched);
+ if (offset == -1)
+ return E_UNEXPECTED; // Could not decode instruction, this should never happen.
+
+ pOffsets[found] = pMap->nativeStartOffset + offset + skipBytes;
+ }
+
+ found++;
+ }
+ }
+
+ if (pOffsets)
+ *pFetched = found < bufferSize ? found : bufferSize;
+ else
+ *pFetched = found;
+
+ if (found == 0)
+ return E_FAIL;
+
+ if (pOffsets && found > bufferSize)
+ return S_FALSE;
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Creates a CordbNativeCode (if it's not already created) and adds it to the
+// hash table of CordbNativeCode instances belonging to this module.
+// Used by CordbFunction::InitNativeCodeInfo.
+//
+// Arguments:
+// Input:
+// methodToken - the methodDef token of the function this native code belongs to
+// methodDesc - the methodDesc for the jitted method
+// startAddress - the hot code startAddress for this method
+
+// Return value:
+// found or created CordbNativeCode pointer
+// Assumptions: methodToken is in the metadata for this module
+// methodDesc and startAddress should be consistent for
+// a jitted instance of methodToken's method
+//-----------------------------------------------------------------------------
+CordbNativeCode * CordbModule::LookupOrCreateNativeCode(mdMethodDef methodToken,
+ VMPTR_MethodDesc methodDesc,
+ CORDB_ADDRESS startAddress)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+ _ASSERTE(startAddress != NULL);
+ _ASSERTE(methodDesc != VMPTR_MethodDesc::NullPtr());
+
+ CordbNativeCode * pNativeCode = NULL;
+ NativeCodeFunctionData codeInfo;
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ // see if we already have this--if not, we'll make an instance, otherwise we'll just return the one we have.
+ pNativeCode = m_nativeCodeTable.GetBase((UINT_PTR) startAddress);
+
+ if (pNativeCode == NULL)
+ {
+ GetProcess()->GetDAC()->GetNativeCodeInfoForAddr(methodDesc, startAddress, &codeInfo);
+
+ // We didn't have an instance, so we'll build one and add it to the hash table
+ LOG((LF_CORDB,
+ LL_INFO10000,
+ "R:CT::RSCreating code w/ ver:0x%x, md:0x%x, nativeStart=0x%08x, nativeSize=0x%08x\n",
+ codeInfo.encVersion,
+ VmPtrToCookie(codeInfo.vmNativeCodeMethodDescToken),
+ codeInfo.m_rgCodeRegions[kHot].pAddress,
+ codeInfo.m_rgCodeRegions[kHot].cbSize));
+
+ // Lookup the function object that this code should be bound to
+ CordbFunction* pFunction = CordbModule::LookupOrCreateFunction(methodToken, codeInfo.encVersion);
+ _ASSERTE(pFunction != NULL);
+
+ // There are bugs with the on-demand class load performed by CordbFunction in some cases. The old stack
+ // tracing code avoided them by eagerly loading the parent class so I am following suit
+ pFunction->InitParentClassOfFunction();
+
+ // First, create a new CordbNativeCode instance--we'll need this to make the CordbJITInfo instance
+ pNativeCode = new (nothrow)CordbNativeCode(pFunction, &codeInfo, codeInfo.isInstantiatedGeneric != 0);
+ _ASSERTE(pNativeCode != NULL);
+
+ m_nativeCodeTable.AddBaseOrThrow(pNativeCode);
+ }
+
+ return pNativeCode;
+} // CordbNativeCode::LookupOrCreateFromJITData
+
+// LoadNativeInfo loads from the left side any native variable info
+// from the JIT.
+//
+void CordbNativeCode::LoadNativeInfo()
+{
+ THROW_IF_NEUTERED(this);
+ INTERNAL_API_ENTRY(this->GetProcess());
+
+
+ // If we've either never done this before (no info), or we have, but the version number has increased, we
+ // should try and get a newer version of our JIT info.
+ if(m_nativeVarData.IsInitialized())
+ {
+ return;
+ }
+
+ // You can't do this if the function is implemented as part of the Runtime.
+ if (GetFunction()->IsNativeImpl() == CordbFunction::kNativeOnly)
+ {
+ ThrowHR(CORDBG_E_FUNCTION_NOT_IL);
+ }
+ CordbProcess *pProcess = GetProcess();
+ // Get everything via the DAC
+ if (m_fCodeAvailable)
+ {
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ pProcess->GetDAC()->GetNativeCodeSequencePointsAndVarInfo(GetVMNativeCodeMethodDescToken(),
+ GetAddress(),
+ m_fCodeAvailable,
+ &m_nativeVarData,
+ &m_sequencePoints);
+ }
+
+} // CordbNativeCode::LoadNativeInfo
+
+
+
diff --git a/src/debug/di/nativepipeline.cpp b/src/debug/di/nativepipeline.cpp
new file mode 100644
index 0000000000..21105f359a
--- /dev/null
+++ b/src/debug/di/nativepipeline.cpp
@@ -0,0 +1,64 @@
+// 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.
+//*****************************************************************************
+// File: NativePipeline.cpp
+//
+
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "nativepipeline.h"
+
+#if defined(ENABLE_EVENT_REDIRECTION_PIPELINE)
+#include "eventredirection.h"
+#include "eventredirectionpipeline.h"
+#endif
+
+#include "sstring.h"
+
+//-----------------------------------------------------------------------------
+// Returns null if redirection is not enabled, else returns a new redirection pipeline.
+INativeEventPipeline * CreateEventRedirectionPipelineIfEnabled()
+{
+#if !defined(ENABLE_EVENT_REDIRECTION_PIPELINE)
+ return NULL;
+#else
+
+ BOOL fEnabled = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_DbgRedirect) != 0;
+ if (!fEnabled)
+ {
+ return NULL;
+ }
+
+ return new (nothrow) EventRedirectionPipeline();
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// Allocate and return a pipeline object for this platform
+// Has debug checks (such as for event redirection)
+//
+// Returns:
+// newly allocated pipeline object. Caller must call delete on it.
+INativeEventPipeline * NewPipelineWithDebugChecks()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+ INativeEventPipeline * pRedirection = CreateEventRedirectionPipelineIfEnabled();
+ if (pRedirection != NULL)
+ {
+ return pRedirection;
+ }
+
+ return NewPipelineForThisPlatform();
+}
+
+
+
+
diff --git a/src/debug/di/nativepipeline.h b/src/debug/di/nativepipeline.h
new file mode 100644
index 0000000000..c9560bfe65
--- /dev/null
+++ b/src/debug/di/nativepipeline.h
@@ -0,0 +1,228 @@
+// 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.
+//*****************************************************************************
+// NativePipeline.h
+//
+
+//
+// defines native pipeline abstraction, which includes debug-support
+// for event redirection.
+//*****************************************************************************
+
+
+#ifndef _NATIVE_PIPELINE_H
+#define _NATIVE_PIPELINE_H
+
+//-----------------------------------------------------------------------------
+// Interface for native-debugging pipeline associated with a single process
+// that is being debugged.
+//
+// On windows, this is a wrapper around the win32 debugging API
+// (eg, kernel32!WaitForDebugEvent). On rotor, it has an alternative implementation.
+// . See code:IEventChannel for more information.
+// @dbgtodo : All of the APIs that return BOOL should probably be changed to
+// return HRESULTS so we don't have to rely on some implicit GetLastError protocol.
+//-----------------------------------------------------------------------------
+class INativeEventPipeline
+{
+public:
+ // Call to delete the pipeline. This can only be called once.
+ virtual void Delete() = 0;
+
+
+ //
+ // set whether to kill outstanding debuggees when the debugger exits.
+ //
+ // Arguments:
+ // fKillOnExit - When the debugger thread (this thread) exits, outstanding debuggees will be
+ // terminated (if true), else detached (if false)
+ //
+ // Returns:
+ // True on success, False on failure.
+ //
+ // Notes:
+ // This is a cross-platform wrapper around Kernel32!DebugSetProcessKillOnExit.
+ // This affects all debuggees handled by this thread.
+ // This is not supported or necessary for Mac debugging. The only reason we need this on Windows is to
+ // ask the OS not to terminate the debuggee when the debugger exits. The Mac debugging pipeline
+ // doesn't automatically kill the debuggee when the debugger exits.
+ //
+
+ virtual BOOL DebugSetProcessKillOnExit(bool fKillOnExit) = 0;
+
+ // Create
+ virtual HRESULT CreateProcessUnderDebugger(
+ MachineInfo machineInfo,
+ LPCWSTR lpApplicationName,
+ LPCWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation) = 0;
+
+ // Attach
+ virtual HRESULT DebugActiveProcess(MachineInfo machineInfo, DWORD processId) = 0;
+
+ // Detach
+ virtual HRESULT DebugActiveProcessStop(DWORD processId) =0;
+
+ //
+ // Block and wait for the next debug event from the debuggee process.
+ //
+ // Arguments:
+ // pEvent - buffer for the debug event to be returned
+ // dwTimeout - number of milliseconds to wait before timing out
+ // pProcess - the CordbProcess associated with this pipeline; used to look up a thread ID if necessary
+ //
+ // Return Value:
+ // TRUE if a debug event is available
+ //
+ // Notes:
+ // Once a debug event is returned, it is consumed from the pipeline and will not be accessible in the
+ // future. Caller is responsible for saving the debug event if necessary.
+ //
+
+ virtual BOOL WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess) =0;
+
+ //
+ // This is specific to Windows. When a debug event is sent to the debugger, the debuggee process is
+ // suspended. The debugger must call this function to resume the debuggee process.
+ //
+ // Arguments:
+ // dwProcessId - process ID of the debuggee
+ // dwThreadId - thread ID of the thread which has triggered a debug event before
+ // dwContinueStatus - whether to handle the exception (if any) reported on the specified thread
+ //
+ // Return Value:
+ // TRUE if successful
+ //
+ // Notes:
+ // For Mac debugging, the process isn't actually suspended when a debug event is raised. As such,
+ // this function is a nop for Mac debugging. See code:Debugger::SendRawEvent.
+ //
+ // Of course, this is a semantic difference from Windows. However, in most cases, the LS suspends
+ // all managed threads by calling code:Debugger::TrapAllRuntimeThreads immediately after raising a
+ // debug event. The only case where this is not true is code:Debugger::SendCreateProcess, but that
+ // doesn't seem to be a problem at this point.
+ //
+
+ virtual BOOL ContinueDebugEvent(
+ DWORD dwProcessId,
+ DWORD dwThreadId,
+ DWORD dwContinueStatus
+ ) =0;
+
+ //
+ // Return a handle for the debuggee process.
+ //
+ // Return Value:
+ // handle for the debuggee process (see below)
+ //
+ // Notes:
+ // Handles are a Windows-specific concept. For Mac debugging, the handle returned by this function is
+ // only valid for waiting on process termination. This is ok for now because the only cases where a
+ // real process handle is needed are related to interop-debugging, which isn't supported on the Mac.
+ //
+
+ virtual HANDLE GetProcessHandle() = 0;
+
+ //
+ // Terminate the debuggee process.
+ //
+ // Arguments:
+ // exitCode - the exit code for the debuggee process
+ //
+ // Return Value:
+ // TRUE if successful
+ //
+ // Notes:
+ // The exit code is ignored for Mac debugging.
+ //
+
+ virtual BOOL TerminateProcess(UINT32 exitCode) = 0;
+
+ //
+ // Resume any suspended threads in the currend process.
+ // This decreases the suspend count of each thread by at most 1.
+ // Call multiple times until it returns S_FALSE if you want to really ensure
+ // all threads are running.
+ //
+ // Notes:
+ // On Windows the OS may suspend threads when continuing a 2nd-chance exception.
+ // Call this to get them resumed again. On other platforms this
+ // will typically be a no-op, so I provide a default implementation to avoid
+ // everyone having to override this.
+ //
+ // Return Value:
+ // S_OK if at least one thread was resumed from a suspended state
+ // S_FALSE if nothing was done
+ // An error code indicating why we were not able to attempt this
+
+ virtual HRESULT EnsureThreadsRunning()
+ {
+ return S_FALSE;
+ }
+
+#ifdef FEATURE_PAL
+ // Used by debugger side (RS) to cleanup the target (LS) named pipes
+ // and semaphores when the debugger detects the debuggee process exited.
+ virtual void CleanupTargetProcess()
+ {
+ }
+#endif
+};
+
+//
+// Helper accessors for manipulating native pipeline.
+// These also provide some platform abstractions for DEBUG_EVENT.
+//
+
+// Returns process ID that the debug event is on.
+DWORD GetProcessId(const DEBUG_EVENT * pEvent);
+
+// Returns Thread ID of the thread that fired the debug event.
+DWORD GetThreadId(const DEBUG_EVENT * pEvent);
+
+//
+// Determines if this is an exception event.
+//
+// Arguments:
+// pEvent - [required, in]: debug event to inspect
+// pfFirstChance - [required, out]: set if this is an 1st-chance exception.
+// ppRecord - [required, out]: if this is an exception, pointer into to the exception record.
+// this pointer has the same lifetime semantics as the DEBUG_EVENT (it may
+// likely be a pointer into the debug-event).
+//
+// Returns:
+// True if this is an exception. Sets outparameters to exception values.
+// Else false.
+//
+// Notes:
+// Exceptions are spceial because they need to be sent to the CLR for filtering.
+BOOL IsExceptionEvent(const DEBUG_EVENT * pEvent, BOOL * pfFirstChance, const EXCEPTION_RECORD ** ppRecord);
+
+
+//-----------------------------------------------------------------------------
+// Allocate and return a pipeline object for this platform
+//
+// Returns:
+// newly allocated pipeline object. Caller must call Dispose() on it.
+INativeEventPipeline * NewPipelineForThisPlatform();
+
+//-----------------------------------------------------------------------------
+// Allocate and return a pipeline object for this platform
+// Has debug checks (such as for event redirection)
+//
+// Returns:
+// newly allocated pipeline object. Caller must call Dispose() on it.
+INativeEventPipeline * NewPipelineWithDebugChecks();
+
+
+
+#endif // _NATIVE_PIPELINE_H
+
diff --git a/src/debug/di/platformspecific.cpp b/src/debug/di/platformspecific.cpp
new file mode 100644
index 0000000000..9df22b1728
--- /dev/null
+++ b/src/debug/di/platformspecific.cpp
@@ -0,0 +1,40 @@
+// 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 "stdafx.h"
+
+//
+// This file exists just to pull in platform specific source files. More precisely we're switching on the
+// platform being targetted for debugging (not the platform we're currently building debugger executables
+// for). We do this instead of using build rules to overcome limitations with build when it comes including
+// different source files based on build macros.
+//
+
+#if FEATURE_DBGIPC_TRANSPORT_DI
+#include "dbgtransportpipeline.cpp"
+#include "shimremotedatatarget.cpp"
+#include "remoteeventchannel.cpp"
+#else
+#include "WindowsPipeline.cpp"
+#include "EventRedirectionPipeline.cpp"
+#include "ShimLocalDataTarget.cpp"
+#include "LocalEventChannel.cpp"
+#endif
+
+#if DBG_TARGET_X86
+#include "i386/cordbregisterset.cpp"
+#include "i386/primitives.cpp"
+#elif DBG_TARGET_AMD64
+#include "amd64/cordbregisterset.cpp"
+#include "amd64/primitives.cpp"
+#elif DBG_TARGET_ARM
+#include "arm/cordbregisterset.cpp"
+#include "arm/primitives.cpp"
+#elif DBG_TARGET_ARM64
+#include "arm64/cordbregisterset.cpp"
+#include "arm64/primitives.cpp"
+#else
+#error Unsupported platform
+#endif
diff --git a/src/debug/di/process.cpp b/src/debug/di/process.cpp
new file mode 100644
index 0000000000..44e4a0c667
--- /dev/null
+++ b/src/debug/di/process.cpp
@@ -0,0 +1,15235 @@
+// 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.
+//*****************************************************************************
+// File: process.cpp
+//
+
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "primitives.h"
+#include "safewrap.h"
+
+#include "check.h"
+
+#ifndef SM_REMOTESESSION
+#define SM_REMOTESESSION 0x1000
+#endif
+
+#include "corpriv.h"
+#include "corexcep.h"
+#include "../../dlls/mscorrc/resource.h"
+#include <limits.h>
+
+#include <sstring.h>
+
+// @dbgtodo shim: process has some private hooks into the shim.
+#include "shimpriv.h"
+
+#include "metadataexports.h"
+#include "readonlydatatargetfacade.h"
+#include "metahost.h"
+
+// Keep this around for retail debugging. It's very very useful because
+// it's global state that we can always find, regardless of how many locals the compiler
+// optimizes away ;)
+struct RSDebuggingInfo;
+extern RSDebuggingInfo * g_pRSDebuggingInfo;
+
+//---------------------------------------------------------------------------------------
+//
+// OpenVirtualProcessImpl method called by the shim to get an ICorDebugProcess4 instance
+//
+// Arguments:
+// clrInstanceId - target pointer identifying which CLR in the Target to debug.
+// pDataTarget - data target abstraction.
+// hDacModule - the handle of the appropriate DAC dll for this runtime
+// riid - interface ID to query for.
+// ppProcessOut - new object for target, interface ID matches riid.
+// ppFlagsOut - currently only has 1 bit to indicate whether or not this runtime
+// instance will send a managed event after attach
+//
+// Return Value:
+// S_OK on success. Else failure
+//
+// Assumptions:
+//
+// Notes:
+// The outgoing process object can be cleaned up by calling Detach (which
+// will reset the Attach bit.)
+// @dbgtodo attach-bit: need to determine fate of attach bit.
+//
+//---------------------------------------------------------------------------------------
+STDAPI OpenVirtualProcessImpl(
+ ULONG64 clrInstanceId,
+ IUnknown * pDataTarget,
+ HMODULE hDacModule,
+ CLR_DEBUGGING_VERSION * pMaxDebuggerSupportedVersion,
+ REFIID riid,
+ IUnknown ** ppInstance,
+ CLR_DEBUGGING_PROCESS_FLAGS* pFlagsOut)
+{
+ HRESULT hr = S_OK;
+ RSExtSmartPtr<CordbProcess> pProcess;
+ PUBLIC_API_ENTRY(NULL);
+ EX_TRY
+ {
+
+ if ( (pDataTarget == NULL) || (clrInstanceId == 0) || (pMaxDebuggerSupportedVersion == NULL) ||
+ ((pFlagsOut == NULL) && (ppInstance == NULL))
+ )
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ // We consider the top 8 bits of the struct version to be the only part that represents
+ // a breaking change. This gives us some freedom in the future to have the debugger
+ // opt into getting more data.
+ const WORD kMajorMask = 0xff00;
+ const WORD kMaxStructMajor = 0;
+ if ((pMaxDebuggerSupportedVersion->wStructVersion & kMajorMask) > kMaxStructMajor)
+ {
+ // Don't know how to interpret the version structure
+ ThrowHR(CORDBG_E_UNSUPPORTED_VERSION_STRUCT);
+ }
+
+ // This process object is intended to be used for the V3 pipeline, and so
+ // much of the process from V2 is not being used. For example,
+ // - there is no ShimProcess object
+ // - there is no w32et thread (all threads are effectively an event thread)
+ // - the stop state is 'live', which corresponds to CordbProcess not knowing what
+ // its stop state really is (because that is now controlled by the shim).
+ IfFailThrow(CordbProcess::OpenVirtualProcess(
+ clrInstanceId,
+ pDataTarget, // takes a reference
+ hDacModule,
+ NULL, // Cordb
+ (DWORD) 0, // 0 for V3 cases (pShim == NULL).
+ NULL, // no Shim in V3 cases
+ &pProcess));
+
+ // CordbProcess::OpenVirtualProcess already did the external addref to pProcess.
+ // Since pProcess is a smart ptr, it will external release in this function.
+ // Living reference will be the one from the QI.
+
+ // get the managed debug event pending flag
+ if(pFlagsOut != NULL)
+ {
+ hr = pProcess->GetAttachStateFlags(pFlagsOut);
+ if(FAILED(hr))
+ {
+ ThrowHR(hr);
+ }
+ }
+
+ //
+ // Check to make sure the debugger supports debugging this version
+ // Note that it's important that we still store the flags (above) in this case
+ //
+ if (!CordbProcess::IsCompatibleWith(pMaxDebuggerSupportedVersion->wMajor))
+ {
+ // Not compatible - don't keep the process instance, and return this specific error-code
+ ThrowHR(CORDBG_E_UNSUPPORTED_FORWARD_COMPAT);
+ }
+
+ //
+ // Now Query for the requested interface
+ //
+ if(ppInstance != NULL)
+ {
+ IfFailThrow(pProcess->QueryInterface(riid, reinterpret_cast<void**> (ppInstance)));
+ }
+
+ // if you have to add code here that could fail make sure ppInstance gets released and NULL'ed at exit
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if((FAILED(hr) || ppInstance == NULL) && pProcess != NULL)
+ {
+ // The process has a strong reference to itself which is only released by neutering it.
+ // Since we aren't handing out the ref then we need to clean it up
+ _ASSERTE(ppInstance == NULL || *ppInstance == NULL);
+ pProcess->Neuter();
+ }
+ return hr;
+};
+
+//---------------------------------------------------------------------------------------
+// DEPRECATED - use OpenVirtualProcessImpl
+// OpenVirtualProcess method used by the shim in CLR v4 Beta1
+// We'd like a beta1 shim/VS to still be able to open dumps using a CLR v4 Beta2+ mscordbi.dll,
+// so we'll leave this in place (at least until after Beta2 is in wide use).
+//---------------------------------------------------------------------------------------
+STDAPI OpenVirtualProcess2(
+ ULONG64 clrInstanceId,
+ IUnknown * pDataTarget,
+ HMODULE hDacModule,
+ REFIID riid,
+ IUnknown ** ppInstance,
+ CLR_DEBUGGING_PROCESS_FLAGS* pFlagsOut)
+{
+ CLR_DEBUGGING_VERSION maxVersion = {0};
+ maxVersion.wMajor = 4;
+ return OpenVirtualProcessImpl(clrInstanceId, pDataTarget, hDacModule, &maxVersion, riid, ppInstance, pFlagsOut);
+}
+
+//---------------------------------------------------------------------------------------
+// DEPRECATED - use OpenVirtualProcessImpl
+// Public OpenVirtualProcess method to get an ICorDebugProcess4 instance
+// Used directly in CLR v4 pre Beta1 - can probably be safely removed now
+//---------------------------------------------------------------------------------------
+STDAPI OpenVirtualProcess(
+ ULONG64 clrInstanceId,
+ IUnknown * pDataTarget,
+ REFIID riid,
+ IUnknown ** ppInstance)
+{
+ return OpenVirtualProcess2(clrInstanceId, pDataTarget, NULL, riid, ppInstance, NULL);
+};
+
+//-----------------------------------------------------------------------------
+// Most Hresults to Unrecoverable error indicate an internal error
+// in the Right-Side.
+// However, a few are legal (eg, "could actually happen in a retail scenario and
+// not indicate an issue in mscorbi"). Track that here.
+//-----------------------------------------------------------------------------
+
+bool IsLegalFatalError(HRESULT hr)
+{
+ return
+ (hr == CORDBG_E_INCOMPATIBLE_PROTOCOL) ||
+ (hr == CORDBG_E_CANNOT_DEBUG_FIBER_PROCESS) ||
+ (hr == CORDBG_E_UNCOMPATIBLE_PLATFORMS) ||
+ (hr == CORDBG_E_MISMATCHED_CORWKS_AND_DACWKS_DLLS) ||
+ // This should only happen in the case of a security attack on us.
+ (hr == E_ACCESSDENIED) ||
+ (hr == E_FAIL);
+}
+
+//-----------------------------------------------------------------------------
+// Safe wait. Use this anytime we're waiting on:
+// - an event signaled by the helper thread.
+// - something signaled by a thread that holds the process lock.
+// Note that we must preserve GetLastError() semantics.
+//-----------------------------------------------------------------------------
+inline DWORD SafeWaitForSingleObject(CordbProcess * p, HANDLE h, DWORD dwTimeout)
+{
+ // Can't hold process lock while blocking
+ _ASSERTE(!p->ThreadHoldsProcessLock());
+
+ return ::WaitForSingleObject(h, dwTimeout);
+}
+
+#define CORDB_WAIT_TIMEOUT 360000 // milliseconds
+
+//---------------------------------------------------------------------------------------
+//
+// Get the timeout value used in waits.
+//
+// Return Value:
+// Number of milliseconds to waite or possible INFINITE (-1).
+//
+//
+// Notes:
+// Uses registry values for fine tuning.
+//
+
+// static
+static inline DWORD CordbGetWaitTimeout()
+{
+#ifdef _DEBUG
+ // 0 = Wait forever
+ // 1 = Wait for CORDB_WAIT_TIMEOUT
+ // n = Wait for n milliseconds
+ static ConfigDWORD cordbWaitTimeout;
+ DWORD dwTimeoutVal = cordbWaitTimeout.val(CLRConfig::INTERNAL_DbgWaitTimeout);
+ if (dwTimeoutVal == 0)
+ return DWORD(-1);
+ else if (dwTimeoutVal != 1)
+ return dwTimeoutVal;
+ else
+#endif
+ {
+ return CORDB_WAIT_TIMEOUT;
+ }
+}
+
+//----------------------------------------------------------------------------
+// Implementation of IDacDbiInterface::IMetaDataLookup.
+// lookup Internal Metadata Importer keyed by PEFile
+// isILMetaDataForNGENImage is true iff the IMDInternalImport returned represents a pointer to
+// metadata from an IL image when the module was an ngen'ed image.
+IMDInternalImport * CordbProcess::LookupMetaData(VMPTR_PEFile vmPEFile, bool &isILMetaDataForNGENImage)
+{
+ INTERNAL_DAC_CALLBACK(this);
+
+ HASHFIND hashFindAppDomain;
+ HASHFIND hashFindModule;
+ IMDInternalImport * pMDII = NULL;
+ isILMetaDataForNGENImage = false;
+
+ // Check to see if one of the cached modules has the metadata we need
+ // If not we will do a more exhaustive search below
+ for (CordbAppDomain * pAppDomain = m_appDomains.FindFirst(&hashFindAppDomain);
+ pAppDomain != NULL;
+ pAppDomain = m_appDomains.FindNext(&hashFindAppDomain))
+ {
+ for (CordbModule * pModule = pAppDomain->m_modules.FindFirst(&hashFindModule);
+ pModule != NULL;
+ pModule = pAppDomain->m_modules.FindNext(&hashFindModule))
+ {
+ if (pModule->GetPEFile() == vmPEFile)
+ {
+ pMDII = NULL;
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ pMDII = pModule->GetInternalMD();
+ );
+ if(pMDII != NULL)
+ return pMDII;
+ }
+ }
+ }
+
+ // Cache didn't have it... time to search harder
+ PrepopulateAppDomainsOrThrow();
+
+ // There may be perf issues here. The DAC may make a lot of metadata requests, and so
+ // this may be an area for potential perf optimizations if we find things running slow.
+
+ // enumerate through all Modules
+ for (CordbAppDomain * pAppDomain = m_appDomains.FindFirst(&hashFindAppDomain);
+ pAppDomain != NULL;
+ pAppDomain = m_appDomains.FindNext(&hashFindAppDomain))
+ {
+ pAppDomain->PrepopulateModules();
+
+ for (CordbModule * pModule = pAppDomain->m_modules.FindFirst(&hashFindModule);
+ pModule != NULL;
+ pModule = pAppDomain->m_modules.FindNext(&hashFindModule))
+ {
+ if (pModule->GetPEFile() == vmPEFile)
+ {
+ pMDII = NULL;
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ pMDII = pModule->GetInternalMD();
+ );
+
+ if ( pMDII == NULL)
+ {
+ // If we couldn't get metadata from the CordbModule, then we need to ask the
+ // debugger if it can find the metadata elsewhere.
+ // If this was live debugging, we should have just gotten the memory contents.
+ // Thus this code is for dump debugging, when you don't have the metadata in the dump.
+ pMDII = LookupMetaDataFromDebugger(vmPEFile, isILMetaDataForNGENImage, pModule);
+ }
+ return pMDII;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+IMDInternalImport * CordbProcess::LookupMetaDataFromDebugger(
+ VMPTR_PEFile vmPEFile,
+ bool &isILMetaDataForNGENImage,
+ CordbModule * pModule)
+{
+ DWORD dwImageTimeStamp = 0;
+ DWORD dwImageSize = 0;
+ bool isNGEN = false;
+ StringCopyHolder filePath;
+ IMDInternalImport * pMDII = NULL;
+
+ // First, see if the debugger can locate the exact metadata we want.
+ if (this->GetDAC()->GetMetaDataFileInfoFromPEFile(vmPEFile, dwImageTimeStamp, dwImageSize, isNGEN, &filePath))
+ {
+ _ASSERTE(filePath.IsSet());
+
+ // Since we track modules by their IL images, that presents a little bit of oddness here. The correct
+ // thing to do is preferentially load the NI content.
+ // We don't discriminate between timestamps & sizes becuase CLRv4 deterministic NGEN guarantees that the
+ // IL image and NGEN image have the same timestamp and size. Should that guarantee change, this code
+ // will be horribly broken.
+
+ // If we happen to have an NI file path, use it instead.
+ const WCHAR * pwszFilePath = pModule->GetNGenImagePath();
+ if (pwszFilePath)
+ {
+ // Force the issue, regardless of the older codepath's opinion.
+ isNGEN = true;
+ }
+ else
+ {
+ pwszFilePath = (WCHAR *)filePath;
+ }
+
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ pMDII = LookupMetaDataFromDebuggerForSingleFile(pModule, pwszFilePath, dwImageTimeStamp, dwImageSize);
+ );
+
+ // If it's an ngen'ed image and the debugger couldn't find it, we can use the metadata from
+ // the corresponding IL image if the debugger can locate it.
+ filePath.Clear();
+ if ((pMDII == NULL) &&
+ (isNGEN) &&
+ (this->GetDAC()->GetILImageInfoFromNgenPEFile(vmPEFile, dwImageTimeStamp, dwImageSize, &filePath)))
+ {
+ _ASSERTE(filePath.IsSet());
+
+ WCHAR *mutableFilePath = (WCHAR *)filePath;
+
+#if defined(FEATURE_CORESYSTEM)
+ size_t pathLen = wcslen(mutableFilePath);
+
+ const wchar_t *nidll = W(".ni.dll");
+ const wchar_t *niexe = W(".ni.exe");
+ const size_t dllLen = wcslen(nidll); // used for ni.exe as well
+
+ const wchar_t *niwinmd = W(".ni.winmd");
+ const size_t winmdLen = wcslen(niwinmd);
+
+ if (pathLen > dllLen && _wcsicmp(mutableFilePath+pathLen-dllLen, nidll) == 0)
+ {
+ wcscpy_s(mutableFilePath+pathLen-dllLen, dllLen, W(".dll"));
+ }
+ else if (pathLen > dllLen && _wcsicmp(mutableFilePath+pathLen-dllLen, niexe) == 0)
+ {
+ wcscpy_s(mutableFilePath+pathLen-dllLen, dllLen, W(".exe"));
+ }
+ else if (pathLen > winmdLen && _wcsicmp(mutableFilePath+pathLen-winmdLen, niwinmd) == 0)
+ {
+ wcscpy_s(mutableFilePath+pathLen-winmdLen, winmdLen, W(".winmd"));
+ }
+#endif//FEATURE_CORESYSTEM
+
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ pMDII = LookupMetaDataFromDebuggerForSingleFile(pModule, mutableFilePath, dwImageTimeStamp, dwImageSize);
+ );
+
+ if (pMDII != NULL)
+ {
+ isILMetaDataForNGENImage = true;
+ }
+ }
+ }
+ return pMDII;
+}
+
+// We do not know if the image being sent to us is an IL image or ngen image.
+// CordbProcess::LookupMetaDataFromDebugger() has this knowledge when it looks up the file to hand off
+// to this function.
+// DacDbiInterfaceImpl::GetMDImport() has this knowledge in the isNGEN flag.
+// The CLR v2 code that windbg used made a distinction whether the metadata came from
+// the exact binary or not (i.e. were we getting metadata from the IL image and using
+// it against the ngen image?) but that information was never used and so not brought forward.
+// It would probably be more interesting generally to track whether the debugger gives us back
+// a file that bears some relationship to the file we asked for, which would catch the NI/IL case
+// as well.
+IMDInternalImport * CordbProcess::LookupMetaDataFromDebuggerForSingleFile(
+ CordbModule * pModule,
+ LPCWSTR pwszFilePath,
+ DWORD dwTimeStamp,
+ DWORD dwSize)
+{
+ INTERNAL_DAC_CALLBACK(this);
+
+ ULONG32 cchLocalImagePath = MAX_LONGPATH;
+ ULONG32 cchLocalImagePathRequired;
+ NewArrayHolder<WCHAR> pwszLocalFilePath = NULL;
+ IMDInternalImport * pMDII = NULL;
+
+ const HRESULT E_NSF_BUFFER = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+ HRESULT hr = E_NSF_BUFFER;
+ for(unsigned i=0; i<2 && hr == E_NSF_BUFFER; i++)
+ {
+ if (pwszLocalFilePath != NULL)
+ pwszLocalFilePath.Release();
+
+ if (NULL == (pwszLocalFilePath = new (nothrow) WCHAR[cchLocalImagePath+1]))
+ ThrowHR(E_OUTOFMEMORY);
+
+ cchLocalImagePathRequired = 0;
+
+ hr = m_pMetaDataLocator->GetMetaData(pwszFilePath,
+ dwTimeStamp,
+ dwSize,
+ cchLocalImagePath,
+ &cchLocalImagePathRequired,
+ pwszLocalFilePath);
+
+ pwszLocalFilePath[cchLocalImagePath] = W('\0');
+ cchLocalImagePath = cchLocalImagePathRequired;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ hr = pModule->InitPublicMetaDataFromFile(pwszLocalFilePath, ofReadOnly, false);
+ if (SUCCEEDED(hr))
+ {
+ // While we're successfully returning a metadata reader, remember that there's
+ // absolutely no guarantee this metadata is an exact match for the vmPEFile.
+ // The debugger could literally send us back a path to any managed file with
+ // metadata content that is readable and we'll 'succeed'.
+ // For now, this is by-design. A debugger should be allowed to decide if it wants
+ // to take a risk by returning 'mostly matching' metadata to see if debugging is
+ // possible in the absense of a true match.
+ pMDII = pModule->GetInternalMD();
+ }
+ }
+
+ return pMDII;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Implement IDacDbiInterface::IAllocator::Alloc
+// Expected to throws on error.
+//
+// Arguments:
+// lenBytes - size of the byte array to allocate
+//
+// Return Value:
+// Return the newly allocated byte array, or throw on OOM
+//
+// Notes:
+// Since this function is a callback from DAC, it must not take the process lock.
+// If it does, we may deadlock between the DD lock and the process lock.
+// If we really need to take the process lock for whatever reason, we must take it in the DBI functions
+// which call the DAC API that ends up calling this function.
+// See code:InternalDacCallbackHolder for more information.
+//
+
+void * CordbProcess::Alloc(SIZE_T lenBytes)
+{
+ return new BYTE[lenBytes]; // throws
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Implements IDacDbiInterface::IAllocator::Free
+//
+// Arguments:
+// p - pointer to the memory to be released
+//
+// Notes:
+// Since this function is a callback from DAC, it must not take the process lock.
+// If it does, we may deadlock between the DD lock and the process lock.
+// If we really need to take the process lock for whatever reason, we must take it in the DBI functions
+// which call the DAC API that ends up calling this function.
+// See code:InternalDacCallbackHolder for more information.
+//
+
+void CordbProcess::Free(void * p)
+{
+ // This shouldn't throw.
+ delete [] ((BYTE *) p);
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// #DBIVersionChecking
+//
+// There are a few checks we need to do to make sure we are using the matching DBI and DAC for a particular
+// version of the runtime.
+//
+// 1. Runtime vs. DBI
+// - Desktop
+// This is done by making sure that the CorDebugInterfaceVersion passed to code:CreateCordbObject is
+// compatible with the version of the DBI.
+//
+// - Windows CoreCLR
+// This is done by dbgshim.dll. It checks whether the runtime DLL and the DBI DLL have the same
+// product version. See CreateDebuggingInterfaceForVersion() in dbgshim.cpp.
+//
+// - Remote transport (Mac CoreCLR + CoreSystem CoreCLR)
+// Since there is no dbgshim.dll for a remote CoreCLR, we have to do this check in some other place.
+// We do this in code:CordbProcess::CreateDacDbiInterface, by calling
+// code:DacDbiInterfaceImpl::CheckDbiVersion right after we have created the DDMarshal.
+// The IDacDbiInterface implementation on remote device checks the product version of the device
+// coreclr by:
+// mac - looking at the Info.plist file in the CoreCLR bundle.
+// CoreSystem - this check is skipped at the moment, but should be implemented if we release it
+//
+// The one twist here is that the DBI needs to communicate with the IDacDbiInterface
+// implementation on the device BEFORE it can verify the product versions. This means that we need to
+// have one IDacDbiInterface API which is consistent across all versions of the IDacDbiInterface.
+// This puts two constraints on CheckDbiVersion():
+//
+// 1. It has to be the first API on the IDacDbiInterface.
+// - Otherwise, a wrong version of the DBI may end up calling a different API on the
+// IDacDbiInterface and getting random results. (Really what matters is that it is
+// protocol message id 0, at present the source code position implies the message id)
+//
+// 2. Its parameters cannot change.
+// - Otherwise, we may run into random errors when we marshal/unmarshal the arguments for the
+// call to CheckDbiVersion(). Debugging will still fail, but we won't get the
+// version mismatch error. (Again, the protocol is what ultimately matters)
+// - To mitigate the impact of this constraint, we use the code:DbiVersion structure.
+// In addition to the DBI version, it also contains a format number (in case we decide to
+// check something else in the future), a breaking change number so that we can force
+// breaking changes between a DBI and a DAC, and space reserved for future use.
+//
+// 2. DBI vs. DAC
+// - Desktop and Windows CoreCLR (old architecture)
+// No verification is done. There is a transitive implication that if DBI matches runtime and DAC matches
+// runtime then DBI matches DAC. Technically because the DBI only matches runtime on major version number
+// runtime and DAC could be from different builds. However because we service all three binaries together
+// and DBI always loads the DAC that is sitting in the same directory DAC and DBI generally get tight
+// version coupling. A user with admin privleges could put different builds together and no version check
+// would ever fail though.
+//
+// - Desktop and Windows CoreCLR (new architecture)
+// No verification is done. Similar to above its implied that if DBI matches runtime and runtime matches
+// DAC then DBI matches DAC. The only difference is that here both the DBI and DAC are provided by the
+// debugger. We provide timestamp and filesize for both binaries which are relatively strongly bound hints,
+// but there is no enforcement on the returned binaries beyond the runtime compat checking.
+//
+// - Remote transport (Mac CoreCLR and CoreSystem CoreCLR)
+// Because the transport exists between DBI and DAC it becomes much more important to do a versioning check
+//
+// Mac - currently does a tightly bound version check between DBI and the runtime (CheckDbiVersion() above),
+// which transitively gives a tightly bound check to DAC. In same function there is also a check that is
+// logically a DAC DBI protocol check, verifying that the m_dwProtocolBreakingChangeCounter of DbiVersion
+// matches. However this check should be weaker than the build version check and doesn't add anything here.
+//
+// CoreSystem - currently skips the tightly bound version check to make internal deployment and usage easier.
+// We want to use old desktop side debugger components to target newer CoreCLR builds, only forcing a desktop
+// upgrade when the protocol actually does change. To do this we use two checks:
+// 1. The breaking change counter in CheckDbiVersion() whenever a dev knows they are breaking back
+// compat and wants to be explicit about it. This is the same as mac above.
+// 2. During the auto-generation of the DDMarshal classes we take an MD5 hash of IDacDbiInterface source
+// code and embed it in two DDMarshal functions, one which runs locally and one that runs remotely.
+// If both DBI and DAC were built from the same source then the local and remote hashes will match. If the
+// hashes don't match then we assume there has been a been a breaking change in the protocol. Note
+// this hash could have both false-positives and false-negatives. False positives could occur when
+// IDacDbiInterface is changed in a trivial way, such as changing a comment. False negatives could
+// occur when the semantics of the protocol are changed even though the interface is not. Another
+// case would be changing the DDMarshal proxy generation code. In addition to the hashes we also
+// embed timestamps when the auto-generated code was produced. However this isn't used for version
+// matching, only as a hint to indicate which of two mismatched versions is newer.
+//
+//
+// 3. Runtime vs. DAC
+// - Desktop, Windows CoreCLR, CoreSystem CoreCLR
+// In both cases we check this by matching the timestamp in the debug directory of the runtime image
+// and the timestamp we store in the DAC table when we generate the DAC dll. This is done in
+// code:ClrDataAccess::VerifyDlls.
+//
+// - Mac CoreCLR
+// On Mac, we don't have a timestamp in the runtime image. Instead, we rely on checking the 16-byte
+// UUID in the image. This UUID is used to check whether a symbol file matches the image, so
+// conceptually it's the same as the timestamp we use on Windows. This is also done in
+// code:ClrDataAccess::VerifyDlls.
+//
+//---------------------------------------------------------------------------------------
+//
+// Instantiates a DacDbi Interface object in a live-debugging scenario that matches
+// the current instance of mscorwks in this process.
+//
+// Return Value:
+// Returns on success. Else throws.
+//
+// Assumptions:
+// Client will code:CordbProcess::FreeDac when its done with the DacDbi interface.
+// Caller has initialized clrInstanceId.
+//
+// Notes:
+// This looks for the DAC next to this current DBI. This assumes that Dac and Dbi are both on
+// the local file system. That assumption will break in zero-copy deployment scenarios.
+//
+//---------------------------------------------------------------------------------------
+void
+CordbProcess::CreateDacDbiInterface()
+{
+ _ASSERTE(m_pDACDataTarget != NULL);
+ _ASSERTE(m_pDacPrimitives == NULL); // don't double-init
+
+ // Caller has already determined which CLR in the target is being debugged.
+ _ASSERTE(m_clrInstanceId != 0);
+
+ m_pDacPrimitives = NULL;
+
+ HRESULT hrStatus = S_OK;
+
+ // Non-marshalling path for live local dac.
+ // in the new arch we can get the module from OpenVirtualProcess2 but in the shim case
+ // and the deprecated OpenVirtualProcess case we must assume it comes from DAC in the
+ // same directory as DBI
+ if(m_hDacModule == NULL)
+ {
+ m_hDacModule.Assign(ShimProcess::GetDacModule());
+ }
+
+ //
+ // Get the access interface, passing our callback interfaces (data target, allocator and metadata lookup)
+ //
+
+ IDacDbiInterface::IAllocator * pAllocator = this;
+ IDacDbiInterface::IMetaDataLookup * pMetaDataLookup = this;
+
+
+ typedef HRESULT (STDAPICALLTYPE * PFN_DacDbiInterfaceInstance)(
+ ICorDebugDataTarget *,
+ CORDB_ADDRESS,
+ IDacDbiInterface::IAllocator *,
+ IDacDbiInterface::IMetaDataLookup *,
+ IDacDbiInterface **);
+
+ IDacDbiInterface* pInterfacePtr = NULL;
+ PFN_DacDbiInterfaceInstance pfnEntry = (PFN_DacDbiInterfaceInstance)GetProcAddress(m_hDacModule, "DacDbiInterfaceInstance");
+ if (!pfnEntry)
+ {
+ ThrowLastError();
+ }
+
+ hrStatus = pfnEntry(m_pDACDataTarget, m_clrInstanceId, pAllocator, pMetaDataLookup, &pInterfacePtr);
+ IfFailThrow(hrStatus);
+
+ // We now have a resource, pInterfacePtr, that needs to be freed.
+ m_pDacPrimitives = pInterfacePtr;
+
+ // Setup DAC target consistency checking based on what we're using for DBI
+ m_pDacPrimitives->DacSetTargetConsistencyChecks( m_fAssertOnTargetInconsistency );
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Is the DAC/DBI interface initialized?
+//
+// Return Value:
+// TRUE iff init.
+//
+// Notes:
+// The RS will try to initialize DD as soon as it detects the runtime as loaded.
+// If the DD interface has not initialized, then it very likely the runtime has not
+// been loaded into the target.
+//
+BOOL CordbProcess::IsDacInitialized()
+{
+ return m_pDacPrimitives != NULL;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Get the DAC interface.
+//
+// Return Value:
+// the Dac/Dbi interface pointer to the process.
+// Never returns NULL.
+//
+// Assumptions:
+// Caller is responsible for ensuring Data-Target is safe to access (eg, not
+// currently running).
+// Caller is responsible for ensuring DAC-cache is flushed. Call code:CordbProcess::ForceDacFlush
+// as needed.
+//
+//---------------------------------------------------------------------------------------
+IDacDbiInterface * CordbProcess::GetDAC()
+{
+ // Since the DD primitives may throw, easiest way to model that is to make this throw.
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ // We should always have the DAC/DBI interface.
+ _ASSERTE(m_pDacPrimitives != NULL);
+ return m_pDacPrimitives;
+}
+
+//---------------------------------------------------------------------------------------
+// Get the Data-Target
+//
+// Returns:
+// pointer to the data-target. Should be non-null.
+// Lifetime of the pointer is until this process object is neutered.
+//
+ICorDebugDataTarget * CordbProcess::GetDataTarget()
+{
+ return m_pDACDataTarget;
+}
+
+//---------------------------------------------------------------------------------------
+// Create a CordbProcess object around an existing OS process.
+//
+// Arguments:
+// pDataTarget - abstracts access to the debuggee.
+// clrInstanceId - identifies the CLR instance within the debuggee. (This is the
+// base address of mscorwks)
+// pCordb - Pointer to the implementation of the owning Cordb object implementing the
+// owning ICD interface.
+// This should go away - we can get the functionality from the pShim.
+// If this is null, then pShim must be null too.
+// processID - OS process ID of target process. 0 if pShim == NULL.
+// pShim - shim counter part object. This allows hooks back for v2 compat. This will
+// go away once we no longer support V2 backwards compat.
+// This must be non-null for any V2 paths (including non-DAC-ized code).
+// If this is null, then we're in a V3 path.
+// ppProcess - out parameter for new process object. This gets addreffed.
+//
+// Return Value:
+// S_OK on success, and *ppProcess set to newly created debuggee object. Else error.
+//
+// Notes:
+// @dbgtodo - , shim: Cordb, and pShim will all eventually go away.
+//
+//---------------------------------------------------------------------------------------
+
+// static
+HRESULT CordbProcess::OpenVirtualProcess(
+ ULONG64 clrInstanceId,
+ IUnknown * pDataTarget,
+ HMODULE hDacModule,
+ Cordb* pCordb,
+ DWORD dwProcessID,
+ ShimProcess * pShim,
+ CordbProcess ** ppProcess)
+{
+ _ASSERTE(pDataTarget != NULL);
+
+ // In DEBUG builds, verify that we do actually have an ICorDebugDataTarget (i.e. that
+ // someone hasn't messed up the COM interop marshalling, etc.).
+#ifdef _DEBUG
+ {
+ IUnknown * pTempDt;
+ HRESULT hrQi = pDataTarget->QueryInterface(IID_ICorDebugDataTarget, (void**)&pTempDt);
+ _ASSERTE_MSG(SUCCEEDED(hrQi), "OpenVirtualProcess was passed something that isn't actually an ICorDebugDataTarget");
+ pTempDt->Release();
+ }
+#endif
+
+ // If we're emulating V2, then both pCordb and pShim are non-NULL.
+ // If we're doing a real V3 path, then they're both NULL.
+ // Either way, they should have the same null-status.
+ _ASSERTE((pCordb == NULL) == (pShim == NULL));
+
+ // If we're doing real V3, then we must have a real instance ID
+ _ASSERTE(!((pShim == NULL) && (clrInstanceId == 0)));
+
+ *ppProcess = NULL;
+
+ HRESULT hr = S_OK;
+ RSUnsafeExternalSmartPtr<CordbProcess> pProcess;
+ pProcess.Assign(new (nothrow) CordbProcess(clrInstanceId, pDataTarget, hDacModule, pCordb, dwProcessID, pShim));
+
+ if (pProcess == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ ICorDebugProcess * pThis = pProcess;
+ (void)pThis; //prevent "unused variable" error from GCC
+
+ // CordbProcess::Init may need shim hooks, so connect Shim now.
+ // This will bump reference count.
+ if (pShim != NULL)
+ {
+ pShim->SetProcess(pProcess);
+
+ _ASSERTE(pShim->GetProcess() == pThis);
+ _ASSERTE(pShim->GetWin32EventThread() != NULL);
+ }
+
+ hr = pProcess->Init();
+
+ if (SUCCEEDED(hr))
+ {
+ *ppProcess = pProcess;
+ pProcess->ExternalAddRef();
+ }
+ else
+ {
+ // handle failure path
+ pProcess->CleanupHalfBakedLeftSide();
+
+ if (pShim != NULL)
+ {
+ // Shim still needs to be disposed to clean up other resources.
+ pShim->SetProcess(NULL);
+ }
+
+ // In failure case, pProcess's dtor will do the final release.
+ }
+
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// CordbProcess constructor
+//
+// Arguments:
+// pDataTarget - Pointer to an implementation of ICorDebugDataTarget
+// (or ICorDebugMutableDataTarget), which virtualizes access to the process.
+// clrInstanceId - representation of the CLR to debug in the process. Must be specified
+// (non-zero) if pShim is NULL. If 0, use the first CLR that we see.
+// pCordb - Pointer to the implementation of the owning Cordb object implementing the
+// owning ICD interface.
+// pW32 - Pointer to the Win32 event thread to use when processing events for this
+// process.
+// dwProcessID - For V3, 0.
+// Else for shim codepaths, the processID of the process this object will represent.
+// pShim - Pointer to the shim for handling V2 debuggers on the V3 architecture.
+//
+//---------------------------------------------------------------------------------------
+
+CordbProcess::CordbProcess(ULONG64 clrInstanceId,
+ IUnknown * pDataTarget,
+ HMODULE hDacModule,
+ Cordb * pCordb,
+ DWORD dwProcessID,
+ ShimProcess * pShim)
+ : CordbBase(NULL, dwProcessID, enumCordbProcess),
+ m_fDoDelayedManagedAttached(false),
+ m_cordb(pCordb),
+ m_handle(NULL),
+ m_detached(false),
+ m_uninitializedStop(false),
+ m_exiting(false),
+ m_terminated(false),
+ m_unrecoverableError(false),
+ m_specialDeferment(false),
+ m_helperThreadDead(false),
+ m_loaderBPReceived(false),
+ m_cOutstandingEvals(0),
+ m_cOutstandingHandles(0),
+ m_clrInstanceId(clrInstanceId),
+ m_stopCount(0),
+ m_synchronized(false),
+ m_syncCompleteReceived(false),
+ m_pShim(pShim),
+ m_userThreads(11),
+ m_oddSync(false),
+#ifdef FEATURE_INTEROP_DEBUGGING
+ m_unmanagedThreads(11),
+#endif
+ m_appDomains(11),
+ m_sharedAppDomain(0),
+ m_steppers(11),
+ m_continueCounter(1),
+ m_flushCounter(0),
+ m_leftSideEventAvailable(NULL),
+ m_leftSideEventRead(NULL),
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ m_leftSideUnmanagedWaitEvent(NULL),
+#endif // FEATURE_INTEROP_DEBUGGING
+ m_initialized(false),
+ m_stopRequested(false),
+ m_stopWaitEvent(NULL),
+#ifdef FEATURE_INTEROP_DEBUGGING
+ m_cFirstChanceHijackedThreads(0),
+ m_unmanagedEventQueue(NULL),
+ m_lastQueuedUnmanagedEvent(NULL),
+ m_lastQueuedOOBEvent(NULL),
+ m_outOfBandEventQueue(NULL),
+ m_lastDispatchedIBEvent(NULL),
+ m_dispatchingUnmanagedEvent(false),
+ m_dispatchingOOBEvent(false),
+ m_doRealContinueAfterOOBBlock(false),
+ m_state(0),
+#endif // FEATURE_INTEROP_DEBUGGING
+ m_helperThreadId(0),
+ m_pPatchTable(NULL),
+ m_cPatch(0),
+ m_rgData(NULL),
+ m_rgNextPatch(NULL),
+ m_rgUncommitedOpcode(NULL),
+ m_minPatchAddr(MAX_ADDRESS),
+ m_maxPatchAddr(MIN_ADDRESS),
+ m_iFirstPatch(0),
+ m_hHelperThread(NULL),
+ m_dispatchedEvent(DB_IPCE_DEBUGGER_INVALID),
+ m_pDefaultAppDomain(NULL),
+ m_hDacModule(hDacModule),
+ m_pDacPrimitives(NULL),
+ m_pEventChannel(NULL),
+ m_fAssertOnTargetInconsistency(false),
+ m_runtimeOffsetsInitialized(false),
+ m_writableMetadataUpdateMode(LegacyCompatPolicy)
+{
+ _ASSERTE((m_id == 0) == (pShim == NULL));
+
+ HRESULT hr = pDataTarget->QueryInterface(IID_ICorDebugDataTarget, reinterpret_cast<void **>(&m_pDACDataTarget));
+ IfFailThrow(hr);
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ m_DbgSupport.m_DebugEventQueueIdx = 0;
+ m_DbgSupport.m_TotalNativeEvents = 0;
+ m_DbgSupport.m_TotalIB = 0;
+ m_DbgSupport.m_TotalOOB = 0;
+ m_DbgSupport.m_TotalCLR = 0;
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ g_pRSDebuggingInfo->m_MRUprocess = this;
+
+ // This is a strong reference to ourselves.
+ // This is cleared in code:CordbProcess::Neuter
+ m_pProcess.Assign(this);
+
+#ifdef _DEBUG
+ // On Debug builds, we'll ASSERT by default whenever the target appears to be corrupt or
+ // otherwise inconsistent (both in DAC and DBI). But we also need the ability to
+ // explicitly test corrupt targets.
+ // Tests should set COMPlus_DbgIgnoreInconsistentTarget=1 to suppress these asserts
+ // Note that this controls two things:
+ // 1) DAC behavior - see code:IDacDbiInterface::DacSetTargetConsistencyChecks
+ // 2) RS-only consistency asserts - see code:CordbProcess::TargetConsistencyCheck
+ if( !CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgDisableTargetConsistencyAsserts) )
+ {
+ m_fAssertOnTargetInconsistency = true;
+ }
+#endif
+}
+
+/*
+ A list of which resources owned by this object are accounted for.
+
+ UNKNOWN
+ Cordb* m_cordb;
+ CordbHashTable m_unmanagedThreads; // Released in CordbProcess but not removed from hash
+ DebuggerIPCEvent* m_lastQueuedEvent;
+
+ // CordbUnmannagedEvent is a struct which is not derrived from CordbBase.
+ // It contains a CordbUnmannagedThread which may need to be released.
+ CordbUnmanagedEvent *m_unmanagedEventQueue;
+ CordbUnmanagedEvent *m_lastQueuedUnmanagedEvent;
+ CordbUnmanagedEvent *m_outOfBandEventQueue;
+ CordbUnmanagedEvent *m_lastQueuedOOBEvent;
+
+ BYTE* m_pPatchTable;
+ BYTE *m_rgData;
+ void *m_pbRemoteBuf;
+
+ RESOLVED
+ // Nutered
+ CordbHashTable m_userThreads;
+ CordbHashTable m_appDomains;
+
+ // Cleaned up in ExitProcess
+ DebuggerIPCEvent* m_queuedEventList;
+
+ CordbHashTable m_steppers; // Closed in ~CordbProcess
+
+ // Closed in CloseIPCEventHandles called from ~CordbProcess
+ HANDLE m_leftSideEventAvailable;
+ HANDLE m_leftSideEventRead;
+
+ // Closed in ~CordbProcess
+ HANDLE m_handle;
+ HANDLE m_leftSideUnmanagedWaitEvent;
+ HANDLE m_stopWaitEvent;
+
+ // Deleted in ~CordbProcess
+ CRITICAL_SECTION m_processMutex;
+
+*/
+
+
+CordbProcess::~CordbProcess()
+{
+ LOG((LF_CORDB, LL_INFO1000, "CP::~CP: deleting process 0x%08x\n", this));
+
+ DTOR_ENTRY(this);
+
+ _ASSERTE(IsNeutered());
+
+ _ASSERTE(m_cordb == NULL);
+
+ // We shouldn't still be in Cordb's list of processes. Unfortunately, our root Cordb object
+ // may have already been deleted b/c we're at the mercy of ref-counting, so we can't check.
+
+ _ASSERTE(m_sharedAppDomain == NULL);
+
+ m_processMutex.Destroy();
+ m_StopGoLock.Destroy();
+
+ // These handles were cleared in neuter
+ _ASSERTE(m_handle == NULL);
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ _ASSERTE(m_leftSideUnmanagedWaitEvent == NULL);
+#endif // FEATURE_INTEROP_DEBUGGING
+ _ASSERTE(m_stopWaitEvent == NULL);
+
+ // Set this to mark that we really did cleanup.
+}
+
+//-----------------------------------------------------------------------------
+// Static build helper.
+// This will create a process under the pCordb root, and add it to the list.
+// We don't return the process - caller gets the pid and looks it up under
+// the Cordb object.
+//
+// Arguments:
+// pCordb - Pointer to the implementation of the owning Cordb object implementing the
+// owning ICD interface.
+// szProgramName - Name of the program to execute.
+// szProgramArgs - Command line arguments for the process.
+// lpProcessAttributes - OS-specific attributes for process creation.
+// lpThreadAttributes - OS-specific attributes for thread creation.
+// fInheritFlags - OS-specific flag for child process inheritance.
+// dwCreationFlags - OS-specific creation flags.
+// lpEnvironment - OS-specific environmental strings.
+// szCurrentDirectory - OS-specific string for directory to run in.
+// lpStartupInfo - OS-specific info on startup.
+// lpProcessInformation - OS-specific process information buffer.
+// corDebugFlags - What type of process to create, currently always managed.
+//-----------------------------------------------------------------------------
+HRESULT ShimProcess::CreateProcess(
+ Cordb * pCordb,
+ ICorDebugRemoteTarget * pRemoteTarget,
+ LPCWSTR szProgramName,
+ __in_z LPWSTR szProgramArgs,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL fInheritHandles,
+ DWORD dwCreationFlags,
+ PVOID lpEnvironment,
+ LPCWSTR szCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation,
+ CorDebugCreateProcessFlags corDebugFlags
+)
+{
+ _ASSERTE(pCordb != NULL);
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ // The transport cannot deal with creating a suspended process (it needs the debugger to start up and
+ // listen for connections).
+ _ASSERTE((dwCreationFlags & CREATE_SUSPENDED) == 0);
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+ HRESULT hr = S_OK;
+
+ RSExtSmartPtr<ShimProcess> pShim;
+ EX_TRY
+ {
+ pShim.Assign(new ShimProcess());
+
+ // Indicate that this process was started under the debugger as opposed to attaching later.
+ pShim->m_attached = false;
+
+ hr = pShim->CreateAndStartWin32ET(pCordb);
+ IfFailThrow(hr);
+
+ // Call out to newly created Win32-event Thread to create the process.
+ // If this succeeds, new CordbProcess will add a ref to the ShimProcess
+ hr = pShim->GetWin32EventThread()->SendCreateProcessEvent(pShim->GetMachineInfo(),
+ szProgramName,
+ szProgramArgs,
+ lpProcessAttributes,
+ lpThreadAttributes,
+ fInheritHandles,
+ dwCreationFlags,
+ lpEnvironment,
+ szCurrentDirectory,
+ lpStartupInfo,
+ lpProcessInformation,
+ corDebugFlags);
+ IfFailThrow(hr);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // If this succeeds, then process takes ownership of thread. Else we need to kill it.
+ if (FAILED(hr))
+ {
+ if (pShim != NULL)
+ {
+ pShim->Dispose();
+ }
+ }
+ // Always release our ref to ShimProcess. If the Process was created, then it takes a reference.
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Static build helper for the attach case.
+// On success, this will add the process to the pCordb list, and then
+// callers can look it up there by pid.
+//
+// Arguments:
+// pCordb - root under which this all lives
+// dwProcessID - OS process ID to attach to
+// fWin32Attach - are we interop debugging?
+//-----------------------------------------------------------------------------
+HRESULT ShimProcess::DebugActiveProcess(
+ Cordb * pCordb,
+ ICorDebugRemoteTarget * pRemoteTarget,
+ DWORD dwProcessID,
+ BOOL fWin32Attach
+)
+{
+ _ASSERTE(pCordb != NULL);
+
+ HRESULT hr = S_OK;
+
+ RSExtSmartPtr<ShimProcess> pShim;
+
+ EX_TRY
+ {
+ pShim.Assign(new ShimProcess());
+
+ // Indicate that this process was attached to, asopposed to being started under the debugger.
+ pShim->m_attached = true;
+
+ hr = pShim->CreateAndStartWin32ET(pCordb);
+ IfFailThrow(hr);
+
+ // If this succeeds, new CordbProcess will add a ref to the ShimProcess
+ hr = pShim->GetWin32EventThread()->SendDebugActiveProcessEvent(pShim->GetMachineInfo(),
+ dwProcessID,
+ fWin32Attach == TRUE,
+ NULL);
+ IfFailThrow(hr);
+
+ _ASSERTE(SUCCEEDED(hr));
+
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ // Don't do this when we are remote debugging since we won't be getting the loader breakpoint.
+ // We don't support JIT attach in remote debugging scenarios anyway.
+ //
+ // When doing jit attach for pure managed debugging we allow the native attach event to be signaled
+ // after DebugActiveProcess completes which means we must wait here long enough to have set the debuggee
+ // bit indicating managed attach is coming.
+ // However in interop debugging we can't do that because there are debug events which come before the
+ // loader breakpoint (which is how far we need to get to set the debuggee bit). If we blocked
+ // DebugActiveProcess there then the debug events would be refering to an ICorDebugProcess that hasn't
+ // yet been returned to the caller of DebugActiveProcess. Instead, for interop debugging we force the
+ // native debugger to wait until it gets the loader breakpoint to set the event. Note we can't converge
+ // on that solution for the pure managed case because there is no loader breakpoint event. Hence pure
+ // managed and interop debugging each require their own solution
+ //
+ // See bugs Dev10 600873 and 595322 for examples of what happens if we wait in interop or don't wait
+ // in pure managed respectively
+ //
+ // Long term this should all go away because we won't need to set a managed attach pending bit because
+ // there shouldn't be any IPC events involved in managed attach. There might not even be a notion of
+ // being 'managed attached'
+ if(!pShim->m_fIsInteropDebugging)
+ {
+ DWORD dwHandles = 2;
+ HANDLE arrHandles[2];
+
+ arrHandles[0] = pShim->m_terminatingEvent;
+ arrHandles[1] = pShim->m_markAttachPendingEvent;
+
+ // Wait for the completion of marking pending attach bit or debugger detaching
+ WaitForMultipleObjectsEx(dwHandles, arrHandles, FALSE, INFINITE, FALSE);
+ }
+#endif //!FEATURE_DBGIPC_TRANSPORT_DI
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // If this succeeds, then process takes ownership of thread. Else we need to kill it.
+ if (FAILED(hr))
+ {
+ if (pShim!= NULL)
+ {
+ pShim->Dispose();
+ }
+ }
+
+ // Always release our ref to ShimProcess. If the Process was created, then it takes a reference.
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Neuter all of all children, but not the actual process object.
+//
+// Assumptions:
+// This clears Right-side state. Assumptions about left-side state are either:
+// 1. We're in a shutdown scenario, where all left-side state is already
+// freed.
+// 2. Caller already verified there are no left-side resources (eg, by calling
+// code:CordbProcess::IsReadyForDetach)
+// 3. Caller did code:CordbProcess::NeuterLeftSideResources first
+// to clean up left-side resources.
+//
+// Notes:
+// This could be called multiple times (code:CordbProcess::FlushAll), so
+// be sure to null out any potential dangling pointers. State may be rebuilt
+// up after each time.
+void CordbProcess::NeuterChildren()
+{
+ _ASSERTE(GetProcessLock()->HasLock());
+
+ // Frees left-side resources. See assumptions above.
+ m_LeftSideResourceCleanupList.NeuterAndClear(this);
+
+
+ m_EvalTable.Clear();
+
+
+ // Sweep neuter lists.
+ m_ExitNeuterList.NeuterAndClear(this);
+ m_ContinueNeuterList.NeuterAndClear(this);
+
+ m_userThreads.NeuterAndClear(GetProcessLock());
+
+ m_pDefaultAppDomain = NULL;
+
+ // Frees per-appdomain left-side resources. See assumptions above.
+ m_appDomains.NeuterAndClear(GetProcessLock());
+ if (m_sharedAppDomain != NULL)
+ {
+ m_sharedAppDomain->Neuter();
+ m_sharedAppDomain->InternalRelease();
+ m_sharedAppDomain = NULL;
+ }
+
+ m_steppers.NeuterAndClear(GetProcessLock());
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ m_unmanagedThreads.NeuterAndClear(GetProcessLock());
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ // Explicitly keep the Win32EventThread alive so that we can use it in the window
+ // between NeuterChildren + Neuter.
+}
+
+//-----------------------------------------------------------------------------
+// Neuter
+//
+// When the process dies, remove all the resources associated with this object.
+//
+// Notes:
+// Once we neuter ourself, we can no longer send IPC events. So this is useful
+// on detach. This will be called on FlushAll (which has Whidbey detach
+// semantics)
+//-----------------------------------------------------------------------------
+void CordbProcess::Neuter()
+{
+ // Process's Neuter is at the top of the neuter tree. So we take the process-lock
+ // here and then all child items (appdomains, modules, etc) will assert
+ // that they hold the lock.
+ _ASSERTE(!this->ThreadHoldsProcessLock());
+
+ // Take the process lock.
+ RSLockHolder lockHolder(GetProcessLock());
+
+
+ NeuterChildren();
+
+ // Release the metadata interfaces
+ m_pMetaDispenser.Clear();
+
+
+ if (m_hHelperThread != NULL)
+ {
+ CloseHandle(m_hHelperThread);
+ m_hHelperThread = NULL;
+ }
+
+ {
+ lockHolder.Release();
+ {
+ // We may still hold the Stop-Go lock.
+ // @dbgtodo - left-side resources / shutdown, shim: Currently
+ // the shim shutdown is too interwoven with CordbProcess to split
+ // it out from the locks. Must fully hoist the W32ET and make
+ // it safely outside the RS, and outside the protection of RS
+ // locks.
+ PUBLIC_API_UNSAFE_ENTRY_FOR_SHIM(this);
+
+ // Now that all of our children are neutered, it should be safe to kill the W32ET.
+ // Shutdown the shim, and this will also shutdown the W32ET.
+ // Do this outside of the process-lock so that we can shutdown the
+ // W23ET.
+ if (m_pShim != NULL)
+ {
+ m_pShim->Dispose();
+ m_pShim.Clear();
+ }
+ }
+
+ lockHolder.Acquire();
+ }
+
+ // Unload DAC, and then release our final data target references
+ FreeDac();
+ m_pDACDataTarget.Clear();
+ m_pMutableDataTarget.Clear();
+ m_pMetaDataLocator.Clear();
+
+ if (m_pEventChannel != NULL)
+ {
+ m_pEventChannel->Delete();
+ m_pEventChannel = NULL;
+ }
+
+ // Need process lock to clear the patch table
+ ClearPatchTable();
+
+ CordbProcess::CloseIPCHandles();
+
+ CordbBase::Neuter();
+
+ m_cordb.Clear();
+
+ // Need to release this reference to ourselves. Other leaf objects may still hold
+ // strong references back to this CordbProcess object.
+ _ASSERTE(m_pProcess == this);
+ m_pProcess.Clear();
+}
+
+// Wrapper to return metadata dispenser.
+//
+// Notes:
+// Does not adjust reference count of dispenser.
+// Dispenser is destroyed in code:CordbProcess::Neuter
+// Dispenser is non-null.
+IMetaDataDispenserEx * CordbProcess::GetDispenser()
+{
+ _ASSERTE(m_pMetaDispenser != NULL);
+ return m_pMetaDispenser;
+}
+
+
+void CordbProcess::CloseIPCHandles()
+{
+ INTERNAL_API_ENTRY(this);
+
+ // Close off Right Side's handles.
+ if (m_leftSideEventAvailable != NULL)
+ {
+ CloseHandle(m_leftSideEventAvailable);
+ m_leftSideEventAvailable = NULL;
+ }
+
+ if (m_leftSideEventRead != NULL)
+ {
+ CloseHandle(m_leftSideEventRead);
+ m_leftSideEventRead = NULL;
+ }
+
+ if (m_handle != NULL)
+ {
+ // @dbgtodo - We should probably add asserts to all calls to CloseHandles(), but this has been
+ // a particularly problematic spot in the past for Mac debugging.
+ BOOL fSuccess = CloseHandle(m_handle);
+ (void)fSuccess; //prevent "unused variable" error from GCC
+ _ASSERTE(fSuccess);
+
+ m_handle = NULL;
+ }
+
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ if (m_leftSideUnmanagedWaitEvent != NULL)
+ {
+ CloseHandle(m_leftSideUnmanagedWaitEvent);
+ m_leftSideUnmanagedWaitEvent = NULL;
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ if (m_stopWaitEvent != NULL)
+ {
+ CloseHandle(m_stopWaitEvent);
+ m_stopWaitEvent = NULL;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Create new OS Thread for the Win32 Event Thread (the thread used in interop-debugging to sniff
+// native debug events). This is 1:1 w/ a CordbProcess object.
+// This will then be used to actuall create the CordbProcess object.
+// The process object will then take ownership of the thread.
+//
+// Arguments:
+// pCordb - the root object that the process lives under
+//
+// Return values:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT ShimProcess::CreateAndStartWin32ET(Cordb * pCordb)
+{
+
+ //
+ // Create the win32 event listening thread
+ //
+ CordbWin32EventThread * pWin32EventThread = new (nothrow) CordbWin32EventThread(pCordb, this);
+
+ HRESULT hr = S_OK;
+
+ if (pWin32EventThread != NULL)
+ {
+ hr = pWin32EventThread->Init();
+
+ if (SUCCEEDED(hr))
+ {
+ hr = pWin32EventThread->Start();
+ }
+
+ if (FAILED(hr))
+ {
+ delete pWin32EventThread;
+ pWin32EventThread = NULL;
+ }
+ }
+ else
+ {
+ hr = E_OUTOFMEMORY;
+ }
+
+ m_pWin32EventThread = pWin32EventThread;
+ return ErrWrapper(hr);
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Try to initialize the DAC. Called in scenarios where it may fail.
+//
+// Return Value:
+// TRUE - DAC is initialized.
+// FALSE - Not initialized, but can try again later. Common case if
+// target has not yet loaded the runtime.
+// Throws exception - fatal.
+//
+// Assumptions:
+// Target is stopped by OS, so we can safely inspect it without it moving on us.
+//
+// Notes:
+// This can be called eagerly to sniff if the LS is initialized.
+//
+//---------------------------------------------------------------------------------------
+BOOL CordbProcess::TryInitializeDac()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ // Target is stopped by OS, so we can safely inspect it without it moving on us.
+
+ // We want to avoid exceptions in the normal case, so we do some pre-checks
+ // to detect failure without relying on exceptions.
+ // Can't initialize DAC until mscorwks is loaded. So that's a sanity test.
+ HRESULT hr = EnsureClrInstanceIdSet();
+ if (FAILED(hr))
+ {
+ return FALSE;
+ }
+
+ // By this point, we know which CLR in the target to debug. That means there is a CLR
+ // in the target, and it's safe to initialize DAC.
+ _ASSERTE(m_clrInstanceId != 0);
+
+ // Now expect it to succeed
+ InitializeDac();
+ return TRUE;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Load & Init DAC, expecting to succeed.
+//
+// Return Value:
+// Throws on failure.
+//
+// Assumptions:
+// Caller invokes this at a point where they can expect it to succeed.
+// This is called early in the startup path because DAC is needed for accessing
+// data in the target.
+//
+// Notes:
+// This needs to succeed, and should always succeed (baring a bad installation)
+// so we assert on failure paths.
+// This may be called mutliple times.
+//
+//---------------------------------------------------------------------------------------
+void CordbProcess::InitializeDac()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+ INTERNAL_API_ENTRY(this);
+
+ // For Mac debugginger, m_hDacModule is not used, and it will always be NULL. To check whether DAC has
+ // been initialized, we need to check something else, namely m_pDacPrimitives.
+ if (m_pDacPrimitives == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "About to load DAC\n"));
+ CreateDacDbiInterface(); // throws
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000, "Dac already loaded, 0x%p\n", (HMODULE)m_hDacModule));
+ }
+
+ // Always flush dac.
+ ForceDacFlush();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Free DAC resources
+//
+// Notes:
+// This should clean up state such that code:CordbProcess::InitializeDac could be called again.
+//
+//---------------------------------------------------------------------------------------
+void CordbProcess::FreeDac()
+{
+ CONTRACTL
+ {
+ NOTHROW; // backout code.
+ }
+ CONTRACTL_END;
+
+ if (m_pDacPrimitives != NULL)
+ {
+ m_pDacPrimitives->Destroy();
+ m_pDacPrimitives = NULL;
+ }
+
+ if (m_hDacModule != NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "Unloading DAC\n"));
+ m_hDacModule.Clear();
+ }
+}
+
+IEventChannel * CordbProcess::GetEventChannel()
+{
+ _ASSERTE(m_pEventChannel != NULL);
+ return m_pEventChannel;
+}
+
+//---------------------------------------------------------------------------------------
+// Mark that the process is being interop-debugged.
+//
+// Notes:
+// @dbgtodo shim: this should eventually move into the shim or go away.
+// It's only to support V2 legacy interop-debugging.
+// Called after code:CordbProcess::Init if we want to enable interop debugging.
+// This allows us to separate out Interop-debugging flags from the core initialization,
+// and paves the way for us to eventually remove it.
+//
+// Since we're always on the naitve-pipeline, the Enabling interop debugging just changes
+// how the native debug events are being handled. So this must be called after Init, but
+// before any events are actually handled.
+// This mus be calle on the win32 event thread to gaurantee that it's called before WFDE.
+void CordbProcess::EnableInteropDebugging()
+{
+ CONTRACTL
+ {
+ THROWS;
+ PRECONDITION(m_pShim != NULL);
+ }
+ CONTRACTL_END;
+
+ // Must be on W32ET to gaurantee that we're called after Init yet before WFDE (which
+ // are both called on the W32et).
+ _ASSERTE(IsWin32EventThread());
+#ifdef FEATURE_INTEROP_DEBUGGING
+
+ m_state |= PS_WIN32_ATTACHED;
+ if (GetDCB() != NULL)
+ {
+ GetDCB()->m_rightSideIsWin32Debugger = true;
+ UpdateLeftSideDCBField(&(GetDCB()->m_rightSideIsWin32Debugger), sizeof(GetDCB()->m_rightSideIsWin32Debugger));
+ }
+
+ // Tell the Shim we're interop-debugging.
+ m_pShim->SetIsInteropDebugging(true);
+#else
+ ThrowHR(CORDBG_E_INTEROP_NOT_SUPPORTED);
+#endif
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Init -- create any objects that the process object needs to operate.
+//
+// Arguments:
+//
+// Return Value:
+// S_OK on success
+//
+// Assumptions:
+// Called on Win32 Event Thread, after OS debugging pipeline is established but
+// before WaitForDebugEvent / ContinueDebugEvent. This means the target is stopped.
+//
+// Notes:
+// To enable interop-debugging, call code:CordbProcess::EnableInteropDebugging
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::Init()
+{
+ INTERNAL_API_ENTRY(this);
+
+ HRESULT hr = S_OK;
+ BOOL fIsLSStarted = FALSE; // see meaning below.
+
+ FAIL_IF_NEUTERED(this);
+
+
+ EX_TRY
+ {
+ m_processMutex.Init("Process Lock", RSLock::cLockReentrant, RSLock::LL_PROCESS_LOCK);
+ m_StopGoLock.Init("Stop-Go Lock", RSLock::cLockReentrant, RSLock::LL_STOP_GO_LOCK);
+
+#ifdef _DEBUG
+ m_appDomains.DebugSetRSLock(GetProcessLock());
+ m_userThreads.DebugSetRSLock(GetProcessLock());
+#ifdef FEATURE_INTEROP_DEBUGGING
+ m_unmanagedThreads.DebugSetRSLock(GetProcessLock());
+#endif
+ m_steppers.DebugSetRSLock(GetProcessLock());
+#endif
+
+ // See if the data target is mutable, and cache the mutable interface if it is
+ // We must initialize this before we try to use the data target to access the memory in the target process.
+ m_pMutableDataTarget.Clear(); // if we were called already, release
+ hr = m_pDACDataTarget->QueryInterface(IID_ICorDebugMutableDataTarget, (void**)&m_pMutableDataTarget);
+ if (!SUCCEEDED(hr))
+ {
+ // The data target doesn't support mutation. We'll fail any requests that require mutation.
+ m_pMutableDataTarget.Assign(new ReadOnlyDataTargetFacade());
+ }
+
+ m_pMetaDataLocator.Clear();
+ hr = m_pDACDataTarget->QueryInterface(IID_ICorDebugMetaDataLocator, reinterpret_cast<void **>(&m_pMetaDataLocator));
+
+ // Get the metadata dispenser.
+ hr = InternalCreateMetaDataDispenser(IID_IMetaDataDispenserEx, (void **)&m_pMetaDispenser);
+
+ // We statically link in the dispenser. We expect it to succeed, except for OOM, which
+ // debugger doesn't yet handle.
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ IfFailThrow(hr);
+
+ _ASSERTE(m_pMetaDispenser != NULL);
+
+ // In order to allow users to call the metadata reader from multiple threads we need to set
+ // a flag on the dispenser to create threadsafe readers. This is done best-effort but
+ // really shouldn't ever fail. See issue 696511.
+ VARIANT optionValue;
+ VariantInit(&optionValue);
+ V_VT(&optionValue) = VT_UI4;
+ V_UI4(&optionValue) = MDThreadSafetyOn;
+ m_pMetaDispenser->SetOption(MetaDataThreadSafetyOptions, &optionValue);
+
+ //
+ // Setup internal events.
+ // @dbgtodo shim: these events should eventually be in the shim.
+ //
+
+
+ // Managed debugging is built on the native-pipeline, and that will detect against double-attaches.
+
+ // @dbgtodo shim: In V2, LSEA + LSER were used by the LS's helper thread. Now with the V3 pipeline,
+ // that helper-thread uses native-debug events. The W32ET gets those events and then uses LSEA, LSER to
+ // signal existing RS infrastructure. Eventually get rid of LSEA, LSER completely.
+ //
+
+ m_leftSideEventAvailable = WszCreateEvent(NULL, FALSE, FALSE, NULL);
+ if (m_leftSideEventAvailable == NULL)
+ {
+ ThrowLastError();
+ }
+
+ m_leftSideEventRead = WszCreateEvent(NULL, FALSE, FALSE, NULL);
+ if (m_leftSideEventRead == NULL)
+ {
+ ThrowLastError();
+ }
+
+ m_stopWaitEvent = WszCreateEvent(NULL, TRUE, FALSE, NULL);
+ if (m_stopWaitEvent == NULL)
+ {
+ ThrowLastError();
+ }
+
+ if (m_pShim != NULL)
+ {
+ // Get a handle to the debuggee.
+ // This is not needed in the V3 pipeline because we don't assume we have a live, local, process.
+ m_handle = GetShim()->GetNativePipeline()->GetProcessHandle();
+
+ if (m_handle == NULL)
+ {
+ ThrowLastError();
+ }
+ }
+
+ // The LS startup goes through the following phases:
+ // 1) mscorwks not yet loaded (eg, any unmanaged app)
+ // 2) mscorwks loaded (DAC can now be used)
+ // 3) IPC Block created at OS level
+ // 4) IPC block data initialized (so we can read meainingful data from it)
+ // 5) LS marks that it's initialized (queryable by a DAC primitive) (may not be atomic)
+ // 6) LS fires a "Startup" exception (sniffed by WFDE).
+ //
+ // LS is currently stopped by OS debugging, so it's doesn't shift phases.
+ // From the RS's perspective:
+ // - after phase 5 is an attach
+ // - before phase 6 is a launch.
+ // This means there's an overlap: if we catch it at phase 5, we'll just get
+ // an extra Startup exception from phase 6, which is safe. This overlap is good
+ // because it means there's no bad window to do an attach in.
+
+ // fIsLSStarted means before phase 6 (eg, RS should expect a startup exception)
+
+ // Determines if the LS is started.
+
+ {
+ BOOL fReady = TryInitializeDac();
+
+ if (fReady)
+ {
+ // Invoke DAC primitive.
+ _ASSERTE(m_pDacPrimitives != NULL);
+ fIsLSStarted = m_pDacPrimitives->IsLeftSideInitialized();
+ }
+ else
+ {
+ _ASSERTE(m_pDacPrimitives == NULL);
+
+ // DAC is not yet loaded, so we're at least before phase 2, which is before phase 6.
+ // So leave fIsLSStarted = false. We'll get a startup exception later.
+ _ASSERTE(!fIsLSStarted);
+ }
+ }
+
+
+ if (fIsLSStarted)
+ {
+ // Left-side has started up. This is common for Attach cases when managed-code is already running.
+
+ if (m_pShim != NULL)
+ {
+ FinishInitializeIPCChannelWorker(); // throws
+
+ // At this point, the control block is complete and all four
+ // events are available and valid for the remote process.
+
+ // Request that the process object send an Attach IPC event.
+ // This is only used in an attach case.
+ // @dbgtodo sync: this flag can go away once the
+ // shim can use real sync APIs.
+ m_fDoDelayedManagedAttached = true;
+ }
+ else
+ {
+ // In the V3 pipeline case, if we have the DD-interface, then the runtime is loaded
+ // and we consider it initialized.
+ if (IsDacInitialized())
+ {
+ m_initialized = true;
+ }
+ }
+ }
+ else
+ {
+ // LS is not started yet. This would be common for "Launch" cases.
+ // We will get a Startup Exception notification when it does start.
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (FAILED(hr))
+ {
+ CleanupHalfBakedLeftSide();
+ }
+
+ return hr;
+}
+
+
+COM_METHOD CordbProcess::CanCommitChanges(ULONG cSnapshots,
+ ICorDebugEditAndContinueSnapshot *pSnapshots[],
+ ICorDebugErrorInfoEnum **pError)
+{
+ return E_NOTIMPL;
+}
+
+COM_METHOD CordbProcess::CommitChanges(ULONG cSnapshots,
+ ICorDebugEditAndContinueSnapshot *pSnapshots[],
+ ICorDebugErrorInfoEnum **pError)
+{
+ return E_NOTIMPL;
+}
+
+
+//
+// Terminating -- places the process into the terminated state. This should
+// also get any blocking process functions unblocked so they'll return
+// a failure code.
+//
+void CordbProcess::Terminating(BOOL fDetach)
+{
+ INTERNAL_API_ENTRY(this);
+
+ LOG((LF_CORDB, LL_INFO1000,"CP::T: Terminating process 0x%x detach=%d\n", m_id, fDetach));
+ m_terminated = true;
+
+ m_cordb->ProcessStateChanged();
+
+ // Set events that may be blocking stuff.
+ // But don't set RSER unless we actually read the event. We don't block on RSER
+ // since that wait also checks the leftside's process handle.
+ SetEvent(m_leftSideEventRead);
+ SetEvent(m_leftSideEventAvailable);
+ SetEvent(m_stopWaitEvent);
+
+ if (m_pShim != NULL)
+ m_pShim->SetTerminatingEvent();
+
+ if (fDetach && (m_pEventChannel != NULL))
+ {
+ m_pEventChannel->Detach();
+ }
+}
+
+
+// Wrapper to give shim access to code:CordbProcess::QueueManagedAttachIfNeededWorker
+void CordbProcess::QueueManagedAttachIfNeeded()
+{
+ PUBLIC_API_ENTRY_FOR_SHIM(this);
+ QueueManagedAttachIfNeededWorker();
+}
+
+//---------------------------------------------------------------------------------------
+// Hook from Shim to request a managed attach IPC event
+//
+// Notes:
+// Called by shim after the loader-breakpoint is handled.
+// @dbgtodo sync: ths should go away once the shim can initiate
+// a sync
+void CordbProcess::QueueManagedAttachIfNeededWorker()
+{
+ HRESULT hrQueue = S_OK;
+
+ // m_fDoDelayedManagedAttached ensures that we only send an Attach event if the LS is actually present.
+ if (m_fDoDelayedManagedAttached && GetShim()->GetAttached())
+ {
+ RSLockHolder lockHolder(&this->m_processMutex);
+ GetDAC()->MarkDebuggerAttachPending();
+
+ hrQueue = this->QueueManagedAttach();
+ }
+
+ if (m_pShim != NULL)
+ m_pShim->SetMarkAttachPendingEvent();
+
+ IfFailThrow(hrQueue);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// QueueManagedAttach
+//
+// Send a managed attach. This is asynchronous and will return immediately.
+//
+// Return Value:
+// S_OK on success
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::QueueManagedAttach()
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ _ASSERTE(m_fDoDelayedManagedAttached);
+ m_fDoDelayedManagedAttached = false;
+
+ _ASSERTE(IsDacInitialized());
+
+ // We don't know what Queue it.
+ SendAttachProcessWorkItem * pItem = new (nothrow) SendAttachProcessWorkItem(this);
+
+ if (pItem == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ this->m_cordb->m_rcEventThread->QueueAsyncWorkItem(pItem);
+
+ return S_OK;
+}
+
+// However, we still want to synchronize.
+// @dbgtodo sync: when we hoist attaching, we can send an DB_IPCE_ASYNC_BREAK event instead or Attach
+// (for V2 semantics, we still need to synchronize the process)?
+void SendAttachProcessWorkItem::Do()
+{
+ HRESULT hr;
+
+ // This is being processed on the RCET, where it's safe to take the Stop-Go lock.
+ RSLockHolder ch(this->GetProcess()->GetStopGoLock());
+
+ DebuggerIPCEvent *event = (DebuggerIPCEvent*) _alloca(CorDBIPC_BUFFER_SIZE);
+
+ // This just acts like an async-break, which will kick off things.
+ // This will not induce any faked attach events from the VM (like it did in V2).
+ // The Left-side will still slip foward allowing the async-break to happen, so
+ // we may get normal debug events in addition to the sync-complete.
+ //
+ // 1. In the common attach case, we should just get a sync-complete.
+ // 2. In Jit-attach cases, the LS is sending an event, and so we'll get that event and then the sync-complete.
+ GetProcess()->InitAsyncIPCEvent(event, DB_IPCE_ATTACHING, VMPTR_AppDomain::NullPtr());
+
+ // This should result in a sync-complete from the Left-side, which will be raised as an exception
+ // that the debugger passes into Filter and then internally goes through code:CordbProcess::TriageSyncComplete
+ // and that triggers code:CordbRCEventThread::FlushQueuedEvents to be called on the RCET.
+ // We already pre-queued a fake CreateProcess event.
+
+ // The left-side will also mark itself as attached in response to this event.
+ // We explicitly don't mark it as attached from the right-side because we want to let the left-side
+ // synchronize first (to stop all running threads) before marking the debugger as attached.
+ LOG((LF_CORDB, LL_INFO1000, "[%x] CP::S: sending attach.\n", GetCurrentThreadId()));
+
+ hr = GetProcess()->SendIPCEvent(event, CorDBIPC_BUFFER_SIZE);
+
+ LOG((LF_CORDB, LL_INFO1000, "[%x] CP::S: sent attach.\n", GetCurrentThreadId()));
+}
+
+//---------------------------------------------------------------------------------------
+// Try to lookup a cached thread object
+//
+// Arguments:
+// vmThread - vm identifier for thread.
+//
+// Returns:
+// Thread object if cached; null if not yet cached.
+//
+// Notes:
+// This does not create the thread object if it's not cached. Caching is unpredictable,
+// and so this may appear to randomly return NULL.
+// Callers should prefer code:CordbProcess::LookupOrCreateThread unless they expicitly
+// want to check RS state.
+CordbThread * CordbProcess::TryLookupThread(VMPTR_Thread vmThread)
+{
+ return m_userThreads.GetBase(VmPtrToCookie(vmThread));
+}
+
+//---------------------------------------------------------------------------------------
+// Lookup (or create) a CordbThread object by the given volatile OS id. Returns null if not a manged thread
+//
+// Arguments:
+// dwThreadId - os thread id that a managed thread may be using.
+//
+// Returns:
+// Thread instance if there is currently a managed thread scheduled to run on dwThreadId.
+// NULL if this tid is not a valid Managed thread. (This is considered a common case)
+// Throws on error.
+//
+// Notes:
+// OS Thread ID is not fiber-safe, so this is a dangerous function to call.
+// Avoid this as much as possible. Prefer using VMPTR_Thread and
+// code:CordbProcess::LookupOrCreateThread instead of OS thread IDs.
+// See code:CordbThread::GetID for details.
+CordbThread * CordbProcess::TryLookupOrCreateThreadByVolatileOSId(DWORD dwThreadId)
+{
+ PrepopulateThreadsOrThrow();
+ return TryLookupThreadByVolatileOSId(dwThreadId);
+}
+
+//---------------------------------------------------------------------------------------
+// Lookup a cached CordbThread object by the tid. Returns null if not in the cache (which
+// includes unmanged thread)
+//
+// Arguments:
+// dwThreadId - os thread id that a managed thread may be using.
+//
+// Returns:
+// Thread instance if there is currently a managed thread scheduled to run on dwThreadId.
+// NULL if this tid is not a valid Managed thread. (This is considered a common case)
+// Throws on error.
+//
+// Notes:
+// Avoids this method:
+// * OS Thread ID is not fiber-safe, so this is a dangerous function to call.
+// * This is juts a Lookup, not LookupOrCreate, so it should only be used by methods
+// that care about the RS state (instead of just LS state).
+// Prefer using VMPTR_Thread and code:CordbProcess::LookupOrCreateThread
+//
+CordbThread * CordbProcess::TryLookupThreadByVolatileOSId(DWORD dwThreadId)
+{
+ HASHFIND find;
+ for (CordbThread * pThread = m_userThreads.FindFirst(&find);
+ pThread != NULL;
+ pThread = m_userThreads.FindNext(&find))
+ {
+ _ASSERTE(pThread != NULL);
+
+ // Get the OS tid. This returns 0 if the thread is switched out.
+ DWORD dwThreadId2 = GetDAC()->TryGetVolatileOSThreadID(pThread->m_vmThreadToken);
+ if (dwThreadId2 == dwThreadId)
+ {
+ return pThread;
+ }
+ }
+
+ // This OS thread ID does not match any managed thread id.
+ return NULL;
+}
+
+//---------------------------------------------------------------------------------------
+// Preferred CordbThread lookup routine.
+//
+// Arguments:
+// vmThread - LS thread to lookup. Must be non-null.
+//
+// Returns:
+// CordbThread instance for given vmThread. May return a previously cached
+// instance or create a new instance. Never returns NULL.
+// Throw on error.
+CordbThread * CordbProcess::LookupOrCreateThread(VMPTR_Thread vmThread)
+{
+ _ASSERTE(!vmThread.IsNull());
+
+ // Return if we have an existing instance.
+ CordbThread * pReturn = TryLookupThread(vmThread);
+ if (pReturn != NULL)
+ {
+ return pReturn;
+ }
+
+ RSInitHolder<CordbThread> pThread(new CordbThread(this, vmThread)); // throws
+ pReturn = pThread.TransferOwnershipToHash(&m_userThreads);
+
+ return pReturn;
+}
+
+
+
+
+HRESULT CordbProcess::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugProcess)
+ {
+ *pInterface = static_cast<ICorDebugProcess*>(this);
+ }
+ else if (id == IID_ICorDebugController)
+ {
+ *pInterface = static_cast<ICorDebugController*>(static_cast<ICorDebugProcess*>(this));
+ }
+ else if (id == IID_ICorDebugProcess2)
+
+ {
+ *pInterface = static_cast<ICorDebugProcess2*>(this);
+ }
+ else if (id == IID_ICorDebugProcess3)
+ {
+ *pInterface = static_cast<ICorDebugProcess3*>(this);
+ }
+ else if (id == IID_ICorDebugProcess4)
+ {
+ *pInterface = static_cast<ICorDebugProcess4*>(this);
+ }
+ else if (id == IID_ICorDebugProcess5)
+ {
+ *pInterface = static_cast<ICorDebugProcess5*>(this);
+ }
+ else if (id == IID_ICorDebugProcess7)
+ {
+ *pInterface = static_cast<ICorDebugProcess7*>(this);
+ }
+ else if (id == IID_ICorDebugProcess8)
+ {
+ *pInterface = static_cast<ICorDebugProcess8*>(this);
+ }
+#ifdef FEATURE_LEGACYNETCF_DBG_HOST_CONTROL
+ else if (id == IID_ICorDebugLegacyNetCFHostCallbackInvoker_PrivateWindowsPhoneOnly)
+ {
+ *pInterface = static_cast<ICorDebugLegacyNetCFHostCallbackInvoker_PrivateWindowsPhoneOnly*>(this);
+ }
+#endif
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugProcess*>(this));
+ }
+
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+
+
+
+// Public implementation of ICorDebugProcess4::ProcessStateChanged
+HRESULT CordbProcess::ProcessStateChanged(CorDebugStateChange eChange)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this)
+ {
+ switch(eChange)
+ {
+ case PROCESS_RUNNING:
+ FlushProcessRunning();
+ break;
+
+ case FLUSH_ALL:
+ FlushAll();
+ break;
+
+ default:
+ ThrowHR(E_INVALIDARG);
+
+ }
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+
+HRESULT CordbProcess::EnumerateHeap(ICorDebugHeapEnum **ppObjects)
+{
+ if (!ppObjects)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ EX_TRY
+ {
+ if (m_pDacPrimitives->AreGCStructuresValid())
+ {
+ CordbHeapEnum *pHeapEnum = new CordbHeapEnum(this);
+ GetContinueNeuterList()->Add(this, pHeapEnum);
+ hr = pHeapEnum->QueryInterface(__uuidof(ICorDebugHeapEnum), (void**)ppObjects);
+ }
+ else
+ {
+ hr = CORDBG_E_GC_STRUCTURES_INVALID;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbProcess::GetGCHeapInformation(COR_HEAPINFO *pHeapInfo)
+{
+ if (!pHeapInfo)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ EX_TRY
+ {
+ GetDAC()->GetGCHeapInformation(pHeapInfo);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbProcess::EnumerateHeapRegions(ICorDebugHeapSegmentEnum **ppRegions)
+{
+ if (!ppRegions)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ EX_TRY
+ {
+ DacDbiArrayList<COR_SEGMENT> segments;
+ hr = GetDAC()->GetHeapSegments(&segments);
+
+ if (SUCCEEDED(hr))
+ {
+ if (!segments.IsEmpty())
+ {
+ CordbHeapSegmentEnumerator *segEnum = new CordbHeapSegmentEnumerator(this, &segments[0], (DWORD)segments.Count());
+ GetContinueNeuterList()->Add(this, segEnum);
+ hr = segEnum->QueryInterface(__uuidof(ICorDebugHeapSegmentEnum), (void**)ppRegions);
+ }
+ else
+ {
+ hr = E_OUTOFMEMORY;
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbProcess::GetObject(CORDB_ADDRESS addr, ICorDebugObjectValue **pObject)
+{
+ HRESULT hr = S_OK;
+
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ EX_TRY
+ {
+ if (!m_pDacPrimitives->IsValidObject(addr))
+ {
+ hr = CORDBG_E_CORRUPT_OBJECT;
+ }
+ else if (pObject == NULL)
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ RSLockHolder ch(GetProcess()->GetStopGoLock());
+ RSLockHolder procLock(this->GetProcess()->GetProcessLock());
+
+ CordbAppDomain *cdbAppDomain = NULL;
+ CordbType *pType = NULL;
+ hr = GetTypeForObject(addr, &pType, &cdbAppDomain);
+
+ if (SUCCEEDED(hr))
+ {
+ _ASSERTE(pType != NULL);
+ _ASSERTE(cdbAppDomain != NULL);
+
+ DebuggerIPCE_ObjectData objData;
+ m_pDacPrimitives->GetBasicObjectInfo(addr, ELEMENT_TYPE_CLASS, cdbAppDomain->GetADToken(), &objData);
+
+ NewHolder<CordbObjectValue> pNewObjectValue(new CordbObjectValue(cdbAppDomain, pType, TargetBuffer(addr, (ULONG)objData.objSize), &objData));
+ hr = pNewObjectValue->Init();
+
+ if (SUCCEEDED(hr))
+ {
+ hr = pNewObjectValue->QueryInterface(__uuidof(ICorDebugObjectValue), (void**)pObject);
+ if (SUCCEEDED(hr))
+ pNewObjectValue.SuppressRelease();
+ }
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+
+HRESULT CordbProcess::EnumerateGCReferences(BOOL enumerateWeakReferences, ICorDebugGCReferenceEnum **ppEnum)
+{
+ if (!ppEnum)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ EX_TRY
+ {
+ CordbRefEnum *pRefEnum = new CordbRefEnum(this, enumerateWeakReferences);
+ GetContinueNeuterList()->Add(this, pRefEnum);
+ hr = pRefEnum->QueryInterface(IID_ICorDebugGCReferenceEnum, (void**)ppEnum);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT CordbProcess::EnumerateHandles(CorGCReferenceType types, ICorDebugGCReferenceEnum **ppEnum)
+{
+ if (!ppEnum)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ EX_TRY
+ {
+ CordbRefEnum *pRefEnum = new CordbRefEnum(this, types);
+ GetContinueNeuterList()->Add(this, pRefEnum);
+ hr = pRefEnum->QueryInterface(IID_ICorDebugGCReferenceEnum, (void**)ppEnum);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbProcess::EnableNGENPolicy(CorDebugNGENPolicy ePolicy)
+{
+#ifdef FEATURE_CORECLR
+ return E_NOTIMPL;
+#else
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+
+ IDacDbiInterface* pDAC = GetProcess()->GetDAC();
+ hr = pDAC->EnableNGENPolicy(ePolicy);
+
+ PUBLIC_API_END(hr);
+ return hr;
+#endif
+}
+
+
+HRESULT CordbProcess::GetTypeID(CORDB_ADDRESS obj, COR_TYPEID *pId)
+{
+ if (pId == NULL)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ EX_TRY
+ {
+ hr = GetProcess()->GetDAC()->GetTypeID(obj, pId);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbProcess::GetTypeForTypeID(COR_TYPEID id, ICorDebugType **ppType)
+{
+ if (ppType == NULL)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+
+ PUBLIC_API_ENTRY(this);
+ RSLockHolder stopGoLock(this->GetProcess()->GetStopGoLock());
+ RSLockHolder procLock(this->GetProcess()->GetProcessLock());
+
+ EX_TRY
+ {
+ DebuggerIPCE_ExpandedTypeData data;
+ GetDAC()->GetObjectExpandedTypeInfoFromID(AllBoxed, VMPTR_AppDomain::NullPtr(), id, &data);
+
+ CordbType *type = 0;
+ hr = CordbType::TypeDataToType(GetSharedAppDomain(), &data, &type);
+
+ if (SUCCEEDED(hr))
+ hr = type->QueryInterface(IID_ICorDebugType, (void**)ppType);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+
+COM_METHOD CordbProcess::GetArrayLayout(COR_TYPEID id, COR_ARRAY_LAYOUT *pLayout)
+{
+ if (pLayout == NULL)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+
+ hr = GetProcess()->GetDAC()->GetArrayLayout(id, pLayout);
+
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+COM_METHOD CordbProcess::GetTypeLayout(COR_TYPEID id, COR_TYPE_LAYOUT *pLayout)
+{
+ if (pLayout == NULL)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+
+ hr = GetProcess()->GetDAC()->GetTypeLayout(id, pLayout);
+
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+COM_METHOD CordbProcess::GetTypeFields(COR_TYPEID id, ULONG32 celt, COR_FIELD fields[], ULONG32 *pceltNeeded)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+
+ hr = GetProcess()->GetDAC()->GetObjectFields(id, celt, fields, pceltNeeded);
+
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+COM_METHOD CordbProcess::SetWriteableMetadataUpdateMode(WriteableMetadataUpdateMode flags)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+
+ if(flags != LegacyCompatPolicy &&
+ flags != AlwaysShowUpdates)
+ {
+ hr = E_INVALIDARG;
+ }
+ else if(m_pShim != NULL)
+ {
+ if(flags != LegacyCompatPolicy)
+ {
+ hr = CORDBG_E_UNSUPPORTED;
+ }
+ }
+
+ if(SUCCEEDED(hr))
+ {
+ m_writableMetadataUpdateMode = flags;
+ }
+
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+COM_METHOD CordbProcess::EnableExceptionCallbacksOutsideOfMyCode(BOOL enableExceptionsOutsideOfJMC)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+
+ hr = GetProcess()->GetDAC()->SetSendExceptionsOutsideOfJMC(enableExceptionsOutsideOfJMC);
+
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+#ifdef FEATURE_LEGACYNETCF_DBG_HOST_CONTROL
+
+COM_METHOD CordbProcess::InvokePauseCallback()
+{
+ return S_OK;
+}
+
+COM_METHOD CordbProcess::InvokeResumeCallback()
+{
+ return S_OK;
+}
+
+#endif
+
+HRESULT CordbProcess::GetTypeForObject(CORDB_ADDRESS addr, CordbType **ppType, CordbAppDomain **pAppDomain)
+{
+ VMPTR_AppDomain appDomain;
+ VMPTR_Module mod;
+ VMPTR_DomainFile domainFile;
+
+ HRESULT hr = E_FAIL;
+ if (GetDAC()->GetAppDomainForObject(addr, &appDomain, &mod, &domainFile))
+ {
+ CordbAppDomain *cdbAppDomain = appDomain.IsNull() ? GetSharedAppDomain() : LookupOrCreateAppDomain(appDomain);
+
+ _ASSERTE(cdbAppDomain);
+
+ DebuggerIPCE_ExpandedTypeData data;
+ GetDAC()->GetObjectExpandedTypeInfo(AllBoxed, appDomain, addr, &data);
+
+ CordbType *type = 0;
+ hr = CordbType::TypeDataToType(cdbAppDomain, &data, &type);
+
+ if (SUCCEEDED(hr))
+ {
+ *ppType = type;
+ if (pAppDomain)
+ *pAppDomain = cdbAppDomain;
+ }
+ }
+
+ return hr;
+}
+
+
+// ******************************************
+// CordbRefEnum
+// ******************************************
+CordbRefEnum::CordbRefEnum(CordbProcess *proc, BOOL walkWeakRefs)
+ : CordbBase(proc, 0, enumCordbHeap), mRefHandle(0), mEnumStacksFQ(TRUE),
+ mHandleMask((UINT32)(walkWeakRefs ? CorHandleAll : CorHandleStrongOnly))
+{
+}
+
+CordbRefEnum::CordbRefEnum(CordbProcess *proc, CorGCReferenceType types)
+ : CordbBase(proc, 0, enumCordbHeap), mRefHandle(0), mEnumStacksFQ(FALSE),
+ mHandleMask((UINT32)types)
+{
+}
+
+void CordbRefEnum::Neuter()
+{
+ EX_TRY
+ {
+ if (mRefHandle)
+ {
+ GetProcess()->GetDAC()->DeleteRefWalk(mRefHandle);
+ mRefHandle = 0;
+ }
+ }
+ EX_CATCH
+ {
+ _ASSERTE(!"Hit an error freeing a ref walk.");
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ CordbBase::Neuter();
+}
+
+HRESULT CordbRefEnum::QueryInterface(REFIID riid, void **ppInterface)
+{
+ if (ppInterface == NULL)
+ return E_INVALIDARG;
+
+ if (riid == IID_ICorDebugGCReferenceEnum)
+ {
+ *ppInterface = static_cast<ICorDebugGCReferenceEnum*>(this);
+ }
+ else if (riid == IID_IUnknown)
+ {
+ *ppInterface = static_cast<IUnknown*>(static_cast<ICorDebugGCReferenceEnum*>(this));
+ }
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbRefEnum::Skip(ULONG celt)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbRefEnum::Reset()
+{
+ PUBLIC_API_ENTRY(this);
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (mRefHandle)
+ {
+ GetProcess()->GetDAC()->DeleteRefWalk(mRefHandle);
+ mRefHandle = 0;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbRefEnum::Clone(ICorDebugEnum **ppEnum)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbRefEnum::GetCount(ULONG *pcelt)
+{
+ return E_NOTIMPL;
+}
+
+
+//
+
+HRESULT CordbRefEnum::Next(ULONG celt, COR_GC_REFERENCE refs[], ULONG *pceltFetched)
+{
+ if (refs == NULL || pceltFetched == NULL)
+ return E_POINTER;
+
+ CordbProcess *process = GetProcess();
+ HRESULT hr = S_OK;
+
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(process);
+
+ RSLockHolder procLockHolder(process->GetProcessLock());
+
+ EX_TRY
+ {
+ if (!mRefHandle)
+ hr = process->GetDAC()->CreateRefWalk(&mRefHandle, mEnumStacksFQ, mEnumStacksFQ, mHandleMask);
+
+ if (SUCCEEDED(hr))
+ {
+ DacGcReference dacRefs[32];
+ ULONG toFetch = _countof(dacRefs);
+ ULONG total = 0;
+
+ for (ULONG c = 0; SUCCEEDED(hr) && c < (celt/_countof(dacRefs) + 1); ++c)
+ {
+ // Fetch 32 references at a time, the last time, only fetch the remainder (that is, if
+ // the user didn't fetch a multiple of 32).
+ if (c == celt/_countof(dacRefs))
+ toFetch = celt % _countof(dacRefs);
+
+ ULONG fetched = 0;
+ hr = process->GetDAC()->WalkRefs(mRefHandle, toFetch, dacRefs, &fetched);
+
+ if (SUCCEEDED(hr))
+ {
+ for (ULONG i = 0; i < fetched; ++i)
+ {
+ CordbAppDomain *pDomain = process->LookupOrCreateAppDomain(dacRefs[i].vmDomain);
+
+ ICorDebugAppDomain *pAppDomain;
+ ICorDebugValue *pOutObject = NULL;
+ if (dacRefs[i].pObject & 1)
+ {
+ dacRefs[i].pObject &= ~1;
+ ICorDebugObjectValue *pObjValue = NULL;
+
+ hr = process->GetObject(dacRefs[i].pObject, &pObjValue);
+
+ if (SUCCEEDED(hr))
+ {
+ hr = pObjValue->QueryInterface(IID_ICorDebugValue, (void**)&pOutObject);
+ pObjValue->Release();
+ }
+ }
+ else
+ {
+ ICorDebugReferenceValue *tmpValue = NULL;
+ IfFailThrow(CordbReferenceValue::BuildFromGCHandle(pDomain,
+ dacRefs[i].objHnd,
+ &tmpValue));
+
+ if (SUCCEEDED(hr))
+ {
+ hr = tmpValue->QueryInterface(IID_ICorDebugValue, (void**)&pOutObject);
+ tmpValue->Release();
+ }
+ }
+
+ if (SUCCEEDED(hr) && pDomain)
+ {
+ hr = pDomain->QueryInterface(IID_ICorDebugAppDomain, (void**)&pAppDomain);
+ }
+
+ if (FAILED(hr))
+ break;
+
+ refs[total].Domain = pAppDomain;
+ refs[total].Location = pOutObject;
+ refs[total].Type = (CorGCReferenceType)dacRefs[i].dwType;
+ refs[total].ExtraData = dacRefs[i].i64ExtraData;
+
+ total++;
+ }
+ }
+ }
+
+ *pceltFetched = total;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+
+// ******************************************
+// CordbHeapEnum
+// ******************************************
+CordbHeapEnum::CordbHeapEnum(CordbProcess *proc)
+ : CordbBase(proc, 0, enumCordbHeap), mHeapHandle(0)
+{
+}
+
+HRESULT CordbHeapEnum::QueryInterface(REFIID riid, void **ppInterface)
+{
+ if (ppInterface == NULL)
+ return E_INVALIDARG;
+
+ if (riid == IID_ICorDebugHeapEnum)
+ {
+ *ppInterface = static_cast<ICorDebugHeapEnum*>(this);
+ }
+ else if (riid == IID_IUnknown)
+ {
+ *ppInterface = static_cast<IUnknown*>(static_cast<ICorDebugHeapEnum*>(this));
+ }
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbHeapEnum::Skip(ULONG celt)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbHeapEnum::Reset()
+{
+ Clear();
+ return S_OK;
+}
+
+void CordbHeapEnum::Clear()
+{
+ EX_TRY
+ {
+ if (mHeapHandle)
+ {
+ GetProcess()->GetDAC()->DeleteHeapWalk(mHeapHandle);
+ mHeapHandle = 0;
+ }
+ }
+ EX_CATCH
+ {
+ _ASSERTE(!"Hit an error freeing the heap walk.");
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+}
+
+HRESULT CordbHeapEnum::Clone(ICorDebugEnum **ppEnum)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbHeapEnum::GetCount(ULONG *pcelt)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbHeapEnum::Next(ULONG celt, COR_HEAPOBJECT objects[], ULONG *pceltFetched)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ RSLockHolder stopGoLock(this->GetProcess()->GetStopGoLock());
+ RSLockHolder procLock(this->GetProcess()->GetProcessLock());
+ ULONG fetched = 0;
+
+ EX_TRY
+ {
+ if (mHeapHandle == 0)
+ {
+ hr = GetProcess()->GetDAC()->CreateHeapWalk(&mHeapHandle);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ hr = GetProcess()->GetDAC()->WalkHeap(mHeapHandle, celt, objects, &fetched);
+ _ASSERTE(fetched <= celt);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ // Return S_FALSE if we've reached the end of the enum.
+ if (fetched < celt)
+ hr = S_FALSE;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // Set the fetched parameter to reflect the number of elements (if any)
+ // that were successfully saved to "objects"
+ if (pceltFetched)
+ *pceltFetched = fetched;
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Flush state for when the process starts running.
+//
+// Notes:
+// Helper for code:CordbProcess::ProcessStateChanged.
+// Since ICD Arrowhead does not own the eventing pipeline, it needs the debugger to
+// notifying it of when the process is running again. This is like the counterpart
+// to code:CordbProcess::Filter
+void CordbProcess::FlushProcessRunning()
+{
+ _ASSERTE(GetProcessLock()->HasLock());
+
+ // Update the continue counter.
+ m_continueCounter++;
+
+ // Safely dispose anything that should be neutered on continue.
+ MarkAllThreadsDirty();
+ ForceDacFlush();
+}
+
+//---------------------------------------------------------------------------------------
+// Flush all cached state and bring us back to "cold startup"
+//
+// Notes:
+// Helper for code:CordbProcess::ProcessStateChanged.
+// This is used if the data-target changes underneath us in a way that is
+// not consistent with the process running forward. For example, if for
+// a time-travel debugger, the data-target may flow "backwards" in time.
+//
+void CordbProcess::FlushAll()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr;
+ _ASSERTE(GetProcessLock()->HasLock());
+
+ //
+ // First, determine if it's safe to Flush
+ //
+
+ hr = IsReadyForDetach();
+ IfFailThrow(hr);
+
+ // Check for outstanding CordbHandle values.
+ if (OutstandingHandles())
+ {
+ ThrowHR(CORDBG_E_DETACH_FAILED_OUTSTANDING_TARGET_RESOURCES);
+ }
+
+ // FlushAll is a superset of FlushProcessRunning.
+ // This will also ensure we clear the DAC cache.
+ FlushProcessRunning();
+
+ // If we detach before the CLR is loaded into the debuggee, then we can no-op a lot of work.
+ // We sure can't be sending IPC events to the LS before it exists.
+ NeuterChildren();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Detach the Debugger from the LS process.
+//
+//
+// Return Value:
+// S_OK on successful detach. Else errror.
+//
+// Assumptions:
+// Target is stopped.
+//
+// Notes:
+// Once we're detached, the LS can resume running and exit.
+// So it's possible to get an ExitProcess callback in the middle of the Detach phase. If that happens,
+// we must return CORDBG_E_PROCESS_TERMINATED and pretend that the exit happened before we tried to detach.
+// Else if we detach successfully, return S_OK.
+//
+// @dbgtodo attach-bit: need to figure out semantics of Detach
+// in V3, especially w.r.t to an attach bit.
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::Detach()
+{
+ PUBLIC_API_ENTRY(this);
+
+ FAIL_IF_NEUTERED(this);
+
+ if (IsInteropDebugging())
+ {
+ return CORDBG_E_INTEROP_NOT_SUPPORTED;
+ }
+
+
+ HRESULT hr = S_OK;
+ // A very important note: we require that the process is synchronized before doing a detach. This ensures
+ // that no events are on their way from the Left Side. We also require that the user has drained the
+ // managed event queue, but there is currently no way to really enforce that here.
+ // @todo- why can't we enforce that the managed event Q is drained?
+ ATT_REQUIRE_SYNCED_OR_NONINIT_MAY_FAIL(this);
+
+
+ hr = IsReadyForDetach();
+ if (FAILED(hr))
+ {
+ // Avoid neutering. Gives client a chance to fix detach issue and retry.
+ return hr;
+ }
+
+ // Since the detach may resume the LS and allow it to exit, which may invoke the EP callback
+ // which may destroy this process object, be sure to protect us w/ an extra AddRef/Release
+ RSSmartPtr<CordbProcess> pRef(this);
+
+
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::Detach - beginning\n"));
+ if (m_pShim == NULL) // This API is moved off to the shim
+ {
+
+ // This is still invasive.
+ // Ignore failures. This will fail for a non-invasive target.
+ if (IsDacInitialized())
+ {
+ HRESULT hrIgnore = S_OK;
+ EX_TRY
+ {
+ GetDAC()->MarkDebuggerAttached(FALSE);
+ }
+ EX_CATCH_HRESULT(hrIgnore);
+ }
+ }
+ else
+ {
+ EX_TRY
+ {
+ DetachShim();
+ }
+ EX_CATCH_HRESULT(hr);
+ }
+
+ // Either way, neuter everything.
+ this->Neuter();
+
+ // Implicit release on pRef
+ LOG((LF_CORDB, LL_INFO1000, "CP::Detach - returning w/ hr=0x%x\n", hr));
+ return hr;
+}
+
+// Free up key left-side resources
+//
+// Called on detach
+// This does key neutering of objects that hold left-side resources and require
+// preemptively freeing the resources.
+// After this, code:CordbProcess::Neuter should only affect right-side state.
+void CordbProcess::NeuterChildrenLeftSideResources()
+{
+ _ASSERTE(GetStopGoLock()->HasLock());
+
+ _ASSERTE(!GetProcessLock()->HasLock());
+ RSLockHolder lockHolder(GetProcessLock());
+
+
+ // Need process-lock to operate on hashtable, but can't yet Neuter under process-lock,
+ // so we have to copy the contents to an auxilary list which we can then traverse outside the lock.
+ RSPtrArray<CordbAppDomain> listAppDomains;
+ m_appDomains.CopyToArray(&listAppDomains);
+
+
+
+ // Must not hold process lock so that we can be safe to send IPC events
+ // to cleanup left-side resources.
+ lockHolder.Release();
+ _ASSERTE(!GetProcessLock()->HasLock());
+
+ // Frees left-side resources. This may send IPC events.
+ // This will make normal neutering a nop.
+ m_LeftSideResourceCleanupList.NeuterLeftSideResourcesAndClear(this);
+
+ for(unsigned int idx = 0; idx < listAppDomains.Length(); idx++)
+ {
+ CordbAppDomain * pAppDomain = listAppDomains[idx];
+
+ // CordbHandleValue is in the appdomain exit list, and that needs
+ // to send an IPC event to cleanup and release the handle from
+ // the GCs handle table.
+ pAppDomain->GetSweepableExitNeuterList()->NeuterLeftSideResourcesAndClear(this);
+ }
+ listAppDomains.Clear();
+
+}
+
+//---------------------------------------------------------------------------------------
+// Detach the Debugger from the LS process for the V2 case
+//
+// Assumptions:
+// This will NeuterChildren(), caller will do the real Neuter()
+// Caller has already ensured that detach is safe.
+//
+// @dbgtodo attach-bit: this should be moved into the shim; need
+// to figure out semantics for freeing left-side resources (especially GC
+// handles) on detach.
+void CordbProcess::DetachShim()
+{
+
+ HASHFIND hashFind;
+ HRESULT hr = S_OK;
+
+ // If we detach before the CLR is loaded into the debuggee, then we can no-op a lot of work.
+ // We sure can't be sending IPC events to the LS before it exists.
+ if (m_initialized)
+ {
+ // The managed event queue is not necessarily drained. Cordbg could call detach between any callback.
+ // While the process is still stopped, neuter all of our children.
+ // This will make our Neuter() a nop and saves the W32ET from having to do dangerous work.
+ this->NeuterChildrenLeftSideResources();
+ {
+ RSLockHolder lockHolder(GetProcessLock());
+ this->NeuterChildren();
+ }
+
+ // Go ahead and detach from the entire process now. This is like sending a "Continue".
+ DebuggerIPCEvent * pIPCEvent = (DebuggerIPCEvent *) _alloca(CorDBIPC_BUFFER_SIZE);
+ InitIPCEvent(pIPCEvent, DB_IPCE_DETACH_FROM_PROCESS, true, VMPTR_AppDomain::NullPtr());
+
+ hr = m_cordb->SendIPCEvent(this, pIPCEvent, CorDBIPC_BUFFER_SIZE);
+ hr = WORST_HR(hr, pIPCEvent->hr);
+ IfFailThrow(hr);
+ }
+ else
+ {
+ // @dbgtodo attach-bit: push this up, once detach IPC event is hoisted.
+ RSLockHolder lockHolder(GetProcessLock());
+
+ // Shouldn't have any appdomains.
+ (void)hashFind; //prevent "unused variable" error from GCC
+ _ASSERTE(m_appDomains.FindFirst(&hashFind) == NULL);
+ }
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::Detach - got reply from LS\n"));
+
+ // It's possible that the LS may exit after they reply to our detach_from_process, but
+ // before we update our internal state that they're detached. So still have to check
+ // failure codes here.
+ hr = this->m_pShim->GetWin32EventThread()->SendDetachProcessEvent(this);
+
+
+ // Since we're auto-continuing when we detach, we should set the stop count back to zero.
+ // This (along w/ m_detached) prevents anyone from calling Continue on this process
+ // after this call returns.
+ m_stopCount = 0;
+
+ if (hr != CORDBG_E_PROCESS_TERMINATED)
+ {
+ // Remember that we've detached from this process object. This will prevent any further operations on
+ // this process, just in case... :)
+ // If LS exited, then don't set this flag because it overrides m_terminated when reporting errors;
+ // and we want to provide a consistent story about whether we detached or whether the LS exited.
+ m_detached = true;
+ }
+ IfFailThrow(hr);
+
+
+ // Now that all complicated cleanup is done, caller can do a final neuter.
+ // This will implicitly stop our Win32 event thread as well.
+}
+
+// Delete all events from the queue without dispatching. This is useful in shutdown.
+// An event that is currently dispatching is not on the queue.
+void CordbProcess::DeleteQueuedEvents()
+{
+ INTERNAL_API_ENTRY(this);
+ // We must have the process lock to ensure that no one is trying to add an event
+ _ASSERTE(!ThreadHoldsProcessLock());
+
+ if (m_pShim != NULL)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(this);
+
+ // DeleteAll() is part of the shim, and it will change external ref counts, so must really
+ // be marked as outside the RS.
+ m_pShim->GetManagedEventQueue()->DeleteAll();
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Track that we're about to dispatch a managed event.
+//
+// Arguments:
+// event - event being dispatched
+//
+// Assumptions:
+// This is used to support code:CordbProcess::AreDispatchingEvent
+// This is always called on the same thread as code:CordbProcess::FinishEventDispatch
+void CordbProcess::StartEventDispatch(DebuggerIPCEventType event)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE(m_dispatchedEvent == DB_IPCE_DEBUGGER_INVALID);
+ _ASSERTE(event != DB_IPCE_DEBUGGER_INVALID);
+ m_dispatchedEvent = event;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Track that we're done dispatching a managed event.
+//
+//
+// Assumptions:
+// This is always called on the same thread as code:CordbProcess::StartEventDispatch
+//
+// Notes:
+// @dbgtodo shim: eventually this goes into the shim when we hoist Continue
+void CordbProcess::FinishEventDispatch()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE(m_dispatchedEvent != DB_IPCE_DEBUGGER_INVALID);
+ m_dispatchedEvent = DB_IPCE_DEBUGGER_INVALID;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Are we in the middle of dispatching an event?
+//
+// Notes:
+// This is used by code::CordbProcess::ContinueInternal. Continue logic takes
+// a shortcut if the continue is called on the dispatch thread.
+// It doesn't matter which event is being dispatch; only that we're on the dispatch thread.
+// @dbgtodo shim: eventually this goes into the shim when we hoist Continue
+bool CordbProcess::AreDispatchingEvent()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return m_dispatchedEvent != DB_IPCE_DEBUGGER_INVALID;
+}
+
+
+
+
+
+// Terminate the app. We'll still dispatch an ExitProcess callback, so the app
+// must wait for that before calling Cordb::Terminate.
+// If this fails, the client can always call the OS's TerminateProcess command
+// to rudely kill the debuggee.
+HRESULT CordbProcess::Terminate(unsigned int exitCode)
+{
+ PUBLIC_API_ENTRY(this);
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::Terminate: with exitcode %u\n", exitCode));
+ FAIL_IF_NEUTERED(this);
+
+
+ // @dbgtodo shutdown: eventually, all of Terminate() will be in the Shim.
+ // Free all the remaining events. Since this will call into the shim, do this outside of any locks.
+ // (ATT_ takes locks).
+ DeleteQueuedEvents();
+
+ ATT_REQUIRE_SYNCED_OR_NONINIT_MAY_FAIL(this);
+
+ // When we terminate the process, it's handle will become signaled and
+ // Win32 Event Thread will leap into action and call CordbWin32EventThread::ExitProcess
+ // Unfortunately, that may destroy this object if the ExitProcess callback
+ // decides to call Release() on the process.
+
+
+ // Indicate that the process is exiting so that (among other things) we don't try and
+ // send messages to the left side while it's being deleted.
+ Lock();
+
+ // In case we're continuing from the loader bp, we don't want to try and kick off an attach. :)
+ m_fDoDelayedManagedAttached = false;
+ m_exiting = true;
+
+
+
+ // We'd like to just take a lock around everything here, but that may deadlock us
+ // since W32ET will wait on the lock, and Continue may wait on W32ET.
+ // So we just do an extra AddRef/Release to make sure we're still around.
+ // @todo - could we move this smartptr up so that it's well-nested w/ the lock?
+ RSSmartPtr<CordbProcess> pRef(this);
+
+ Unlock();
+
+
+ // At any point after this call, the w32 ET may run the ExitProcess code which will race w/ the continue call.
+ // This call only posts a request that the process terminate and does not guarantee the process actually
+ // terminates. In particular, the process can not exit until any outstanding IO requests are done (on cancelled).
+ // It also can not exit if we have an outstanding not-continued native-debug event.
+ // Fortunately, the interesting work in terminate is done in ExitProcessWorkItem::Do, which can take the Stop-Go lock.
+ // Since we're currently holding the stop-go lock, that means we at least get some serialization.
+ //
+ // Note that on Windows, the process isn't really terminated until we receive the EXIT_PROCESS_DEBUG_EVENT.
+ // Before then, we can still still access the debuggee's address space. On the other, for Mac debugging,
+ // the process can die any time after this call, and so we can no longer call into the DAC.
+ GetShim()->GetNativePipeline()->TerminateProcess(exitCode);
+
+ // We just call Continue() so that the debugger doesn't have to. (It's arguably odd
+ // to call Continue() after Terminate).
+ // We're stopped & Synced.
+ // For interop-debugging this is very important because the Terminate may not really kill the process
+ // until after we continue from the current native debug event.
+ ContinueInternal(FALSE);
+
+ // Implicit release on pRef here (since it's going out of scope)...
+ // After this release, this object may be destroyed. So don't use any member functions
+ // (including Locks) after here.
+
+
+ return S_OK;
+}
+
+// This can be called at any time, even if we're in an unrecoverable error state.
+HRESULT CordbProcess::GetID(DWORD *pdwProcessId)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ OK_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pdwProcessId, DWORD *);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // This shouldn't be used in V3 paths. Normally, we can enforce that by checking against
+ // m_pShim. However, this API can be called after being neutered, in which case m_pShim is cleared.
+ // So check against 0 instead.
+ if (m_id == 0)
+ {
+ *pdwProcessId = 0;
+ ThrowHR(E_NOTIMPL);
+ }
+ *pdwProcessId = GetPid();
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+// Helper to get PID internally. We know we'll always succeed.
+// This is more convient for internal callers since they can just use it as an expression
+// without having to check HRESULTS.
+DWORD CordbProcess::GetPid()
+{
+ // This shouldn't be used in V3 paths, in which case it's set to 0. Only the shim should be
+ // calling this. Assert to catch anybody else.
+ _ASSERTE(m_id != 0);
+
+ return (DWORD) m_id;
+}
+
+
+HRESULT CordbProcess::GetHandle(HANDLE *phProcessHandle)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this); // Once we neuter the process, we close our OS handle to it.
+ VALIDATE_POINTER_TO_OBJECT(phProcessHandle, HANDLE *);
+
+ if (m_pShim == NULL)
+ {
+ _ASSERTE(!"CordbProcess::GetHandle() should be not be called on the new architecture");
+ *phProcessHandle = NULL;
+ return E_NOTIMPL;
+ }
+ else
+ {
+ *phProcessHandle = m_handle;
+ return S_OK;
+ }
+}
+
+HRESULT CordbProcess::IsRunning(BOOL *pbRunning)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pbRunning, BOOL*);
+
+ *pbRunning = !GetSynchronized();
+
+ return S_OK;
+}
+
+HRESULT CordbProcess::EnableSynchronization(BOOL bEnableSynchronization)
+{
+ /* !!! */
+ PUBLIC_API_ENTRY(this);
+ return E_NOTIMPL;
+}
+
+HRESULT CordbProcess::Stop(DWORD dwTimeout)
+{
+ PUBLIC_API_ENTRY(this);
+ CORDBRequireProcessStateOK(this);
+
+ HRESULT hr = StopInternal(dwTimeout, VMPTR_AppDomain::NullPtr());
+
+ return ErrWrapper(hr);
+}
+
+HRESULT CordbProcess::StopInternal(DWORD dwTimeout, VMPTR_AppDomain pAppDomainToken)
+{
+ LOG((LF_CORDB, LL_INFO1000, "CP::S: stopping process 0x%x(%d) with timeout %d\n", m_id, m_id, dwTimeout));
+
+ INTERNAL_API_ENTRY(this);
+
+ // Stop + Continue are executed under the Stop-Go lock. This makes them atomic.
+ // We'll toggle the process-lock (b/c we communicate w/ the W32et, so just the process-lock is
+ // not sufficient to make this atomic).
+ // It's ok to take this lock before checking if the CordbProcess has been neutered because
+ // the lock is destroyed in the dtor after neutering.
+ RSLockHolder ch(&m_StopGoLock);
+
+ // Check if this CordbProcess has been neutered under the SG lock.
+ // Otherwise it's possible to race with Detach() and Terminate().
+ FAIL_IF_NEUTERED(this);
+ CORDBFailIfOnWin32EventThread(this);
+
+ if (m_pShim == NULL) // Stop/Go is moved off to the shim
+ {
+ return E_NOTIMPL;
+ }
+
+
+ DebuggerIPCEvent* event;
+ HRESULT hr = S_OK;
+
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "CP::SI, timeout=%d, this=%p\n", dwTimeout, this);
+
+ // Stop() is a syncronous (blocking) operation. Furthermore, we have no way to cancel the async-break request.
+ // Thus if we returned early on a timeout, then we'll be in a random state b/c the LS may get stopped at any
+ // later spot.
+ // One solution just require the param is INFINITE until we fix this and E_INVALIDARG if it's not.
+ // But that could be a breaking change (what if a debugger passes in a really large value that's effectively
+ // INFINITE).
+ // So we'll just ignore it and always treat it as infinite.
+ dwTimeout = INFINITE;
+
+ // Do the checks on the process state under the SG lock. This ensures that another thread cannot come in
+ // after we do the checks and take the lock before we do. For example, Detach() can race with Stop() such
+ // that:
+ // 1. Thread A calls CordbProcess::Detach() and takes the stop-go lock
+ // 2. Thread B calls CordbProcess::Stop(), passes all the checks, and then blocks on the stop-go lock
+ // 3. Thread A finishes the detach, invalides the process state, cleans all the resources, and then
+ // releases the stop-go lock
+ // 4. Thread B gets the lock, but everything has changed
+ CORDBRequireProcessStateOK(this);
+
+ Lock();
+
+ ASSERT_SINGLE_THREAD_ONLY(HoldsLock(&m_StopGoLock));
+
+ // Don't need to stop if the process hasn't even executed any managed code yet.
+ if (!m_initialized)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::S: process isn't initialized yet.\n"));
+
+ // Mark the process as synchronized so no events will be dispatched until the thing is continued.
+ SetSynchronized(true);
+
+ // Remember uninitialized stop...
+ m_uninitializedStop = true;
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // If we're Win32 attached, then suspend all the unmanaged threads in the process.
+ // We may or may not be stopped at a native debug event.
+ if (IsInteropDebugging())
+ {
+ SuspendUnmanagedThreads();
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ // Get the RC Event Thread to stop listening to the process.
+ m_cordb->ProcessStateChanged();
+
+ hr = S_OK;
+ goto Exit;
+ }
+
+ // Don't need to stop if the process is already synchronized.
+ // @todo - Issue 129917. It's possible that we'll get a call to Stop when the LS is already stopped.
+ // Sending an AsyncBreak would deadlock here (b/c the LS will ignore the frivilous request,
+ // and thus never send a SyncComplete, and thus our Waiting on the SyncComplete will deadlock).
+ // We avoid this case by checking m_syncCompleteReceived (which should roughly correspond to
+ // the LS's m_stopped variable).
+ // One window this can happen is after a Continue() pings the RCET but before the RCET actually sweeps + flushes.
+
+ if (GetSynchronized() || GetSyncCompleteRecv())
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::S: process was already synchronized. m_syncCompleteReceived=%d\n", GetSyncCompleteRecv()));
+
+ if (GetSyncCompleteRecv())
+ {
+ // We must be in that window alluded to above (while the RCET is sweeping). Re-ping the RCET.
+ SetSynchronized(true);
+ m_cordb->ProcessStateChanged();
+ }
+ hr = S_OK;
+ goto Exit;
+ }
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::S: process not sync'd, requesting stop.\n");
+
+ m_stopRequested = true;
+
+ // We don't want to dispatch any Win32 debug events while we're trying to stop.
+ // Setting m_specialDeferment=true means that any debug event we get will be queued and not dispatched.
+ // We do this to avoid a nested call to Continue.
+ // These defered events will get dispatched when somebody calls continue (and since they're calling
+ // stop now, they must call continue eventually).
+ // Note that if we got a Win32 debug event between when we took the Stop-Go lock above and now,
+ // that even may have been dispatched. We're ok because SSFW32Stop will hijack that event and continue it,
+ // and then all future events will be queued.
+ m_specialDeferment = true;
+ Unlock();
+
+ BOOL asyncBreakSent;
+
+ // We need to ensure that the helper thread is alive.
+ hr = this->StartSyncFromWin32Stop(&asyncBreakSent);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+
+ if (asyncBreakSent)
+ {
+ hr = S_OK;
+ Lock();
+
+ m_stopRequested = false;
+
+ goto Exit;
+ }
+
+ // Send the async break event to the RC.
+ event = (DebuggerIPCEvent*) _alloca(CorDBIPC_BUFFER_SIZE);
+ InitIPCEvent(event, DB_IPCE_ASYNC_BREAK, false, pAppDomainToken);
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP::S: sending async stop to appd 0x%x.\n", VmPtrToCookie(pAppDomainToken));
+
+ hr = m_cordb->SendIPCEvent(this, event, CorDBIPC_BUFFER_SIZE);
+ hr = WORST_HR(hr, event->hr);
+ if (FAILED(hr))
+ {
+ // We don't hold the lock so just return immediately. Don't adjust stop-count.
+ _ASSERTE(!ThreadHoldsProcessLock());
+ return hr;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::S: sent async stop to appd 0x%x.\n", VmPtrToCookie(pAppDomainToken)));
+
+ // Wait for the sync complete message to come in. Note: when the sync complete message arrives to the RCEventThread,
+ // it will mark the process as synchronized and _not_ dispatch any events. Instead, it will set m_stopWaitEvent
+ // which will let this function return. If the user wants to process any queued events, they will need to call
+ // Continue.
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::S: waiting for event.\n");
+
+ DWORD ret;
+ ret = SafeWaitForSingleObject(this, m_stopWaitEvent, dwTimeout);
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP::S: got event, %d.\n", ret);
+
+ if (m_terminated)
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ if (ret == WAIT_OBJECT_0)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::S: process stopped.\n"));
+
+ m_stopRequested = false;
+ m_cordb->ProcessStateChanged();
+
+ hr = S_OK;
+ Lock();
+ goto Exit;
+ }
+ else if (ret == WAIT_TIMEOUT)
+ {
+ hr = ErrWrapper(CORDBG_E_TIMEOUT);
+ }
+ else
+ hr = HRESULT_FROM_GetLastError();
+
+ // We came out of the wait, but we weren't signaled because a sync complete event came in. Re-check the process and
+ // remove the stop requested flag.
+ Lock();
+ m_stopRequested = false;
+
+ if (GetSynchronized())
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::S: process stopped.\n"));
+
+ m_cordb->ProcessStateChanged();
+
+ hr = S_OK;
+ }
+
+Exit:
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ // Stop queuing any Win32 Debug events. We should be synchronized now.
+ m_specialDeferment = false;
+
+ if (SUCCEEDED(hr))
+ {
+ IncStopCount();
+ }
+
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "CP::S: returning from Stop, hr=0x%08x, m_stopCount=%d.\n", hr, GetStopCount());
+
+ Unlock();
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Clear all RS state on all CordbThread objects.
+//
+// Notes:
+// This clears all the thread-related state that the RS may have cached,
+// such as locals, frames, etc.
+// This would be called if the debugger is resuming execution.
+void CordbProcess::MarkAllThreadsDirty()
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ CordbThread * pThread;
+ HASHFIND find;
+
+ // We don't need to prepopulate here (to collect LS state) because we're just updating RS state.
+ for (pThread = m_userThreads.FindFirst(&find);
+ pThread != NULL;
+ pThread = m_userThreads.FindNext(&find))
+ {
+ _ASSERTE(pThread != NULL);
+ pThread->MarkStackFramesDirty();
+ }
+
+ ClearPatchTable();
+}
+
+HRESULT CordbProcess::Continue(BOOL fIsOutOfBand)
+{
+ PUBLIC_API_ENTRY(this);
+
+ if (m_pShim == NULL) // This API is moved off to the shim
+ {
+ // bias towards failing with CORDBG_E_NUETERED.
+ FAIL_IF_NEUTERED(this);
+ return E_NOTIMPL;
+ }
+
+ HRESULT hr;
+
+ if (fIsOutOfBand)
+ {
+#ifdef FEATURE_INTEROP_DEBUGGING
+ hr = ContinueOOB();
+#else
+ hr = E_INVALIDARG;
+#endif // FEATURE_INTEROP_DEBUGGING
+ }
+ else
+ {
+ hr = ContinueInternal(fIsOutOfBand);
+ }
+
+ return hr;
+}
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+//---------------------------------------------------------------------------------------
+//
+// ContinueOOB
+//
+// Continue the Win32 event as an out-of-band event.
+//
+// Return Value:
+// S_OK on successful continue. Else error.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::ContinueOOB()
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+
+ // If we're continuing from an out-of-band unmanaged event, then just go
+ // ahead and get the Win32 event thread to continue the process. No other
+ // work needs to be done (i.e., don't need to send a managed continue message
+ // or dispatch any events) because any processing done due to the out-of-band
+ // message can't alter the synchronized state of the process.
+
+ Lock();
+ _ASSERTE(m_outOfBandEventQueue != NULL);
+
+ // Are we calling this from the unmanaged callback?
+ if (m_dispatchingOOBEvent)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: continue while dispatching unmanaged out-of-band event.\n");
+ // We don't know what thread we're on here.
+
+ // Tell the Win32 event thread to continue when it returns from handling its unmanaged callback.
+ m_dispatchingOOBEvent = false;
+
+ Unlock();
+ }
+ else
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: continue outside of dispatching.\n");
+
+ // If we're not dispatching this, then they shouldn't be on the win32 event thread.
+ _ASSERTE(!this->IsWin32EventThread());
+
+ Unlock();
+
+ // Send an event to the Win32 event thread to do the continue. This is an out-of-band continue.
+ hr = this->m_pShim->GetWin32EventThread()->SendUnmanagedContinue(this, cOobUMContinue);
+ }
+
+ return hr;
+
+
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+//---------------------------------------------------------------------------------------
+//
+// ContinueInternal
+//
+// Continue the Win32 event.
+//
+// Return Value:
+// S_OK on success. Else error.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::ContinueInternal(BOOL fIsOutOfBand)
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ // Continue has an ATT similar to ATT_REQUIRE_STOPPED_MAY_FAIL, but w/ some subtle differences.
+ // - if we're stopped at a native DE, but not synchronized, we don't want to sync.
+ // - We may get Debug events (especially native ones) at weird times, and thus we have to continue
+ // at weird times.
+
+ // External APIs should not have the process lock.
+ _ASSERTE(!ThreadHoldsProcessLock());
+ _ASSERTE(m_pShim != NULL);
+
+ // OutOfBand should use ContinueOOB
+ _ASSERTE(!fIsOutOfBand);
+
+ // Since Continue is process-wide, just use a null appdomain pointer.
+ VMPTR_AppDomain pAppDomainToken = VMPTR_AppDomain::NullPtr();
+
+ HRESULT hr = S_OK;
+
+ if (m_unrecoverableError)
+ {
+ return CORDBHRFromProcessState(this, NULL);
+ }
+
+
+ // We can't call ContinueInternal for an inband event on the win32 event thread.
+ // This is an issue in the CLR (or an API design decision, depending on your perspective).
+ // Continue() may send an IPC event and we can't do that on the win32 event thread.
+
+ CORDBFailIfOnWin32EventThread(this);
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP::CI: continuing IB, this=0x%X\n", this);
+
+ // Stop + Continue are executed under the Stop-Go lock. This makes them atomic.
+ // We'll toggle the process-lock (b/c we communicate w/ the W32et, so that's not sufficient).
+ RSLockHolder rsLockHolder(&m_StopGoLock);
+
+ // Check for other failures (do these after we have the SG lock).
+ if (m_terminated)
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+ if (m_detached)
+ {
+ return CORDBG_E_PROCESS_DETACHED;
+ }
+
+ Lock();
+
+ ASSERT_SINGLE_THREAD_ONLY(HoldsLock(&m_StopGoLock));
+ _ASSERTE(fIsOutOfBand == FALSE);
+
+ // If we've got multiple Stop calls, we need a Continue for each one. So, if the stop count > 1, just go ahead and
+ // return without doing anything. Note: this is only for in-band or managed events. OOB events are still handled as
+ // normal above.
+ _ASSERTE(GetStopCount() > 0);
+
+ if (GetStopCount() == 0)
+ {
+ Unlock();
+ _ASSERTE(!"Superflous Continue. ICorDebugProcess.Continue() called too many times");
+ return CORDBG_E_SUPERFLOUS_CONTINUE;
+ }
+
+ DecStopCount();
+
+ // We give managed events priority over unmanaged events. That way, the entire queued managed state can drain before
+ // we let any other unmanaged events through.
+
+ // Every stop or event must be matched by a corresponding Continue. m_stopCount counts outstanding stopping events
+ // along with calls to Stop. If the count is high at this point, we simply return. This ensures that even if someone
+ // calls Stop just as they're receiving an event that they can call Continue for that Stop and for that event
+ // without problems.
+ if (GetStopCount() > 0)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP::CI: m_stopCount=%d, Continue just returning S_OK...\n", GetStopCount());
+
+ Unlock();
+ return S_OK;
+ }
+
+ // We're no longer stopped, so reset the m_stopWaitEvent.
+ ResetEvent(m_stopWaitEvent);
+
+ // If we're continuing from an uninitialized stop, then we don't need to do much at all. No event need be sent to
+ // the Left Side (duh, it isn't even there yet.) We just need to get the RC Event Thread to start listening to the
+ // process again, and resume any unmanaged threads if necessary.
+ if (m_uninitializedStop)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: continuing from uninitialized stop.\n");
+
+ // No longer synchronized (it was a partial sync in the first place.)
+ SetSynchronized(false);
+ MarkAllThreadsDirty();
+
+ // No longer in an uninitialized stop.
+ m_uninitializedStop = false;
+
+ // Notify the RC Event Thread.
+ m_cordb->ProcessStateChanged();
+
+ Unlock();
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // We may or may not have a native debug event queued here.
+ // If Cordbg called Stop() from a native debug event (to get the process Synchronized), then
+ // we'll have a native debug event, and we need to continue it.
+ // If Cordbg called Stop() to do an AsyncBreak, then there's no native-debug event.
+
+ // If we're Win32 attached, resume all the unmanaged threads.
+ if (IsInteropDebugging())
+ {
+ if(m_lastDispatchedIBEvent != NULL)
+ {
+ m_lastDispatchedIBEvent->SetState(CUES_UserContinued);
+ }
+
+ // Send to the Win32 event thread to do the unmanaged continue for us.
+ // If we're at a debug event, this will continue it.
+ // Else it will degenerate into ResumeUnmanagedThreads();
+ this->m_pShim->GetWin32EventThread()->SendUnmanagedContinue(this, cRealUMContinue);
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+
+ return S_OK;
+ }
+
+ // If there are more managed events, get them dispatched now.
+ if (!m_pShim->GetManagedEventQueue()->IsEmpty() && GetSynchronized())
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: managed event queued.\n");
+
+ // Mark that we're not synchronized anymore.
+ SetSynchronized(false);
+
+ // If the callback queue is not empty, then the LS is not actually continuing, and so our cached
+ // state is still valid.
+
+ // If we're in the middle of dispatching a managed event, then simply return. This indicates to HandleRCEvent
+ // that the user called Continue and HandleRCEvent will dispatch the next queued event. But if Continue was
+ // called outside the managed callback, all we have to do is tell the RC event thread that something about the
+ // process has changed and it will dispatch the next managed event.
+ if (!AreDispatchingEvent())
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: continuing while not dispatching managed event.\n");
+
+ m_cordb->ProcessStateChanged();
+ }
+
+ Unlock();
+ return S_OK;
+ }
+
+ // Neuter if we have an outstanding object.
+ // Only do this if we're really continuining the debuggee. So don't do this if our stop-count is high b/c we
+ // shouldn't neuter until we're done w/ the current event. And don't do this until we drain the current callback queue.
+ // Note that we can't hold the process lock while we do this b/c Neutering may send IPC events.
+ // However, we're still under the StopGo lock b/c that may help us serialize things.
+
+ // Sweep neuter list. This will catch anything that's marked as 'safe to neuter'. This includes
+ // all objects added to the 'neuter-on-Continue'.
+ // Only do this if we're synced- we don't want to do this if we're continuing from a Native Debug event.
+ if (GetSynchronized())
+ {
+ // Need process-lock to operate on hashtable, but can't yet Neuter under process-lock,
+ // so we have to copy the contents to an auxilary list which we can then traverse outside the lock.
+ RSPtrArray<CordbAppDomain> listAppDomains;
+ HRESULT hrCopy = S_OK;
+ EX_TRY // @dbgtodo cleanup: push this up
+ {
+ m_appDomains.CopyToArray(&listAppDomains);
+ }
+ EX_CATCH_HRESULT(hrCopy);
+ SetUnrecoverableIfFailed(GetProcess(), hrCopy);
+
+ m_ContinueNeuterList.NeuterAndClear(this);
+
+ // @dbgtodo left-side resources: eventually (once
+ // NeuterLeftSideResources is process-lock safe), do this all under the
+ // lock. Can't hold process lock b/c neutering left-side resources
+ // may send events.
+ Unlock();
+
+ // This may send IPC events.
+ // This will make normal neutering a nop.
+ // This will toggle the process lock.
+ m_LeftSideResourceCleanupList.SweepNeuterLeftSideResources(this);
+
+
+ // Many objects (especially CordbValue, FuncEval) don't have clear lifetime semantics and
+ // so they must be put into an exit-neuter list (Process/AppDomain) for worst-case scenarios.
+ // These objects are likely released early, and so we sweep them aggressively on each Continue (kind of like a mini-GC).
+ //
+ // One drawback is that there may be a lot of useless sweeping if the debugger creates a lot of
+ // objects that it holds onto. Consider instead of sweeping, have the object explicitly post itself
+ // to a list that's guaranteed to be cleared. This would let us avoid sweeping not-yet-ready objects.
+ // This will toggle the process lock
+ m_ExitNeuterList.SweepAllNeuterAtWillObjects(this);
+
+
+ for(unsigned int idx = 0; idx < listAppDomains.Length(); idx++)
+ {
+ CordbAppDomain * pAppDomain = listAppDomains[idx];
+
+ // CordbHandleValue is in the appdomain exit list, and that needs
+ // to send an IPC event to cleanup and release the handle from
+ // the GCs handle table.
+ // This will toggle the process lock.
+ pAppDomain->GetSweepableExitNeuterList()->SweepNeuterLeftSideResources(this);
+ }
+ listAppDomains.Clear();
+
+ Lock();
+ }
+
+
+ // At this point, if the managed event queue is empty, m_synchronized may still be true if we had previously
+ // synchronized.
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // Next, check for unmanaged events that may be queued. If there are some queued, then we need to get the Win32
+ // event thread to go ahead and dispatch the next one. If there aren't any queued, then we can just fall through and
+ // send the continue message to the left side. This works even if we have an outstanding ownership request, because
+ // until that answer is received, its just like the event hasn't happened yet.
+ //
+ // If we're terminated, then we've already continued from the last win32 event and so don't continue.
+ // @todo - or we could ensure the PS_SOME_THREADS_SUSPENDED | PS_HIJACKS_IN_PLACE are removed.
+ // Either way, we're just protecting against exit-process at strange times.
+ bool fDoWin32Continue = !m_terminated && ((m_state & (PS_WIN32_STOPPED | PS_SOME_THREADS_SUSPENDED | PS_HIJACKS_IN_PLACE)) != 0);
+
+ // We need to store this before marking the event user continued below
+ BOOL fHasUserUncontinuedEvents = HasUserUncontinuedNativeEvents();
+
+ if(m_lastDispatchedIBEvent != NULL)
+ {
+ m_lastDispatchedIBEvent->SetState(CUES_UserContinued);
+ }
+
+ if (fHasUserUncontinuedEvents)
+ {
+ // ExitProcess is the last debug event we'll get. The Process Handle is not signaled until
+ // after we continue from ExitProcess. m_terminated is only set once we know the process is signaled.
+ // (This isn't 100% true for the detach case, but since you can't do interop detach, we don't care)
+ //_ASSERTE(!m_terminated);
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP::CI: there are queued uncontinued events. m_dispatchingUnmanagedEvent = %d\n", m_dispatchingUnmanagedEvent);
+
+ // Are we being called while in the unmanaged event callback?
+ if (m_dispatchingUnmanagedEvent)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::CI: continue while dispatching.\n"));
+ // The Win32ET could have made a cross-thread call to Continue while dispatching,
+ // so we don't know if this is the win32 ET.
+
+ // Tell the Win32 thread to continue when it returns from handling its unmanaged callback.
+ m_dispatchingUnmanagedEvent = false;
+
+ // If there are no more unmanaged events, then we fall through and continue the process for real. Otherwise,
+ // we can simply return.
+ if (HasUndispatchedNativeEvents())
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: more unmanaged events need dispatching.\n");
+
+ // Note: if we tried to access the Left Side while stopped but couldn't, then m_oddSync will be true. We
+ // need to reset it to false since we're continuing now.
+ m_oddSync = false;
+
+ Unlock();
+ return S_OK;
+ }
+ else
+ {
+ // Also, if there are no more unmanaged events, then when DispatchUnmanagedInBandEvent sees that
+ // m_dispatchingUnmanagedEvent is false, it will continue the process. So we set doWin32Continue to
+ // false here so that we don't try to double continue the process below.
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: no more unmanaged events to dispatch.\n");
+
+ fDoWin32Continue = false;
+ }
+ }
+ else
+ {
+ // after the DebugEvent callback returned the continue still had no been issued. Then later
+ // on another thread the user called back to continue the event, which gets us to right here
+ LOG((LF_CORDB, LL_INFO1000, "CP::CI: continue outside of dispatching.\n"));
+
+ // This should be the common place to Dispatch an IB event that was hijacked for sync.
+
+ // If we're not dispatching, this better not be the win32 event thread.
+ _ASSERTE(!IsWin32EventThread());
+
+ // If the event at the head of the queue is really the last event, or if the event at the head of the queue
+ // hasn't been dispatched yet, then we simply fall through and continue the process for real. However, if
+ // its not the last event, we send to the Win32 event thread and get it to continue, then we return.
+ if (HasUndispatchedNativeEvents())
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: more unmanaged events need dispatching.\n");
+
+ // Note: if we tried to access the Left Side while stopped but couldn't, then m_oddSync will be true. We
+ // need to reset it to false since we're continuing now.
+ m_oddSync = false;
+
+ Unlock();
+
+ hr = this->m_pShim->GetWin32EventThread()->SendUnmanagedContinue(this, cRealUMContinue);
+
+ return hr;
+ }
+ }
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ // Both the managed and unmanaged event queues are now empty. Go
+ // ahead and continue the process for real.
+ LOG((LF_CORDB, LL_INFO1000, "CP::CI: headed for true continue.\n"));
+
+ // We need to check these while under the lock, but action must be
+ // taked outside of the lock.
+ bool fIsExiting = m_exiting;
+ bool fWasSynchronized = GetSynchronized();
+
+ // Mark that we're no longer synchronized.
+ if (fWasSynchronized)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::CI: process was synchronized.\n"));
+
+ SetSynchronized(false);
+ SetSyncCompleteRecv(false);
+
+ // we're no longer in a callback, so set flags to indicate that we've finished.
+ GetShim()->NotifyOnContinue();
+
+ // Flush will update state, including continue counter and marking
+ // frames dirty.
+ this->FlushProcessRunning();
+
+
+ // Tell the RC event thread that something about this process has changed.
+ m_cordb->ProcessStateChanged();
+ }
+
+ m_continueCounter++;
+
+ // If m_oddSync is set, then out last synchronization was due to us syncing the process because we were Win32
+ // stopped. Therefore, while we do need to do most of the work to continue the process below, we don't actually have
+ // to send the managed continue event. Setting wasSynchronized to false here helps us do that.
+ if (m_oddSync)
+ {
+ fWasSynchronized = false;
+ m_oddSync = false;
+ }
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // We must ensure that all managed threads are suspended here. We're about to let all managed threads run free via
+ // the managed continue message to the Left Side. If we don't suspend the managed threads, then they may start
+ // slipping forward even if we receive an in-band unmanaged event. We have to hijack in-band unmanaged events while
+ // getting the managed continue message over to the Left Side to keep the process running free. Otherwise, the
+ // SendIPCEvent will hang below. But in doing so, we could let managed threads slip to far. So we ensure they're all
+ // suspended here.
+ //
+ // Note: we only do this suspension if the helper thread hasn't died yet. If the helper thread has died, then we
+ // know that we're loosing the Runtime. No more managed code is going to run, so we don't bother trying to prevent
+ // managed threads from slipping via the call below.
+ //
+ // Note: we just remember here, under the lock, so we can unlock then wait for the syncing thread to free the
+ // debugger lock. Otherwise, we may block here and prevent someone from continuing from an OOB event, which also
+ // prevents the syncing thread from releasing the debugger lock like we want it to.
+ bool fNeedSuspend = fWasSynchronized && fDoWin32Continue && !m_helperThreadDead;
+
+ // If we receive a new in-band event once we unlock, we need to know to hijack it and keep going while we're still
+ // trying to send the managed continue event to the process.
+ if (fWasSynchronized && fDoWin32Continue && !fIsExiting)
+ {
+ m_specialDeferment = true;
+ }
+
+ if (fNeedSuspend)
+ {
+ // @todo - what does this actually accomplish? We already suspended everything when we first synced.
+
+ // Any thread that may hold a lock blocking the helper is
+ // inside of a can't stop region, and thus we won't suspend it.
+ SuspendUnmanagedThreads();
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ Unlock();
+
+ // Although we've released the Process-lock, we still have the Stop-Go lock.
+ _ASSERTE(m_StopGoLock.HasLock());
+
+ // If we're processing an ExitProcess managed event, then we don't want to really continue the process, so just fall
+ // thru. Note: we did let the unmanaged continue go through above for this case.
+ if (fIsExiting)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::CI: continuing from exit case.\n"));
+ }
+ else if (fWasSynchronized)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::CI: Sending continue to AppD:0x%x.\n", VmPtrToCookie(pAppDomainToken)));
+#ifdef FEATURE_INTEROP_DEBUGGING
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "Continue flags:special=%d, dowin32=%d\n", m_specialDeferment, fDoWin32Continue);
+#endif
+ // Send to the RC to continue the process.
+ DebuggerIPCEvent * pEvent = (DebuggerIPCEvent *) _alloca(CorDBIPC_BUFFER_SIZE);
+
+ InitIPCEvent(pEvent, DB_IPCE_CONTINUE, false, pAppDomainToken);
+
+ hr = m_cordb->SendIPCEvent(this, pEvent, CorDBIPC_BUFFER_SIZE);
+
+ // It is possible that we continue and then the process immediately exits before the helper
+ // thread is finished continuing and can report success back to us. That's arguably a success
+ // case sinceu the process did indeed continue, but since we didn't get the acknowledgement,
+ // we can't be sure it's success. So we call it S_FALSE instead of S_OK.
+ // @todo - how do we handle other failure here?
+ if (hr == CORDBG_E_PROCESS_TERMINATED)
+ {
+ hr = S_FALSE;
+ }
+ _ASSERTE(SUCCEEDED(pEvent->hr));
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::CI: Continue sent to AppD:0x%x.\n", VmPtrToCookie(pAppDomainToken)));
+ }
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // If we're win32 attached to the Left side, then we need to win32 continue the process too (unless, of course, it's
+ // already been done above.)
+ //
+ // Note: we do this here because we want to get the Left Side to receive and ack our continue message above if we
+ // were sync'd. If we were sync'd, then by definition the process (and the helper thread) is running anyway, so all
+ // this continue is going to do is to let the threads that have been suspended go.
+ if (fDoWin32Continue)
+ {
+#ifdef _DEBUG
+ {
+ // A little pause here extends the special deferment region and thus causes native-debug
+ // events to get hijacked. This test some wildly different corner case paths.
+ // See VSWhidbey bugs 131905, 168971
+ static DWORD dwRace = -1;
+ if (dwRace == -1)
+ dwRace = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgRace);
+
+ if ((dwRace & 1) == 1)
+ {
+ Sleep(30);
+ }
+ }
+#endif
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: sending unmanaged continue.\n");
+
+ // Send to the Win32 event thread to do the unmanaged continue for us.
+ hr = this->m_pShim->GetWin32EventThread()->SendUnmanagedContinue(this, cRealUMContinue);
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: continue done, returning.\n");
+
+ return hr;
+}
+
+HRESULT CordbProcess::HasQueuedCallbacks(ICorDebugThread *pThread,
+ BOOL *pbQueued)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pThread,ICorDebugThread *);
+ VALIDATE_POINTER_TO_OBJECT(pbQueued,BOOL *);
+
+ // Shim owns the event queue
+ if (m_pShim != NULL)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(this); // Calling to shim, leaving RS.
+ *pbQueued = m_pShim->GetManagedEventQueue()->HasQueuedCallbacks(pThread);
+ return S_OK;
+ }
+ return E_NOTIMPL; // Not implemented in V3.
+}
+
+//
+// A small helper function to convert a CordbBreakpoint to an ICorDebugBreakpoint based on its type.
+//
+static ICorDebugBreakpoint *CordbBreakpointToInterface(CordbBreakpoint * pBreakpoint)
+{
+ _ASSERTE(pBreakpoint != NULL);
+
+ //
+ // I really dislike this. We've got three subclasses of CordbBreakpoint, but we store them all into the same hash
+ // (m_breakpoints), so when we get one out of the hash, we don't really know what type it is. But we need to know
+ // what type it is because we need to cast it to the proper interface before passing it out. I.e., when we create a
+ // function breakpoint, we return the breakpoint casted to an ICorDebugFunctionBreakpoint. But if we grab that same
+ // breakpoint out of the hash as a CordbBreakpoint and pass it out as an ICorDebugBreakpoint, then that's a
+ // different pointer, and its wrong. So I've added the type to the breakpoint so we can cast properly here. I'd love
+ // to do this a different way, though...
+ //
+ // -- Mon Dec 14 21:06:46 1998
+ //
+ switch(pBreakpoint->GetBPType())
+ {
+ case CBT_FUNCTION:
+ return static_cast<ICorDebugFunctionBreakpoint *>(static_cast<CordbFunctionBreakpoint *> (pBreakpoint));
+ break;
+
+ case CBT_MODULE:
+ return static_cast<ICorDebugModuleBreakpoint*>(static_cast<CordbModuleBreakpoint *> (pBreakpoint));
+ break;
+
+ case CBT_VALUE:
+ return static_cast<ICorDebugValueBreakpoint *>(static_cast<CordbValueBreakpoint *> (pBreakpoint));
+ break;
+
+ default:
+ _ASSERTE(!"Invalid breakpoint type!");
+ }
+
+ return NULL;
+}
+
+
+// Callback data for code:CordbProcess::GetAssembliesInLoadOrder
+class ShimAssemblyCallbackData
+{
+public:
+ // Ctor to intialize callback data
+ //
+ // Arguments:
+ // pAppDomain - appdomain that the assemblies are in.
+ // pAssemblies - preallocated array of smart pointers to hold assemblies
+ // countAssemblies - size of pAssemblies in elements.
+ ShimAssemblyCallbackData(
+ CordbAppDomain * pAppDomain,
+ RSExtSmartPtr<ICorDebugAssembly>* pAssemblies,
+ ULONG countAssemblies)
+ {
+ _ASSERTE(pAppDomain != NULL);
+ _ASSERTE(pAssemblies != NULL);
+
+ m_pProcess = pAppDomain->GetProcess();
+ m_pAppDomain = pAppDomain;
+ m_pAssemblies = pAssemblies;
+ m_countElements = countAssemblies;
+ m_index = 0;
+
+ // Just to be safe, clear them all out
+ for(ULONG i = 0; i < countAssemblies; i++)
+ {
+ pAssemblies[i].Clear();
+ }
+ }
+
+ // Dtor
+ //
+ // Notes:
+ // This can assert end-of-enumeration invariants.
+ ~ShimAssemblyCallbackData()
+ {
+ // Ensure that we went through all assemblies.
+ _ASSERTE(m_index == m_countElements);
+ }
+
+ // Callback invoked from DAC enumeration.
+ //
+ // arguments:
+ // vmDomainAssembly - VMPTR for assembly
+ // pData - a 'this' pointer
+ //
+ static void Callback(VMPTR_DomainAssembly vmDomainAssembly, void * pData)
+ {
+ ShimAssemblyCallbackData * pThis = static_cast<ShimAssemblyCallbackData *> (pData);
+ INTERNAL_DAC_CALLBACK(pThis->m_pProcess);
+
+ CordbAssembly * pAssembly = pThis->m_pAppDomain->LookupOrCreateAssembly(vmDomainAssembly);
+
+ pThis->SetAndMoveNext(pAssembly);
+ }
+
+ // Set the current index in the table and increment the cursor.
+ //
+ // Arguments:
+ // pAssembly - assembly from DAC enumerator
+ void SetAndMoveNext(CordbAssembly * pAssembly)
+ {
+ _ASSERTE(pAssembly != NULL);
+
+ if (m_index >= m_countElements)
+ {
+ // Enumerating the assemblies in the target should be fixed since
+ // the target is not running.
+ // We should never get here unless the target is unstable.
+ // The caller (the shim) pre-allocated the table of assemblies.
+ m_pProcess->TargetConsistencyCheck(!"Target changed assembly count");
+ return;
+ }
+
+ m_pAssemblies[m_index].Assign(pAssembly);
+ m_index++;
+ }
+
+protected:
+ CordbProcess * m_pProcess;
+ CordbAppDomain * m_pAppDomain;
+ RSExtSmartPtr<ICorDebugAssembly>* m_pAssemblies;
+ ULONG m_countElements;
+ ULONG m_index;
+};
+
+//---------------------------------------------------------------------------------------
+// Shim Helper to enumerate the assemblies in the load-order
+//
+// Arguments:
+// pAppdomain - non-null appdmomain to enumerate assemblies.
+// pAssemblies - caller pre-allocated array to hold assemblies
+// countAssemblies - size of the array.
+//
+// Notes:
+// Caller preallocated array (likely from ICorDebugAssemblyEnum::GetCount),
+// and now this function fills in the assemblies in the order they were
+// loaded.
+//
+// The target should be stable, such that the number of assemblies in the
+// target is stable, and therefore countAssemblies as determined by the
+// shim via ICorDebugAssemblyEnum::GetCount should match the number of
+// assemblies enumerated here.
+//
+// Called by code:ShimProcess::QueueFakeAttachEvents.
+// This provides the assemblies in load-order. In contrast,
+// ICorDebugAppDomain::EnumerateAssemblies is a random order. The shim needs
+// load-order to match Whidbey semantics for dispatching fake load-assembly
+// callbacks on attach. The debugger then uses the order
+// in its module display window.
+//
+void CordbProcess::GetAssembliesInLoadOrder(
+ ICorDebugAppDomain * pAppDomain,
+ RSExtSmartPtr<ICorDebugAssembly>* pAssemblies,
+ ULONG countAssemblies)
+{
+ PUBLIC_API_ENTRY_FOR_SHIM(this);
+ RSLockHolder lockHolder(GetProcessLock());
+
+ _ASSERTE(GetShim() != NULL);
+
+ CordbAppDomain * pAppDomainInternal = static_cast<CordbAppDomain *> (pAppDomain);
+
+ ShimAssemblyCallbackData data(pAppDomainInternal, pAssemblies, countAssemblies);
+
+ // Enumerate through and fill out pAssemblies table.
+ GetDAC()->EnumerateAssembliesInAppDomain(
+ pAppDomainInternal->GetADToken(),
+ ShimAssemblyCallbackData::Callback,
+ &data); // user data
+
+ // pAssemblies array has now been updated.
+}
+
+// Callback data for code:CordbProcess::GetModulesInLoadOrder
+class ShimModuleCallbackData
+{
+public:
+ // Ctor to intialize callback data
+ //
+ // Arguments:
+ // pAssembly - assembly that the Modules are in.
+ // pModules - preallocated array of smart pointers to hold Modules
+ // countModules - size of pModules in elements.
+ ShimModuleCallbackData(
+ CordbAssembly * pAssembly,
+ RSExtSmartPtr<ICorDebugModule>* pModules,
+ ULONG countModules)
+ {
+ _ASSERTE(pAssembly != NULL);
+ _ASSERTE(pModules != NULL);
+
+ m_pProcess = pAssembly->GetAppDomain()->GetProcess();
+ m_pAssembly = pAssembly;
+ m_pModules = pModules;
+ m_countElements = countModules;
+ m_index = 0;
+
+ // Just to be safe, clear them all out
+ for(ULONG i = 0; i < countModules; i++)
+ {
+ pModules[i].Clear();
+ }
+ }
+
+ // Dtor
+ //
+ // Notes:
+ // This can assert end-of-enumeration invariants.
+ ~ShimModuleCallbackData()
+ {
+ // Ensure that we went through all Modules.
+ _ASSERTE(m_index == m_countElements);
+ }
+
+ // Callback invoked from DAC enumeration.
+ //
+ // arguments:
+ // vmDomainFile - VMPTR for Module
+ // pData - a 'this' pointer
+ //
+ static void Callback(VMPTR_DomainFile vmDomainFile, void * pData)
+ {
+ ShimModuleCallbackData * pThis = static_cast<ShimModuleCallbackData *> (pData);
+ INTERNAL_DAC_CALLBACK(pThis->m_pProcess);
+
+ CordbModule * pModule = pThis->m_pAssembly->GetAppDomain()->LookupOrCreateModule(vmDomainFile);
+
+ pThis->SetAndMoveNext(pModule);
+ }
+
+ // Set the current index in the table and increment the cursor.
+ //
+ // Arguments:
+ // pModule - Module from DAC enumerator
+ void SetAndMoveNext(CordbModule * pModule)
+ {
+ _ASSERTE(pModule != NULL);
+
+ if (m_index >= m_countElements)
+ {
+ // Enumerating the Modules in the target should be fixed since
+ // the target is not running.
+ // We should never get here unless the target is unstable.
+ // The caller (the shim) pre-allocated the table of Modules.
+ m_pProcess->TargetConsistencyCheck(!"Target changed Module count");
+ return;
+ }
+
+ m_pModules[m_index].Assign(pModule);
+ m_index++;
+ }
+
+protected:
+ CordbProcess * m_pProcess;
+ CordbAssembly * m_pAssembly;
+ RSExtSmartPtr<ICorDebugModule>* m_pModules;
+ ULONG m_countElements;
+ ULONG m_index;
+};
+
+//---------------------------------------------------------------------------------------
+// Shim Helper to enumerate the Modules in the load-order
+//
+// Arguments:
+// pAppdomain - non-null appdmomain to enumerate Modules.
+// pModules - caller pre-allocated array to hold Modules
+// countModules - size of the array.
+//
+// Notes:
+// Caller preallocated array (likely from ICorDebugModuleEnum::GetCount),
+// and now this function fills in the Modules in the order they were
+// loaded.
+//
+// The target should be stable, such that the number of Modules in the
+// target is stable, and therefore countModules as determined by the
+// shim via ICorDebugModuleEnum::GetCount should match the number of
+// Modules enumerated here.
+//
+// Called by code:ShimProcess::QueueFakeAssemblyAndModuleEvent.
+// This provides the Modules in load-order. In contrast,
+// ICorDebugAssembly::EnumerateModules is a random order. The shim needs
+// load-order to match Whidbey semantics for dispatching fake load-Module
+// callbacks on attach. The most important thing is that the manifest module
+// gets a LodModule callback before any secondary modules. For dynamic
+// modules, this is necessary for operations on the secondary module
+// that rely on manifest metadata (eg. GetSimpleName).
+//
+// @dbgtodo : This is almost identical to GetAssembliesInLoadOrder, and
+// (together wih the CallbackData classes) seems a HUGE amount of code and
+// complexity for such a simple thing. We also have extra code to order
+// AppDomains and Threads. We should try and rip all of this extra complexity
+// out, and replace it with better data structures for storing these items.
+// Eg., if we used std::map, we could have efficient lookups and ordered
+// enumerations. However, we do need to be careful about exposing new invariants
+// through ICorDebug that customers may depend on, which could place a long-term
+// compatibility burden on us. We could have a simple generic data structure
+// (eg. built on std::hash_map and std::list) which provided efficient look-up
+// and both in-order and random enumeration.
+//
+void CordbProcess::GetModulesInLoadOrder(
+ ICorDebugAssembly * pAssembly,
+ RSExtSmartPtr<ICorDebugModule>* pModules,
+ ULONG countModules)
+{
+ PUBLIC_API_ENTRY_FOR_SHIM(this);
+ RSLockHolder lockHolder(GetProcessLock());
+
+ _ASSERTE(GetShim() != NULL);
+
+ CordbAssembly * pAssemblyInternal = static_cast<CordbAssembly *> (pAssembly);
+
+ ShimModuleCallbackData data(pAssemblyInternal, pModules, countModules);
+
+ // Enumerate through and fill out pModules table.
+ GetDAC()->EnumerateModulesInAssembly(
+ pAssemblyInternal->GetDomainAssemblyPtr(),
+ ShimModuleCallbackData::Callback,
+ &data); // user data
+
+ // pModules array has now been updated.
+}
+
+
+//---------------------------------------------------------------------------------------
+// Callback to count the number of enumerations in a process.
+//
+// Arguments:
+// id - the connection id.
+// pName - name of the connection
+// pUserData - an EnumerateConnectionsData
+//
+// Notes:
+// Helper function for code:CordbProcess::QueueFakeConnectionEvents
+//
+// static
+void CordbProcess::CountConnectionsCallback(DWORD id, LPCWSTR pName, void * pUserData)
+{
+#if defined(FEATURE_INCLUDE_ALL_INTERFACES)
+ EnumerateConnectionsData * pCallbackData = reinterpret_cast<EnumerateConnectionsData *>(pUserData);
+ INTERNAL_DAC_CALLBACK(pCallbackData->m_pThis);
+
+ pCallbackData->m_uIndex += 1;
+#endif // FEATURE_INCLUDE_ALL_INTERFACES
+}
+
+//---------------------------------------------------------------------------------------
+// Callback to enumerate all the connections in a process.
+//
+// Arguments:
+// id - the connection id.
+// pName - name of the connection
+// pUserData - an EnumerateConnectionsData
+//
+// Notes:
+// Helper function for code:CordbProcess::QueueFakeConnectionEvents
+//
+// static
+void CordbProcess::EnumerateConnectionsCallback(DWORD id, LPCWSTR pName, void * pUserData)
+{
+#if defined(FEATURE_INCLUDE_ALL_INTERFACES)
+ EnumerateConnectionsData * pCallbackData = reinterpret_cast<EnumerateConnectionsData *>(pUserData);
+ INTERNAL_DAC_CALLBACK(pCallbackData->m_pThis);
+
+ // get the next entry in the array to be filled in
+ EnumerateConnectionsEntry * pEntry = &(pCallbackData->m_pEntryArray[pCallbackData->m_uIndex]);
+
+ // initialize the StringCopyHolder in the entry and copy over the name of the connection
+ new (&(pEntry->m_pName)) StringCopyHolder;
+ pEntry->m_pName.AssignCopy(pName);
+ pEntry->m_dwID = id;
+
+ pCallbackData->m_uIndex += 1;
+#endif // FEATURE_INCLUDE_ALL_INTERFACES
+}
+
+//---------------------------------------------------------------------------------------
+// Callback from Shim to queue fake Connection events on attach.
+//
+// Notes:
+// See code:ShimProcess::QueueFakeAttachEvents
+void CordbProcess::QueueFakeConnectionEvents()
+{
+ PUBLIC_API_ENTRY_FOR_SHIM(this);
+
+#ifdef FEATURE_INCLUDE_ALL_INTERFACES
+ EnumerateConnectionsData callbackData;
+ callbackData.m_pThis = this;
+ callbackData.m_uIndex = 0;
+ callbackData.m_pEntryArray = NULL;
+
+ UINT32 uSize = 0;
+
+ // We must take the process lock before calling DAC primitives which will call back into DBI.
+ // On the other hand, we must NOT be holding the lock when we call out to the shim.
+ // So introduce a new scope here.
+ {
+ RSLockHolder lockHolder(GetProcessLock());
+ GetDAC()->EnumerateConnections(CountConnectionsCallback, &callbackData);
+
+ // save the size for later
+ uSize = callbackData.m_uIndex;
+
+ // Allocate the array to store the connections. This array will be released when the dtor runs.
+ callbackData.m_uIndex = 0;
+ callbackData.m_pEntryArray = new EnumerateConnectionsEntry[uSize];
+ GetDAC()->EnumerateConnections(EnumerateConnectionsCallback, &callbackData);
+ _ASSERTE(uSize == callbackData.m_uIndex);
+ }
+
+ {
+ // V2 would send CreateConnection for all connections, and then ChangeConnection
+ // for all connections.
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(this);
+ for (UINT32 i = 0; i < uSize; i++)
+ {
+ EnumerateConnectionsEntry * pEntry = &(callbackData.m_pEntryArray[i]);
+ GetShim()->GetShimCallback()->CreateConnection(
+ this,
+ (CONNID)pEntry->m_dwID,
+ const_cast<WCHAR *>((const WCHAR *)(pEntry->m_pName)));
+ }
+
+ for (UINT32 i = 0; i < uSize; i++)
+ {
+ EnumerateConnectionsEntry * pEntry = &(callbackData.m_pEntryArray[i]);
+ GetShim()->GetShimCallback()->ChangeConnection(this, (CONNID)pEntry->m_dwID);
+ }
+ }
+#endif
+}
+
+//
+// DispatchRCEvent -- dispatches a previously queued IPC event received
+// from the runtime controller. This represents the last amount of processing
+// the DI gets to do on an event before giving it to the user.
+//
+#ifdef _PREFAST_
+#pragma warning(push)
+#pragma warning(disable:21000) // Suppress PREFast warning about overly large function
+#endif
+void CordbProcess::DispatchRCEvent()
+{
+ INTERNAL_API_ENTRY(this);
+
+ CONTRACTL
+ {
+ // This is happening on the RCET thread, so there's no place to propogate an error back up.
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(m_pShim != NULL); // V2 case
+
+ //
+ // Note: the current thread should have the process locked when it
+ // enters this method.
+ //
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ // Create/Launch paths already ensured that we had a callback.
+ _ASSERTE(m_cordb != NULL);
+ _ASSERTE(m_cordb->m_managedCallback != NULL);
+ _ASSERTE(m_cordb->m_managedCallback2 != NULL);
+ _ASSERTE(m_cordb->m_managedCallback3 != NULL);
+
+
+ // Bump up the stop count. Either we'll dispatch a managed event,
+ // or the logic below will decide not to dispatch one and call
+ // Continue itself. Either way, the stop count needs to go up by
+ // one...
+ _ASSERTE(this->GetSyncCompleteRecv());
+ SetSynchronized(true);
+ IncStopCount();
+
+ // As soon as we call Unlock(), we might get neutered and lose our reference to
+ // the shim. Grab it now for use later.
+ RSExtSmartPtr<ShimProcess> pShim(m_pShim);
+
+ Unlock();
+
+ _ASSERTE(!ThreadHoldsProcessLock());
+
+
+ // We want to stay synced until after the callbacks return. This is b/c we're on the RCET,
+ // and we may deadlock if we send IPC events on the RCET if we're not synced (see SendIPCEvent for details).
+ // So here, stopcount=1. The StopContinueHolder bumps it up to 2.
+ // - If Cordbg calls continue in the callback, that bumps it back down to 1, but doesn't actually continue.
+ // The holder dtor then bumps it down to 0, doing the real continue.
+ // - If Cordbg doesn't call continue in the callback, then stopcount stays at 2, holder dtor drops it down to 1,
+ // and then the holder was just a nop.
+ // This gives us delayed continues w/ no extra state flags.
+
+
+ // The debugger may call Detach() immediately after it returns from the callback, but before this thread returns
+ // from this function. Thus after we execute the callbacks, it's possible the CordbProcess object has been neutered.
+
+ // Since we're already sycned, the Stop from the holder here is practically a nop that just bumps up a count.
+ // Create an extra scope for the StopContinueHolder.
+ {
+ StopContinueHolder h;
+ HRESULT hr = h.Init(this);
+ if (FAILED(hr))
+ {
+ CORDBSetUnrecoverableError(this, hr, 0);
+ }
+
+ HRESULT hrCallback = S_OK;
+ // It's possible a ICorDebugProcess::Detach() may have occurred by now.
+ {
+ // @dbgtodo shim: eventually the entire RCET should be considered outside the RS.
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(this);
+
+
+ // Snag the first event off the queue.
+ // Holder will call Delete, which will invoke virtual Dtor that will release ICD objects.
+ // Since these are external refs, we want to do it while "outside" the RS.
+ NewHolder<ManagedEvent> pEvent(pShim->DequeueManagedEvent());
+
+ // Normally pEvent shouldn't be NULL, since this method is called when the queue is not empty.
+ // But due to a race between CordbProcess::Terminate(), CordbWin32EventThread::ExitProcess() and this method
+ // it is totally possible that the queue has already been cleaned up and we can't expect that event is always available.
+ if (pEvent != NULL)
+ {
+ // Since we need to access a member (m_cordb), protect this block with a
+ // lock and a check for Neutering (in case process detach has just
+ // occurred). We'll release the lock around the dispatch later on.
+ RSLockHolder lockHolder(GetProcessLock());
+ if (!IsNeutered())
+ {
+#ifdef _DEBUG
+ // On a debug build, keep track of the last IPC event we dispatched.
+ m_pDBGLastIPCEventType = pEvent->GetDebugCookie();
+#endif
+
+ ManagedEvent::DispatchArgs args(m_cordb->m_managedCallback, m_cordb->m_managedCallback2, m_cordb->m_managedCallback3);
+
+ {
+ // Release lock around the dispatch of the event
+ RSInverseLockHolder inverseLockHolder(GetProcessLock());
+
+ EX_TRY
+ {
+ // This dispatches almost directly into the user's callbacks.
+ // It does not update any RS state.
+ hrCallback = pEvent->Dispatch(args);
+ }
+ EX_CATCH_HRESULT(hrCallback);
+ }
+ }
+ }
+
+ } // we're now back inside the RS
+
+ if (hrCallback == E_NOTIMPL)
+ {
+ ContinueInternal(FALSE);
+ }
+
+
+ } // forces Continue to be called
+
+ Lock();
+
+};
+
+#ifdef _DEBUG
+//---------------------------------------------------------------------------------------
+// Debug-only callback to ensure that an appdomain is not available after the ExitAppDomain event.
+//
+// Arguments:
+// vmAppDomain - appdomain from enumeration
+// pUserData - pointer to a DbgAssertAppDomainDeletedData which contains the VMAppDomain that was just deleted.
+// notes:
+// see code:CordbProcess::DbgAssertAppDomainDeleted for details.
+void CordbProcess::DbgAssertAppDomainDeletedCallback(VMPTR_AppDomain vmAppDomain, void * pUserData)
+{
+ DbgAssertAppDomainDeletedData * pCallbackData = reinterpret_cast<DbgAssertAppDomainDeletedData *>(pUserData);
+ INTERNAL_DAC_CALLBACK(pCallbackData->m_pThis);
+
+ VMPTR_AppDomain vmAppDomainDeleted = pCallbackData->m_vmAppDomainDeleted;
+ CONSISTENCY_CHECK_MSGF((vmAppDomain != vmAppDomainDeleted),
+ ("An ExitAppDomain event was sent for appdomain, but it still shows up in the enumeration.\n vmAppDomain=%p\n",
+ VmPtrToCookie(vmAppDomainDeleted)));
+}
+
+//---------------------------------------------------------------------------------------
+// Debug-only helper to Assert that VMPTR is actually removed.
+//
+// Arguments:
+// vmAppDomainDeleted - vmptr of appdomain that we just got exit event for.
+// This should not be discoverable from the RS.
+//
+// Notes:
+// See code:IDacDbiInterface#Enumeration for rules that we're asserting.
+// Once the exit appdomain event is dispatched, the appdomain should not be discoverable by the RS.
+// Else the RS may use the AppDomain* after it's deleted.
+// This asserts that the AppDomain* is not discoverable.
+//
+// Since this is a debug-only function, it should have no side-effects.
+void CordbProcess::DbgAssertAppDomainDeleted(VMPTR_AppDomain vmAppDomainDeleted)
+{
+ DbgAssertAppDomainDeletedData callbackData;
+ callbackData.m_pThis = this;
+ callbackData.m_vmAppDomainDeleted = vmAppDomainDeleted;
+
+ GetDAC()->EnumerateAppDomains(
+ CordbProcess::DbgAssertAppDomainDeletedCallback,
+ &callbackData);
+}
+
+#endif // _DEBUG
+
+//---------------------------------------------------------------------------------------
+// Update state and potentially Dispatch a single event.
+//
+// Arguments:
+// pEvent - non-null pointer to debug event.
+// pCallback1 - callback object to dispatch on (for V1 callbacks)
+// pCallback2 - 2nd callback object to dispatch on (for new V2 callbacks)
+// pCallback3 - 3rd callback object to dispatch on (for new V4 callbacks)
+//
+//
+// Returns:
+// Nothing. Throws on error.
+//
+// Notes:
+// Generally, this will dispatch exactly 1 callback. It may dispatch 0 callbacks if there is an error
+// or in other corner cases (documented within the dispatch code below).
+// Errors could occur because:
+// - the event is corrupted (exceptional case)
+// - the RS is corrupted / OOM (exceptional case)
+// Exception errors here will propogate back to the Filter() call, and there's not really anything
+// a debugger can do about an error here (perhaps report it to the user).
+// Errors must leave IcorDebug in a consistent state.
+//
+// This is dispatched directly on the Win32Event Thread in response to calling Filter.
+// Therefore, this can't send any IPC events (Not an issue once everything is DAC-ized).
+// A V2 shim can provide a proxy calllack that takes these events and queues them and
+// does the real dispatch to the user to emulate V2 semantics.
+//
+void CordbProcess::RawDispatchEvent(
+ DebuggerIPCEvent * pEvent,
+ RSLockHolder * pLockHolder,
+ ICorDebugManagedCallback * pCallback1,
+ ICorDebugManagedCallback2 * pCallback2,
+ ICorDebugManagedCallback3 * pCallback3)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+ // We start off with the lock, and we'll toggle it.
+ _ASSERTE(ThreadHoldsProcessLock());
+
+
+ //
+ // Call StartEventDispatch to true to guard against calls to Continue()
+ // from within the user's callback. We need Continue() to behave a little
+ // bit differently in such a case.
+ //
+ // Also note that Win32EventThread::ExitProcess will take the lock and free all
+ // events in the queue. (the current event is already off the queue, so
+ // it will be ok). But we can't do the EP callback in the middle of this dispatch
+ // so if this flag is set, EP will wait on the miscWaitEvent (which will
+ // get set in FlushQueuedEvents when we return from here) and let us finish here.
+ //
+ StartEventDispatch(pEvent->type);
+
+ // Keep strong references to these objects in case a callback deletes them from underneath us.
+ RSSmartPtr<CordbAppDomain> pAppDomain;
+ CordbThread * pThread = NULL;
+
+
+ // Get thread that this event is on. In attach scenarios, this may be the first time ICorDebug has seen this thread.
+ if (!pEvent->vmThread.IsNull())
+ {
+ pThread = LookupOrCreateThread(pEvent->vmThread);
+ }
+
+ if (!pEvent->vmAppDomain.IsNull())
+ {
+ pAppDomain.Assign(LookupOrCreateAppDomain(pEvent->vmAppDomain));
+ }
+
+ DWORD dwVolatileThreadId = 0;
+ if (pThread != NULL)
+ {
+ dwVolatileThreadId = pThread->GetUniqueId();
+ }
+
+
+ //
+ // Update the app domain that this thread lives in.
+ //
+ if ((pThread != NULL) && (pAppDomain != NULL))
+ {
+ // It shouldn't be possible for us to see an exited AppDomain here
+ _ASSERTE( !pAppDomain->IsNeutered() );
+
+ pThread->m_pAppDomain = pAppDomain;
+ }
+
+ _ASSERTE(pEvent != NULL);
+ _ASSERTE(pCallback1 != NULL);
+ _ASSERTE(pCallback2 != NULL);
+ _ASSERTE(pCallback3 != NULL);
+
+
+ STRESS_LOG1(LF_CORDB, LL_EVERYTHING, "Pre-Dispatch IPC event: %s\n", IPCENames::GetName(pEvent->type));
+
+ switch (pEvent->type & DB_IPCE_TYPE_MASK)
+ {
+ case DB_IPCE_CREATE_PROCESS:
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->CreateProcess(static_cast<ICorDebugProcess*> (this));
+ }
+ break;
+
+ case DB_IPCE_BREAKPOINT:
+ {
+ _ASSERTE(pThread != NULL);
+ _ASSERTE(pAppDomain != NULL);
+
+ // Find the breakpoint object on this side.
+ CordbBreakpoint *pBreakpoint = NULL;
+
+ // We've found cases out in the wild where we get this event on a thread we don't recognize.
+ // We're not sure how this happens. Add a runtime check to protect ourselves to avoid the
+ // an AV. We still assert because this should not be happening.
+ // It likely means theres some issue where we failed to send a CreateThread notification.
+ TargetConsistencyCheck(pThread != NULL);
+ pBreakpoint = pAppDomain->m_breakpoints.GetBase(LsPtrToCookie(pEvent->BreakpointData.breakpointToken));
+
+ if (pBreakpoint != NULL)
+ {
+ ICorDebugBreakpoint * pIBreakpoint = CordbBreakpointToInterface(pBreakpoint);
+ _ASSERTE(pIBreakpoint != NULL);
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE2(this, pLockHolder, pEvent, "thread=0x%p, bp=0x%p", pThread, pBreakpoint);
+ pCallback1->Breakpoint(pAppDomain, pThread, pIBreakpoint);
+ }
+ }
+ }
+ break;
+
+ case DB_IPCE_USER_BREAKPOINT:
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "[%x] RCET::DRCE: user breakpoint.\n",
+ GetCurrentThreadId());
+
+ _ASSERTE(pThread != NULL);
+ _ASSERTE(pAppDomain != NULL);
+ _ASSERTE(pThread->m_pAppDomain != NULL);
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->Break(pThread->m_pAppDomain, pThread);
+ }
+
+ }
+ break;
+
+ case DB_IPCE_STEP_COMPLETE:
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "[%x] RCET::DRCE: step complete.\n",
+ GetCurrentThreadId());
+
+ PREFIX_ASSUME(pThread != NULL);
+
+ CordbStepper * pStepper = m_steppers.GetBase(LsPtrToCookie(pEvent->StepData.stepperToken));
+
+ // It's possible the stepper is NULL if:
+ // - event X & step-complete are both in the queue
+ // - during dispatch for event X, Cordbg cancels the stepper (thus removing it from m_steppers)
+ // - the Step-Complete still stays in the queue, and so we're here, but out stepper's been removed.
+ // (This could happen for breakpoints too)
+ // Don't dispatch a callback if the stepper is NULL.
+ if (pStepper != NULL)
+ {
+ RSSmartPtr<CordbStepper> pRef(pStepper);
+ pStepper->m_active = false;
+ m_steppers.RemoveBase((ULONG_PTR)pStepper->m_id);
+
+ {
+ _ASSERTE(pThread->m_pAppDomain != NULL);
+ PUBLIC_CALLBACK_IN_THIS_SCOPE2(this, pLockHolder, pEvent, "thrad=0x%p, stepper=0x%p", pThread, pStepper);
+ pCallback1->StepComplete(pThread->m_pAppDomain, pThread, pStepper, pEvent->StepData.reason);
+ }
+
+ // implicit Release on pRef
+ }
+ }
+ break;
+
+ case DB_IPCE_EXCEPTION:
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "[%x] RCET::DRCE: exception.\n",
+ GetCurrentThreadId());
+
+ _ASSERTE(pAppDomain != NULL);
+
+ // For some exceptions very early in startup (eg, TypeLoad), this may have occurred before we
+ // even executed jitted code on the thread. We may have not received a CreateThread yet.
+ // In V2, we detected this and sent a LogMessage on a random thread.
+ // In V3, we lazily create the CordbThread objects (possibly before the CreateThread event),
+ // and so we know we should have one.
+ _ASSERTE(pThread != NULL);
+
+ pThread->SetExInfo(pEvent->Exception.vmExceptionHandle);
+
+ _ASSERTE(pThread->m_pAppDomain != NULL);
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->Exception(pThread->m_pAppDomain, pThread, !pEvent->Exception.firstChance);
+ }
+
+ }
+ break;
+
+ case DB_IPCE_SYNC_COMPLETE:
+ _ASSERTE(!"Should have never queued a sync complete pEvent.");
+ break;
+
+ case DB_IPCE_THREAD_ATTACH:
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO100, "RCET::DRCE: thread attach : ID=%x.\n", dwVolatileThreadId);
+
+ TargetConsistencyCheck(pThread != NULL);
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE1(this, pLockHolder, pEvent, "thread=0x%p", pThread);
+ pCallback1->CreateThread(pAppDomain, pThread);
+ }
+ }
+ break;
+
+ case DB_IPCE_THREAD_DETACH:
+ {
+ STRESS_LOG2(LF_CORDB, LL_INFO100, "[%x] RCET::HRCE: thread detach : ID=%x \n",
+ GetCurrentThreadId(), dwVolatileThreadId);
+
+ // If the runtime thread never entered managed code, there
+ // won't be a CordbThread, and CreateThread was never
+ // called, so don't bother calling ExitThread.
+ if (pThread != NULL)
+ {
+ AddToNeuterOnContinueList(pThread);
+
+ RSSmartPtr<CordbThread> pRefThread(pThread);
+
+ _ASSERTE(pAppDomain != NULL);
+
+ // A thread is reported as dead before we get the exit event.
+ // See code:IDacDbiInterface#IsThreadMarkedDead for the invariant being asserted here.
+ TargetConsistencyCheck(pThread->IsThreadDead());
+
+ // Enforce the enumeration invariants (see code:IDacDbiInterface#Enumeration)that the thread is not discoverable.
+ INDEBUG(pThread->DbgAssertThreadDeleted());
+
+ // Remove the thread from the hash. If we've removed it from the hash, we really should
+ // neuter it ... but that causes test failures.
+ // We'll neuter it in continue.
+ m_userThreads.RemoveBase(VmPtrToCookie(pThread->m_vmThreadToken));
+
+
+ LOG((LF_CORDB, LL_INFO1000, "[%x] RCET::HRCE: sending thread detach.\n", GetCurrentThreadId()));
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->ExitThread(pAppDomain, pThread);
+ }
+
+ // Implicit release on thread & pAppDomain
+ }
+ }
+ break;
+
+ case DB_IPCE_METADATA_UPDATE:
+ {
+ CordbModule * pModule = pAppDomain->LookupOrCreateModule(pEvent->MetadataUpdateData.vmDomainFile);
+ pModule->RefreshMetaData();
+ }
+ break;
+
+ case DB_IPCE_LOAD_MODULE:
+ {
+ _ASSERTE (pAppDomain != NULL);
+ CordbModule * pModule = pAppDomain->LookupOrCreateModule(pEvent->LoadModuleData.vmDomainFile);
+
+ {
+ pModule->SetLoadEventContinueMarker();
+
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->LoadModule(pAppDomain, pModule);
+ }
+
+ }
+ break;
+
+ case DB_IPCE_CREATE_CONNECTION:
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO100,
+ "RCET::HRCE: Connection change %d \n",
+ pEvent->CreateConnection.connectionId);
+
+ // pass back the connection id and the connection name.
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback2->CreateConnection(
+ this,
+ pEvent->CreateConnection.connectionId,
+ const_cast<WCHAR*> (pEvent->CreateConnection.wzConnectionName.GetString()));
+ }
+ break;
+
+ case DB_IPCE_DESTROY_CONNECTION:
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO100,
+ "RCET::HRCE: Connection destroyed %d \n",
+ pEvent->ConnectionChange.connectionId);
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback2->DestroyConnection(this, pEvent->ConnectionChange.connectionId);
+ }
+ break;
+
+ case DB_IPCE_CHANGE_CONNECTION:
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO100,
+ "RCET::HRCE: Connection changed %d \n",
+ pEvent->ConnectionChange.connectionId);
+
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback2->ChangeConnection(this, pEvent->ConnectionChange.connectionId);
+ }
+ break;
+
+ case DB_IPCE_UNLOAD_MODULE:
+ {
+ STRESS_LOG3(LF_CORDB, LL_INFO100, "RCET::HRCE: unload module on thread %#x Mod:0x%x AD:0x%08x\n",
+ dwVolatileThreadId,
+ VmPtrToCookie(pEvent->UnloadModuleData.vmDomainFile),
+ VmPtrToCookie(pEvent->vmAppDomain));
+
+ PREFIX_ASSUME (pAppDomain != NULL);
+
+ CordbModule *module = pAppDomain->LookupOrCreateModule(pEvent->UnloadModuleData.vmDomainFile);
+
+ if (module == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO100, "Already unloaded Module - continue()ing!" ));
+ break;
+ }
+ _ASSERTE(module != NULL);
+ INDEBUG(module->DbgAssertModuleDeleted());
+
+ // The appdomain we're unloading in must be the appdomain we were loaded in. Otherwise, we've got mismatched
+ // module and appdomain pointers. Bugs 65943 & 81728.
+ _ASSERTE(pAppDomain == module->GetAppDomain());
+
+ // Ensure the module gets neutered once we call continue.
+ AddToNeuterOnContinueList(module); // throws
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->UnloadModule(pAppDomain, module);
+ }
+
+ pAppDomain->m_modules.RemoveBase(VmPtrToCookie(pEvent->UnloadModuleData.vmDomainFile));
+ }
+ break;
+
+ case DB_IPCE_LOAD_CLASS:
+ {
+ CordbClass *pClass = NULL;
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "RCET::HRCE: load class on thread %#x Tok:0x%08x Mod:0x%08x Asm:0x%08x AD:0x%08x\n",
+ dwVolatileThreadId,
+ pEvent->LoadClass.classMetadataToken,
+ VmPtrToCookie(pEvent->LoadClass.vmDomainFile),
+ LsPtrToCookie(pEvent->LoadClass.classDebuggerAssemblyToken),
+ VmPtrToCookie(pEvent->vmAppDomain)));
+
+ _ASSERTE (pAppDomain != NULL);
+
+ CordbModule* pModule = pAppDomain->LookupOrCreateModule(pEvent->LoadClass.vmDomainFile);
+ if (pModule == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO100, "Load Class on not-loaded Module - continue()ing!" ));
+ break;
+ }
+ _ASSERTE(pModule != NULL);
+
+ BOOL fDynamic = pModule->IsDynamic();
+
+ // If this is a class load in a dynamic module, the metadata has become invalid.
+ if (fDynamic)
+ {
+ pModule->RefreshMetaData();
+ }
+
+ hr = pModule->LookupOrCreateClass(pEvent->LoadClass.classMetadataToken, &pClass);
+ _ASSERTE(SUCCEEDED(hr) == (pClass != NULL));
+ IfFailThrow(hr);
+
+ // Prevent class load from being sent twice.
+ // @dbgtodo - Microsoft, cordbclass: this is legacy. Can this really happen? Investigate as we dac-ize CordbClass.
+ if (pClass->LoadEventSent())
+ {
+ // Dynamic modules are dynamic at the module level -
+ // you can't add a new version of a class once the module
+ // is baked.
+ // EnC adds completely new classes.
+ // There shouldn't be any other way to send multiple
+ // ClassLoad events.
+ // Except that there are race conditions between loading
+ // an appdomain, and loading a class, so if we get the extra
+ // class load, we should ignore it.
+ break; //out of the switch statement
+ }
+ pClass->SetLoadEventSent(TRUE);
+
+
+ if (pClass != NULL)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->LoadClass(pAppDomain, pClass);
+ }
+ }
+ break;
+
+ case DB_IPCE_UNLOAD_CLASS:
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "RCET::HRCE: unload class on thread %#x Tok:0x%08x Mod:0x%08x AD:0x%08x\n",
+ dwVolatileThreadId,
+ pEvent->UnloadClass.classMetadataToken,
+ VmPtrToCookie(pEvent->UnloadClass.vmDomainFile),
+ VmPtrToCookie(pEvent->vmAppDomain)));
+
+ // get the appdomain object
+ _ASSERTE (pAppDomain != NULL);
+
+ CordbModule *pModule = pAppDomain->LookupOrCreateModule(pEvent->UnloadClass.vmDomainFile);
+ if (pModule == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO100, "Unload Class on not-loaded Module - continue()ing!" ));
+ break;
+ }
+ _ASSERTE(pModule != NULL);
+
+ CordbClass *pClass = pModule->LookupClass(pEvent->UnloadClass.classMetadataToken);
+
+ if (pClass != NULL && !pClass->HasBeenUnloaded())
+ {
+ pClass->SetHasBeenUnloaded(true);
+
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->UnloadClass(pAppDomain, pClass);
+ }
+ }
+ break;
+
+ case DB_IPCE_FIRST_LOG_MESSAGE:
+ {
+ _ASSERTE(pThread != NULL);
+ _ASSERTE(pAppDomain != NULL);
+
+ const WCHAR * pszContent = pEvent->FirstLogMessage.szContent.GetString();
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->LogMessage(
+ pAppDomain,
+ pThread,
+ pEvent->FirstLogMessage.iLevel,
+ const_cast<WCHAR*> (pEvent->FirstLogMessage.szCategory.GetString()),
+ const_cast<WCHAR*> (pszContent));
+ }
+ }
+ break;
+
+ case DB_IPCE_LOGSWITCH_SET_MESSAGE:
+ {
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "[%x] RCET::DRCE: Log Switch Setting Message.\n",
+ GetCurrentThreadId()));
+
+ _ASSERTE(pThread != NULL);
+
+ const WCHAR *pstrLogSwitchName = pEvent->LogSwitchSettingMessage.szSwitchName.GetString();
+ const WCHAR *pstrParentName = pEvent->LogSwitchSettingMessage.szParentSwitchName.GetString();
+
+ // from the thread object get the appdomain object
+ _ASSERTE(pAppDomain == pThread->m_pAppDomain);
+ _ASSERTE (pAppDomain != NULL);
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->LogSwitch(
+ pAppDomain,
+ pThread,
+ pEvent->LogSwitchSettingMessage.iLevel,
+ pEvent->LogSwitchSettingMessage.iReason,
+ const_cast<WCHAR*> (pstrLogSwitchName),
+ const_cast<WCHAR*> (pstrParentName));
+
+ }
+ }
+
+ break;
+ case DB_IPCE_CUSTOM_NOTIFICATION:
+ {
+ _ASSERTE(pThread != NULL);
+ _ASSERTE(pAppDomain != NULL);
+
+
+ // determine first whether custom notifications for this type are enabled -- if not
+ // we just return without doing anything.
+ CordbClass * pNotificationClass = LookupClass(pAppDomain,
+ pEvent->CustomNotification.vmDomainFile,
+ pEvent->CustomNotification.classToken);
+
+ // if the class is NULL, that means the debugger never enabled notifications for it. Otherwise,
+ // the CordbClass instance would already have been created when the notifications were
+ // enabled.
+ if ((pNotificationClass != NULL) && pNotificationClass->CustomNotificationsEnabled())
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback3->CustomNotification(pThread, pAppDomain);
+ }
+ }
+
+ break;
+
+ case DB_IPCE_CREATE_APP_DOMAIN:
+ {
+ STRESS_LOG2(LF_CORDB, LL_INFO100,
+ "RCET::HRCE: create appdomain on thread %#x AD:0x%08x \n",
+ dwVolatileThreadId,
+ VmPtrToCookie(pEvent->vmAppDomain));
+
+
+ // Enumerate may have prepopulated the appdomain, so check if it already exists.
+ // Either way, still send the CreateEvent. (We don't want to skip the Create event
+ // just because the debugger did an enumerate)
+ // We remove AppDomains from the hash as soon as they are exited.
+ pAppDomain.Assign(LookupOrCreateAppDomain(pEvent->AppDomainData.vmAppDomain));
+ _ASSERTE(pAppDomain != NULL); // throws on failure
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ hr = pCallback1->CreateAppDomain(this, pAppDomain);
+ }
+ }
+
+
+ break;
+
+ case DB_IPCE_EXIT_APP_DOMAIN:
+ {
+ STRESS_LOG2(LF_CORDB, LL_INFO100, "RCET::HRCE: exit appdomain on thread %#x AD:0x%08x \n",
+ dwVolatileThreadId,
+ VmPtrToCookie(pEvent->vmAppDomain));
+
+ // In debug-only builds, assert that the appdomain is indeed deleted and not discoverable.
+ INDEBUG(DbgAssertAppDomainDeleted(pEvent->vmAppDomain));
+
+ // If we get an ExitAD message for which we have no AppDomain, then ignore it.
+ // This can happen if an AD gets torn down very early (before the LS AD is to the
+ // point that it can be published).
+ // This could also happen if we attach a debugger right before the Exit event is sent.
+ // In this case, the debuggee is no longer publishing the appdomain.
+ if (pAppDomain == NULL)
+ {
+ break;
+ }
+ _ASSERTE (pAppDomain != NULL);
+
+ // See if this is the default AppDomain exiting. This should only happen very late in
+ // the shutdown cycle, and so we shouldn't do anything significant with m_pDefaultDomain==NULL.
+ // We should try and remove m_pDefaultDomain entirely since we can't count on it always existing.
+ if (pAppDomain == m_pDefaultAppDomain)
+ {
+ m_pDefaultAppDomain = NULL;
+ }
+
+ // Update any threads which were last seen in this AppDomain. We don't
+ // get any notification when a thread leaves an AppDomain, so our idea
+ // of what AppDomain the thread is in may be out of date.
+ UpdateThreadsForAdUnload( pAppDomain );
+
+ // This will still maintain weak references so we could call Continue.
+ AddToNeuterOnContinueList(pAppDomain);
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ hr = pCallback1->ExitAppDomain(this, pAppDomain);
+ }
+
+ // @dbgtodo appdomain: This should occur before the callback.
+ // Even after ExitAppDomain, the outside world will want to continue calling
+ // Continue (and thus they may need to call CordbAppDomain::GetProcess(), which Neutering
+ // would clear). Thus we can't neuter yet.
+
+ // Remove this app domain. This means any attempt to lookup the AppDomain
+ // will fail (which we do at the top of this method). Since any threads (incorrectly) referring
+ // to this AppDomain have been moved to the default AppDomain, no one should be
+ // interested in looking this AppDomain up anymore.
+ m_appDomains.RemoveBase(VmPtrToCookie(pEvent->vmAppDomain));
+ }
+
+ break;
+
+ case DB_IPCE_LOAD_ASSEMBLY:
+ {
+ LOG((LF_CORDB, LL_INFO100,
+ "RCET::HRCE: load assembly on thread %#x Asm:0x%08x AD:0x%08x \n",
+ dwVolatileThreadId,
+ VmPtrToCookie(pEvent->AssemblyData.vmDomainAssembly),
+ VmPtrToCookie(pEvent->vmAppDomain)));
+
+ _ASSERTE (pAppDomain != NULL);
+
+ // Determine if this Assembly is cached.
+ CordbAssembly * pAssembly = pAppDomain->LookupOrCreateAssembly(pEvent->AssemblyData.vmDomainAssembly);
+ _ASSERTE(pAssembly != NULL); // throws on error
+
+ // If created, or have, an Assembly, notify callback.
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ hr = pCallback1->LoadAssembly(pAppDomain, pAssembly);
+ }
+ }
+
+ break;
+
+ case DB_IPCE_UNLOAD_ASSEMBLY:
+ {
+ LOG((LF_CORDB, LL_INFO100, "RCET::DRCE: unload assembly on thread %#x Asm:0x%x AD:0x%x\n",
+ dwVolatileThreadId,
+ VmPtrToCookie(pEvent->AssemblyData.vmDomainAssembly),
+ VmPtrToCookie(pEvent->vmAppDomain)));
+
+ _ASSERTE (pAppDomain != NULL);
+
+ CordbAssembly * pAssembly = pAppDomain->LookupOrCreateAssembly(pEvent->AssemblyData.vmDomainAssembly);
+
+ if (pAssembly == NULL)
+ {
+ // No assembly. This could happen if we attach right before an unload event is sent.
+ return;
+ }
+ _ASSERTE(pAssembly != NULL);
+ INDEBUG(pAssembly->DbgAssertAssemblyDeleted());
+
+ // Ensure the assembly gets neutered when we call continue.
+ AddToNeuterOnContinueList(pAssembly); // throws
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ hr = pCallback1->UnloadAssembly(pAppDomain, pAssembly);
+ }
+
+ pAppDomain->RemoveAssemblyFromCache(pEvent->AssemblyData.vmDomainAssembly);
+ }
+
+ break;
+
+ case DB_IPCE_FUNC_EVAL_COMPLETE:
+ {
+ LOG((LF_CORDB, LL_INFO1000, "RCET::DRCE: func eval complete.\n"));
+
+ CordbEval *pEval = NULL;
+ {
+ pEval = pEvent->FuncEvalComplete.funcEvalKey.UnWrapAndRemove(this);
+ if (pEval == NULL)
+ {
+ _ASSERTE(!"Bogus FuncEval handle in IPC block.");
+ // Bogus handle in IPC block.
+ break;
+ }
+ }
+ _ASSERTE(pEval != NULL);
+
+ _ASSERTE(pThread != NULL);
+ _ASSERTE(pAppDomain != NULL);
+
+ CONSISTENCY_CHECK_MSGF(pEval->m_DbgAppDomainStarted == pAppDomain,
+ ("AppDomain changed from Func-Eval. Eval=%p, Started=%p, Now=%p\n",
+ pEval, pEval->m_DbgAppDomainStarted, (void*) pAppDomain));
+
+ // Hold the data about the result in the CordbEval for later.
+ pEval->m_complete = true;
+ pEval->m_successful = !!pEvent->FuncEvalComplete.successful;
+ pEval->m_aborted = !!pEvent->FuncEvalComplete.aborted;
+ pEval->m_resultAddr = pEvent->FuncEvalComplete.resultAddr;
+ pEval->m_vmObjectHandle = pEvent->FuncEvalComplete.vmObjectHandle;
+ pEval->m_resultType = pEvent->FuncEvalComplete.resultType;
+ pEval->m_resultAppDomainToken = pEvent->FuncEvalComplete.vmAppDomain;
+
+ CordbAppDomain *pResultAppDomain = LookupOrCreateAppDomain(pEvent->FuncEvalComplete.vmAppDomain);
+
+ _ASSERTE(OutstandingEvalCount() > 0);
+ DecrementOutstandingEvalCount();
+
+ CONSISTENCY_CHECK_MSGF(pEval->m_DbgAppDomainStarted == pAppDomain,
+ ("AppDomain changed from Func-Eval. Eval=%p, Started=%p, Now=%p\n",
+ pEval, pEval->m_DbgAppDomainStarted, (void*) pAppDomain));
+
+ // If we did this func eval with this thread stopped at an excpetion, then we need to pretend as if we
+ // really didn't continue from the exception, since, of course, we really didn't on the Left Side.
+ if (pEval->IsEvalDuringException())
+ {
+ pThread->SetExInfo(pEval->m_vmThreadOldExceptionHandle);
+ }
+
+ bool fEvalCompleted = pEval->m_successful || pEval->m_aborted;
+
+ // If a CallFunction() is aborted, the LHS may not complete the abort
+ // immediately and hence we cant do a SendCleanup() at that point. Also,
+ // the debugger may (incorrectly) release the CordbEval before this
+ // DB_IPCE_FUNC_EVAL_COMPLETE event is received. Hence, we maintain an
+ // extra ref-count to determine when this can be done.
+ // Note that this can cause a two-way DB_IPCE_FUNC_EVAL_CLEANUP event
+ // to be sent. Hence, it has to be done before the Continue (see issue 102745).
+
+
+ // Note that if the debugger has already (incorrectly) released the CordbEval,
+ // pEval will be pointing to garbage and should not be used by the debugger.
+ if (fEvalCompleted)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE2(this, pLockHolder, pEvent, "thread=0x%p, eval=0x%p. (Complete)", pThread, pEval);
+ pCallback1->EvalComplete(pResultAppDomain, pThread, pEval);
+ }
+ else
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE2(this, pLockHolder, pEvent, "pThread=0x%p, eval=0x%p. (Exception)", pThread, pEval);
+ pCallback1->EvalException(pResultAppDomain, pThread, pEval);
+ }
+
+ // This release may send an DB_IPCE_FUNC_EVAL_CLEANUP IPC event. That's ok b/c
+ // we're still synced even if if Continue was called inside the callback.
+ // That's because the StopContinueHolder bumped up the stopcount.
+ // Corresponding AddRef() in CallFunction().
+ // @todo - this is leaked if we don't get an EvalComplete event (eg, process exits with
+ // in middle of func-eval).
+ pEval->Release();
+ }
+ break;
+
+
+ case DB_IPCE_NAME_CHANGE:
+ {
+ LOG((LF_CORDB, LL_INFO1000, "RCET::HRCE: Name Change %d 0x%p\n",
+ dwVolatileThreadId,
+ VmPtrToCookie(pEvent->NameChange.vmAppDomain)));
+
+ pThread = NULL;
+ pAppDomain.Clear();
+ if (pEvent->NameChange.eventType == THREAD_NAME_CHANGE)
+ {
+ // Lookup the CordbThread that matches this runtime thread.
+ if (!pEvent->NameChange.vmThread.IsNull())
+ {
+ pThread = LookupOrCreateThread(pEvent->NameChange.vmThread);
+ }
+ }
+ else
+ {
+ _ASSERTE (pEvent->NameChange.eventType == APP_DOMAIN_NAME_CHANGE);
+ pAppDomain.Assign(LookupOrCreateAppDomain(pEvent->NameChange.vmAppDomain));
+ if (pAppDomain)
+ {
+ pAppDomain->InvalidateName();
+ }
+ }
+
+ if (pThread || pAppDomain)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->NameChange(pAppDomain, pThread);
+ }
+ }
+
+ break;
+
+ case DB_IPCE_UPDATE_MODULE_SYMS:
+ {
+ RSExtSmartPtr<IStream> pStream;
+
+ // Find the app domain the module lives in.
+ _ASSERTE (pAppDomain != NULL);
+
+ // Find the Right Side module for this module.
+ CordbModule * pModule = pAppDomain->LookupOrCreateModule(pEvent->UpdateModuleSymsData.vmDomainFile);
+ _ASSERTE(pModule != NULL);
+
+ // This is a legacy event notification for updated PDBs.
+ // Creates a new IStream object. Ownership is handed off via callback.
+ IDacDbiInterface::SymbolFormat symFormat = pModule->GetInMemorySymbolStream(&pStream);
+
+ // We shouldn't get this event if there aren't PDB symbols waiting. Specifically we don't want
+ // to incur the cost of copying over ILDB symbols here without the debugger asking for them.
+ // Eventually we may remove this callback as well and always rely on explicit requests.
+ _ASSERTE(symFormat == IDacDbiInterface::kSymbolFormatPDB);
+
+ if (symFormat == IDacDbiInterface::kSymbolFormatPDB)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+
+ _ASSERTE(pStream != NULL); // Shouldn't send the event if we don't have a stream.
+
+ pCallback1->UpdateModuleSymbols(pAppDomain, pModule, pStream);
+ }
+
+ }
+ break;
+
+ case DB_IPCE_MDA_NOTIFICATION:
+ {
+ RSInitHolder<CordbMDA> pMDA(new CordbMDA(this, &pEvent->MDANotification)); // throws
+
+ // Ctor leaves both internal + ext Ref at 0, adding to neuter list bumps int-ref up to 1.
+ // Neutering will dump it back down to zero.
+ this->AddToNeuterOnExitList(pMDA);
+
+ // We bump up and down the external ref so that even if the callback doensn't touch the refs,
+ // our Ext-Release here will still cause a 1->0 ext-ref transition, which will get it
+ // swept on the neuter list.
+ RSExtSmartPtr<ICorDebugMDA> pExternalMDARef;
+ pMDA.TransferOwnershipExternal(&pExternalMDARef);
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+
+ pCallback2->MDANotification(
+ this,
+ pThread, // may be null
+ pExternalMDARef);
+
+ // pExternalMDARef's dtor will do an external release,
+ // which is very significant because it may be the one that does the 1->0 ext ref transition,
+ // which may mean cause the "NeuterAtWill" bit to get flipped on this CordbMDA object.
+ // Since this is an external release, do it in the PUBLIC_CALLBACK scope.
+ pExternalMDARef.Clear();
+ }
+
+ break;
+ }
+
+ case DB_IPCE_CONTROL_C_EVENT:
+ {
+ hr = S_FALSE;
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ hr = pCallback1->ControlCTrap((ICorDebugProcess*) this);
+ }
+
+ {
+ RSLockHolder ch(this->GetStopGoLock());
+
+ DebuggerIPCEvent eventControlCResult;
+
+ InitIPCEvent(&eventControlCResult,
+ DB_IPCE_CONTROL_C_EVENT_RESULT,
+ false,
+ VMPTR_AppDomain::NullPtr());
+
+ // Indicate whether the debugger has handled the event.
+ eventControlCResult.hr = hr;
+
+ // Send the reply to the LS.
+ SendIPCEvent(&eventControlCResult, sizeof(eventControlCResult));
+ } // release SG lock
+
+ }
+ break;
+
+ // EnC Remap opportunity
+ case DB_IPCE_ENC_REMAP:
+ {
+ LOG((LF_CORDB, LL_INFO1000, "[%x] RCET::DRCE: EnC Remap!.\n",
+ GetCurrentThreadId()));
+
+ _ASSERTE(NULL != pAppDomain);
+
+ CordbModule * pModule = pAppDomain->LookupOrCreateModule(pEvent->EnCRemap.vmDomainFile);
+ PREFIX_ASSUME(pModule != NULL);
+
+ CordbFunction * pCurFunction = NULL;
+ CordbFunction * pResumeFunction = NULL;
+
+ // lookup the version of the function that we are mapping from
+ // this is the one that is currently running
+ pCurFunction = pModule->LookupOrCreateFunction(
+ pEvent->EnCRemap.funcMetadataToken, pEvent->EnCRemap.currentVersionNumber);
+
+ // lookup the version of the function that we are mapping to
+ // it will always be the most recent
+ pResumeFunction = pModule->LookupOrCreateFunction(
+ pEvent->EnCRemap.funcMetadataToken, pEvent->EnCRemap.resumeVersionNumber);
+
+ _ASSERTE(pCurFunction->GetEnCVersionNumber() < pResumeFunction->GetEnCVersionNumber());
+
+ RSSmartPtr<CordbFunction> pRefCurFunction(pCurFunction);
+ RSSmartPtr<CordbFunction> pRefResumeFunction(pResumeFunction);
+
+ // Verify we're not about to overwrite an outstanding remap IP
+ // This should only be set while a remap opportunity is being handled,
+ // and cleared (by CordbThread::MarkStackFramesDirty) on Continue.
+ // We want to be absolutely sure we don't accidentally keep a stale pointer
+ // around because it would point to arbitrary stack space in the CLR potentially
+ // leading to stack corruption.
+ _ASSERTE( pThread->m_EnCRemapFunctionIP == NULL );
+
+ // Stash the address of the remap IP buffer. This indicates that calling
+ // RemapFunction is valid and provides a communications channel between the RS
+ // and LS for the remap IL offset.
+ pThread->m_EnCRemapFunctionIP = pEvent->EnCRemap.resumeILOffset;
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback2->FunctionRemapOpportunity(
+ pAppDomain,
+ pThread,
+ pCurFunction,
+ pResumeFunction,
+ (ULONG32)pEvent->EnCRemap.currentILOffset);
+ }
+
+ // Implicit release on pCurFunction and pResumeFunction.
+ }
+ break;
+
+ // EnC Remap complete
+ case DB_IPCE_ENC_REMAP_COMPLETE:
+ {
+ LOG((LF_CORDB, LL_INFO1000, "[%x] RCET::DRCE: EnC Remap Complete!.\n",
+ GetCurrentThreadId()));
+
+ _ASSERTE(NULL != pAppDomain);
+
+ CordbModule* pModule = pAppDomain->LookupOrCreateModule(pEvent->EnCRemap.vmDomainFile);
+ PREFIX_ASSUME(pModule != NULL);
+
+ // Find the function we're remapping to, which must be the latest version
+ CordbFunction *pRemapFunction=
+ pModule->LookupFunctionLatestVersion(pEvent->EnCRemapComplete.funcMetadataToken);
+ PREFIX_ASSUME(pRemapFunction != NULL);
+
+ // Dispatch the FunctionRemapComplete callback
+ RSSmartPtr<CordbFunction> pRef(pRemapFunction);
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback2->FunctionRemapComplete(pAppDomain, pThread, pRemapFunction);
+ }
+ // Implicit release on pRemapFunction via holder
+ }
+ break;
+
+ case DB_IPCE_BREAKPOINT_SET_ERROR:
+ {
+ LOG((LF_CORDB, LL_INFO1000, "RCET::DRCE: breakpoint set error.\n"));
+
+ RSSmartPtr<CordbBreakpoint> pRef;
+
+ _ASSERTE(pThread != NULL);
+ _ASSERTE(pAppDomain != NULL);
+
+ // Find the breakpoint object on this side.
+ CordbBreakpoint * pBreakpoint = NULL;
+
+
+ if (pThread == NULL)
+ {
+ // We've found cases out in the wild where we get this event on a thread we don't recognize.
+ // We're not sure how this happens. Add a runtime check to protect ourselves to avoid the
+ // an AV. We still assert because this should not be happening.
+ // It likely means theres some issue where we failed to send a CreateThread notification.
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "BreakpointSetError on unrecognized thread. %p\n", pBreakpoint);
+
+ _ASSERTE(!"Missing thread on bp set error");
+ break;
+ }
+
+ pBreakpoint = pAppDomain->m_breakpoints.GetBase(LsPtrToCookie(pEvent->BreakpointSetErrorData.breakpointToken));
+
+ if (pBreakpoint != NULL)
+ {
+ ICorDebugBreakpoint * pIBreakpoint = CordbBreakpointToInterface(pBreakpoint);
+ _ASSERTE(pIBreakpoint != NULL);
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE2(this, pLockHolder, pEvent, "thread=0x%p, bp=0x%p", pThread, pBreakpoint);
+ pCallback1->BreakpointSetError(pAppDomain, pThread, pIBreakpoint, 0);
+ }
+ }
+ // Implicit release on pRef.
+ }
+ break;
+
+
+ case DB_IPCE_EXCEPTION_CALLBACK2:
+ {
+ STRESS_LOG4(LF_CORDB, LL_INFO100,
+ "RCET::DRCE: Exception2 0x%p 0x%X 0x%X 0x%X\n",
+ pEvent->ExceptionCallback2.framePointer.GetSPValue(),
+ pEvent->ExceptionCallback2.nOffset,
+ pEvent->ExceptionCallback2.eventType,
+ pEvent->ExceptionCallback2.dwFlags
+ );
+
+ if (pThread == NULL)
+ {
+ // We've got an exception on a thread we don't know about. This could be a thread that
+ // has never run any managed code, so let's just ignore the exception. We should have
+ // already sent a log message about this situation for the EXCEPTION callback above.
+ _ASSERTE( pEvent->ExceptionCallback2.eventType == DEBUG_EXCEPTION_UNHANDLED );
+ break;
+ }
+
+ pThread->SetExInfo(pEvent->ExceptionCallback2.vmExceptionHandle);
+
+ //
+ // Send all the information back to the debugger.
+ //
+ RSSmartPtr<CordbFrame> pFrame;
+
+ FramePointer fp = pEvent->ExceptionCallback2.framePointer;
+ if (fp != LEAF_MOST_FRAME)
+ {
+ // The interface forces us to to pass a FramePointer via an ICorDebugFrame.
+ // However, we can't get a real ICDFrame without a stackwalk, and we don't
+ // want to do a stackwalk now. so pass a netuered proxy frame. The shim
+ // can map this to a real frame.
+ // See comments at CordbPlaceHolderFrame class for details.
+ pFrame.Assign(new CordbPlaceholderFrame(this, fp));
+ }
+
+ CorDebugExceptionCallbackType type = pEvent->ExceptionCallback2.eventType;
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE3(this, pLockHolder, pEvent, "pThread=0x%p, frame=%p, type=%d", pThread, (ICorDebugFrame*) pFrame, type);
+ hr = pCallback2->Exception(
+ pThread->m_pAppDomain,
+ pThread,
+ pFrame,
+ (ULONG32)(pEvent->ExceptionCallback2.nOffset),
+ type,
+ pEvent->ExceptionCallback2.dwFlags);
+ }
+ }
+ break;
+
+ case DB_IPCE_EXCEPTION_UNWIND:
+ {
+ STRESS_LOG2(LF_CORDB, LL_INFO100,
+ "RCET::DRCE: Exception Unwind 0x%X 0x%X\n",
+ pEvent->ExceptionCallback2.eventType,
+ pEvent->ExceptionCallback2.dwFlags
+ );
+
+ if (pThread == NULL)
+ {
+ // We've got an exception on a thread we don't know about. This probably should never
+ // happen (if it's unwinding, then we expect a managed frame on the stack, and so we should
+ // know about the thread), but if it does fall back to ignoring the exception.
+ _ASSERTE( !"Got unwind event for unknown exception" );
+ break;
+ }
+
+ //
+ // Send all the information back to the debugger.
+ //
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE1(this, pLockHolder, pEvent, "pThread=0x%p", pThread);
+ hr = pCallback2->ExceptionUnwind(
+ pThread->m_pAppDomain,
+ pThread,
+ pEvent->ExceptionUnwind.eventType,
+ pEvent->ExceptionUnwind.dwFlags);
+ }
+ }
+ break;
+
+
+ case DB_IPCE_INTERCEPT_EXCEPTION_COMPLETE:
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO100, "RCET::DRCE: Exception Interception Complete.\n");
+
+ if (pThread == NULL)
+ {
+ // We've got an exception on a thread we don't know about. This probably should never
+ // happen (if it's unwinding, then we expect a managed frame on the stack, and so we should
+ // know about the thread), but if it does fall back to ignoring the exception.
+ _ASSERTE( !"Got complete event for unknown exception" );
+ break;
+ }
+
+ //
+ // Tell the debugger that the exception has been intercepted. This is similar to the
+ // notification we give when we start unwinding for a non-intercepted exception, except that the
+ // interception has been completed at this point, which means that we are conceptually at the end
+ // of the second pass.
+ //
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE1(this, pLockHolder, pEvent, "pThread=0x%p", pThread);
+ hr = pCallback2->ExceptionUnwind(
+ pThread->m_pAppDomain,
+ pThread,
+ DEBUG_EXCEPTION_INTERCEPTED,
+ 0);
+ }
+ }
+ break;
+#ifdef TEST_DATA_CONSISTENCY
+ case DB_IPCE_TEST_CRST:
+ {
+ EX_TRY
+ {
+ // the left side has signaled that we should test whether pEvent->TestCrstData.vmCrst is held
+ GetDAC()->TestCrst(pEvent->TestCrstData.vmCrst);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (pEvent->TestCrstData.fOkToTake)
+ {
+ _ASSERTE(hr == S_OK);
+ if (hr != S_OK)
+ {
+ // we want to catch this in retail builds too
+ ThrowHR(E_FAIL);
+ }
+ }
+ else // the lock was already held
+ {
+ // see if we threw because the lock was held
+ _ASSERTE(hr == CORDBG_E_PROCESS_NOT_SYNCHRONIZED);
+ if (hr != CORDBG_E_PROCESS_NOT_SYNCHRONIZED)
+ {
+ // we want to catch this in retail builds too
+ ThrowHR(E_FAIL);
+ }
+ }
+
+ }
+ break;
+
+ case DB_IPCE_TEST_RWLOCK:
+ {
+ EX_TRY
+ {
+ // the left side has signaled that we should test whether pEvent->TestRWLockData.vmRWLock is held
+ GetDAC()->TestRWLock(pEvent->TestRWLockData.vmRWLock);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (pEvent->TestRWLockData.fOkToTake)
+ {
+ _ASSERTE(hr == S_OK);
+ if (hr != S_OK)
+ {
+ // we want to catch this in retail builds too
+ ThrowHR(E_FAIL);
+ }
+ }
+ else // the lock was already held
+ {
+ // see if we threw because the lock was held
+ _ASSERTE(hr == CORDBG_E_PROCESS_NOT_SYNCHRONIZED);
+ if (hr != CORDBG_E_PROCESS_NOT_SYNCHRONIZED)
+ {
+ // we want to catch this in retail builds too
+ ThrowHR(E_FAIL);
+ }
+ }
+ }
+ break;
+#endif
+
+ default:
+ _ASSERTE(!"Unknown event");
+ LOG((LF_CORDB, LL_INFO1000,
+ "[%x] RCET::HRCE: Unknown event: 0x%08x\n",
+ GetCurrentThreadId(), pEvent->type));
+ }
+
+
+ FinishEventDispatch();
+}
+#ifdef _PREFAST_
+#pragma warning(pop)
+#endif
+
+//---------------------------------------------------------------------------------------
+// Callback for prepopulating threads.
+//
+// Arugments:
+// vmThread - thread as part of the eunmeration.
+// pUserData - data supplied with callback. It's a CordbProcess* object.
+//
+
+// static
+void CordbProcess::ThreadEnumerationCallback(VMPTR_Thread vmThread, void * pUserData)
+{
+ CordbProcess * pThis = reinterpret_cast<CordbProcess *> (pUserData);
+ INTERNAL_DAC_CALLBACK(pThis);
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "ThreadEnumerationCallback()\n");
+
+ // Do lookup / lazy-create.
+ pThis->LookupOrCreateThread(vmThread);
+}
+
+//---------------------------------------------------------------------------------------
+// Fully build up the CordbThread cache to match VM state.
+void CordbProcess::PrepopulateThreadsOrThrow()
+{
+ RSLockHolder lockHolder(GetProcessLock());
+ if (IsDacInitialized())
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "PrepopulateThreadsOrThrow()\n");
+ GetDAC()->EnumerateThreads(ThreadEnumerationCallback, this);
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// Create a Thread enumerator
+//
+// Arguments:
+// pOwnerObj - object (a CordbProcess or CordbThread) that will own the enumerator.
+// pOwnerList - the neuter list that the enumerator will live on
+// pHolder - an outparameter for the enumerator to be initialized.
+//
+void CordbProcess::BuildThreadEnum(CordbBase * pOwnerObj, NeuterList * pOwnerList, RSInitHolder<CordbHashTableEnum> * pHolder)
+{
+ CordbHashTableEnum::BuildOrThrow(
+ pOwnerObj,
+ pOwnerList,
+ &m_userThreads,
+ IID_ICorDebugThreadEnum,
+ pHolder);
+}
+
+// Public implementation of ICorDebugProcess::EnumerateThreads
+HRESULT CordbProcess::EnumerateThreads(ICorDebugThreadEnum **ppThreads)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ if (m_detached)
+ {
+ // #Detach_Check:
+ //
+ // FUTURE: Consider adding this IF block to the PUBLIC_API macros so that
+ // typical public APIs fail quickly if we're trying to do a detach. For
+ // now, I'm hand-adding this check only to the few problematic APIs that get
+ // called while queuing the fake attach events. In these cases, it is not
+ // enough to check if CordbProcess::IsNeutered(), as the detaching thread
+ // may have begun the detaching and neutering process, but not be
+ // finished--in which case m_detached is true, but
+ // CordbProcess::IsNeutered() is still false.
+ ThrowHR(CORDBG_E_PROCESS_DETACHED);
+ }
+
+ ValidateOrThrow(ppThreads);
+
+ RSInitHolder<CordbHashTableEnum> pEnum;
+ InternalEnumerateThreads(pEnum.GetAddr());
+
+ pEnum.TransferOwnershipExternal(ppThreads);
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+// Internal implementation of EnumerateThreads
+VOID CordbProcess::InternalEnumerateThreads(RSInitHolder<CordbHashTableEnum> *ppThreads)
+{
+ INTERNAL_API_ENTRY(this);
+ // Needs to prepopulate
+ PrepopulateThreadsOrThrow();
+ BuildThreadEnum(this, this->GetContinueNeuterList(), ppThreads);
+}
+
+// Implementation of ICorDebugProcess::GetThread
+HRESULT CordbProcess::GetThread(DWORD dwThreadId, ICorDebugThread **ppThread)
+{
+ PUBLIC_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppThread, ICorDebugThread **);
+
+ // No good pre-existing ATT_* contract for this.
+ // Because for legacy, we have to allow this on the win32 event thread.
+ *ppThread = NULL;
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ RSLockHolder lockHolder(GetProcessLock());
+ if (m_detached)
+ {
+ // See code:CordbProcess::EnumerateThreads#Detach_Check
+ ThrowHR(CORDBG_E_PROCESS_DETACHED);
+ }
+ CordbThread * pThread = TryLookupOrCreateThreadByVolatileOSId(dwThreadId);
+ if (pThread == NULL)
+ {
+ // This is a common case because we may be looking up an unmanaged thread.
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ *ppThread = static_cast<ICorDebugThread*> (pThread);
+ pThread->ExternalAddRef();
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::GT returns id=0x%x hr=0x%x ppThread=0x%p",
+ dwThreadId, hr, *ppThread));
+ return hr;
+}
+
+HRESULT CordbProcess::ThreadForFiberCookie(DWORD fiberCookie,
+ ICorDebugThread **ppThread)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbProcess::GetHelperThreadID(DWORD *pThreadID)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ _ASSERTE(m_pShim != NULL);
+ if (pThreadID == NULL)
+ {
+ return (E_INVALIDARG);
+ }
+
+ HRESULT hr = S_OK;
+ // Return the ID of the current helper thread. There may be no thread in the process, or there may be a true helper
+ // thread.
+ if ((m_helperThreadId != 0) && !m_helperThreadDead)
+ {
+ *pThreadID = m_helperThreadId;
+ }
+ else if ((GetDCB() != NULL) && (GetDCB()->m_helperThreadId != 0))
+ {
+ EX_TRY
+ {
+ // be sure we have the latest information
+ UpdateRightSideDCB();
+ *pThreadID = GetDCB()->m_helperThreadId;
+ }
+ EX_CATCH_HRESULT(hr);
+
+ }
+ else
+ {
+ *pThreadID = 0;
+ }
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Sends IPC event to set all the managed threads, except for the one given, to the given state
+//
+// Arguments:
+// state - The state to set the threads to.
+// pExceptThread - The thread to not set. This is usually the thread that is currently
+// sending an IPC event to the RS, and should be excluded.
+//
+// Return Value:
+// Typical HRESULT symantics, nothing abnormal.
+//
+HRESULT CordbProcess::SetAllThreadsDebugState(CorDebugThreadState state,
+ ICorDebugThread * pExceptThread)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pExceptThread, ICorDebugThread *);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ if (GetShim() == NULL)
+ {
+ return E_NOTIMPL;
+ }
+ CordbThread * pCordbExceptThread = static_cast<CordbThread *> (pExceptThread);
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::SATDS: except thread=0x%08x 0x%x\n",
+ pExceptThread,
+ (pCordbExceptThread != NULL) ? pCordbExceptThread->m_id : 0));
+
+ // Send one event to the Left Side to twiddle each thread's state.
+ DebuggerIPCEvent event;
+
+ InitIPCEvent(&event, DB_IPCE_SET_ALL_DEBUG_STATE, true, VMPTR_AppDomain::NullPtr());
+
+ event.SetAllDebugState.vmThreadToken = ((pCordbExceptThread != NULL) ?
+ pCordbExceptThread->m_vmThreadToken : VMPTR_Thread::NullPtr());
+
+ event.SetAllDebugState.debugState = state;
+
+ HRESULT hr = SendIPCEvent(&event, sizeof(DebuggerIPCEvent));
+
+ hr = WORST_HR(hr, event.hr);
+
+ // If that worked, then loop over all the threads on this side and set their states.
+ if (SUCCEEDED(hr))
+ {
+ RSLockHolder lockHolder(GetProcessLock());
+ HASHFIND hashFind;
+ CordbThread * pThread;
+
+ // We don't need to prepopulate here (to collect LS state) because we're just updating RS state.
+ for (pThread = m_userThreads.FindFirst(&hashFind);
+ pThread != NULL;
+ pThread = m_userThreads.FindNext(&hashFind))
+ {
+ if (pThread != pCordbExceptThread)
+ {
+ pThread->m_debugState = state;
+ }
+ }
+ }
+
+ return hr;
+}
+
+
+HRESULT CordbProcess::EnumerateObjects(ICorDebugObjectEnum **ppObjects)
+{
+ /* !!! */
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppObjects, ICorDebugObjectEnum **);
+
+ return E_NOTIMPL;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Determines if the target address is a "CLR transition stub".
+//
+// Arguments:
+// address - The address of an instruction to check in the target address space.
+// pfTransitionStub - Space to store the result, TRUE if the address belongs to a
+// transition stub, FALSE if not. Only valid if this method returns a success code.
+//
+// Return Value:
+// Typical HRESULT symantics, nothing abnormal.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::IsTransitionStub(CORDB_ADDRESS address, BOOL *pfTransitionStub)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pfTransitionStub, BOOL *);
+
+ // Default to FALSE
+ *pfTransitionStub = FALSE;
+
+ if (this->m_helperThreadDead)
+ {
+ return S_OK;
+ }
+
+ // If we're not initialized, then it can't be a stub...
+ if (!m_initialized)
+ {
+ return S_OK;
+ }
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ DebuggerIPCEvent eventData;
+
+ InitIPCEvent(&eventData, DB_IPCE_IS_TRANSITION_STUB, true, VMPTR_AppDomain::NullPtr());
+
+ eventData.IsTransitionStub.address = CORDB_ADDRESS_TO_PTR(address);
+
+ hr = SendIPCEvent(&eventData, sizeof(eventData));
+ hr = WORST_HR(hr, eventData.hr);
+ IfFailThrow(hr);
+
+ _ASSERTE(eventData.type == DB_IPCE_IS_TRANSITION_STUB_RESULT);
+
+ *pfTransitionStub = eventData.IsTransitionStubResult.isStub;
+ LOG((LF_CORDB, LL_INFO1000, "CP::ITS: addr=0x%p result=%d\n", address, *pfTransitionStub));
+ // @todo - beware that IsTransitionStub has a very important sideeffect - it synchronizes the runtime!
+ // This for example covers an OS bug where SetThreadContext may silently fail if we're not synchronized.
+ // (See IMDArocess::SetThreadContext for details on that bug).
+ // If we ever stop using IPC events here and only use DAC; we need to be aware of that.
+
+ // Check against DAC primitives
+ {
+ BOOL fIsStub2 = GetDAC()->IsTransitionStub(address);
+ (void)fIsStub2; //prevent "unused variable" error from GCC
+ CONSISTENCY_CHECK_MSGF(*pfTransitionStub == fIsStub2, ("IsStub2 failed, DAC2:%d, IPC:%d, addr:0x%p", (int) fIsStub2, (int) *pfTransitionStub, CORDB_ADDRESS_TO_PTR(address)));
+
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::ITS: FAILED hr=0x%x\n", hr));
+ }
+ return hr;
+}
+
+
+HRESULT CordbProcess::SetStopState(DWORD threadID, CorDebugThreadState state)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ return E_NOTIMPL;
+}
+
+HRESULT CordbProcess::IsOSSuspended(DWORD threadID, BOOL *pbSuspended)
+{
+ PUBLIC_API_ENTRY(this);
+ // Gotta have a place for the result!
+ if (!pbSuspended)
+ return E_INVALIDARG;
+
+ FAIL_IF_NEUTERED(this);
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ RSLockHolder lockHolder(GetProcessLock());
+
+ // Have we seen this thread?
+ CordbUnmanagedThread *ut = GetUnmanagedThread(threadID);
+
+ // If we have, and if we've suspended it, then say so.
+ if (ut && ut->IsSuspended())
+ {
+ *pbSuspended = TRUE;
+ }
+ else
+ {
+ *pbSuspended = FALSE;
+ }
+#else
+ // Not interop-debugging, we never OS suspend.
+ *pbSuspended = FALSE;
+#endif
+ return S_OK;
+}
+
+//
+// This routine reads a thread context from the process being debugged, taking into account the fact that the context
+// record may be a different size than the one we compiled with. On systems < NT5, then OS doesn't usually allocate
+// space for the extended registers. However, the CONTEXT struct that we compile with does have this space.
+//
+HRESULT CordbProcess::SafeReadThreadContext(LSPTR_CONTEXT pContext, DT_CONTEXT * pCtx)
+{
+ HRESULT hr = S_OK;
+
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ EX_TRY
+ {
+
+ void *pRemoteContext = pContext.UnsafeGet();
+ TargetBuffer tbFull(pRemoteContext, sizeof(DT_CONTEXT));
+
+ // The context may have 2 parts:
+ // 1. Base register, which are always present.
+ // 2. Optional extended registers, which are only present if CONTEXT_EXTENDED_REGISTERS is set
+ // in the flags.
+
+ // At a minimum we have room for a whole context up to the extended registers.
+ #if defined(DT_CONTEXT_EXTENDED_REGISTERS)
+ ULONG32 minContextSize = offsetof(DT_CONTEXT, ExtendedRegisters);
+ #else
+ ULONG32 minContextSize = sizeof(DT_CONTEXT);
+ #endif
+
+ // Read the minimum part.
+ TargetBuffer tbMin = tbFull.SubBuffer(0, minContextSize);
+ SafeReadBuffer(tbMin, (BYTE*) pCtx);
+
+ #if defined(DT_CONTEXT_EXTENDED_REGISTERS)
+ void *pCurExtReg = (void*)((UINT_PTR)pCtx + minContextSize);
+ TargetBuffer tbExtended = tbFull.SubBuffer(minContextSize);
+
+ // Now, read the extended registers if the context contains them. If the context does not have extended registers,
+ // just set them to zero.
+ if (SUCCEEDED(hr) && (pCtx->ContextFlags & CONTEXT_EXTENDED_REGISTERS) == CONTEXT_EXTENDED_REGISTERS)
+ {
+ SafeReadBuffer(tbExtended, (BYTE*) pCurExtReg);
+ }
+ else
+ {
+ memset(pCurExtReg, 0, tbExtended.cbSize);
+ }
+ #endif
+
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+//
+// This routine writes a thread context to the process being debugged, taking into account the fact that the context
+// record may be a different size than the one we compiled with. On systems < NT5, then OS doesn't usually allocate
+// space for the extended registers. However, the CONTEXT struct that we compile with does have this space.
+//
+HRESULT CordbProcess::SafeWriteThreadContext(LSPTR_CONTEXT pContext, const DT_CONTEXT * pCtx)
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+ DWORD sizeToWrite = sizeof(DT_CONTEXT);
+
+ BYTE * pRemoteContext = (BYTE*) pContext.UnsafeGet();
+ BYTE * pCtxSource = (BYTE*) pCtx;
+
+
+#if defined(DT_CONTEXT_EXTENDED_REGISTERS)
+ // If our context has extended registers, then write the whole thing. Otherwise, just write the minimum part.
+ if ((pCtx->ContextFlags & DT_CONTEXT_EXTENDED_REGISTERS) != DT_CONTEXT_EXTENDED_REGISTERS)
+ {
+ sizeToWrite = offsetof(DT_CONTEXT, ExtendedRegisters);
+ }
+#endif
+
+// 64 bit windows puts space for the first 6 stack parameters in the CONTEXT structure so that
+// kernel to usermode transitions don't have to allocate a CONTEXT and do a seperate sub rsp
+// to allocate stack spill space for the arguments. This means that writing to P1Home - P6Home
+// will overwrite the arguments of some function higher on the stack, very bad. Conceptually you
+// can think of these members as not being part of the context, ie they don't represent something
+// which gets saved or restored on context switches. They are just space we shouldn't overwrite.
+// See issue 630276 for more details.
+#if defined DBG_TARGET_AMD64
+ pRemoteContext += offsetof(CONTEXT, ContextFlags); // immediately follows the 6 parameters P1-P6
+ pCtxSource += offsetof(CONTEXT, ContextFlags);
+ sizeToWrite -= offsetof(CONTEXT, ContextFlags);
+#endif
+
+ EX_TRY
+ {
+ // Write the context.
+ TargetBuffer tb(pRemoteContext, sizeToWrite);
+ SafeWriteBuffer(tb, (const BYTE*) pCtxSource);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+
+HRESULT CordbProcess::GetThreadContext(DWORD threadID, ULONG32 contextSize, BYTE context[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ FAIL_IF_MANAGED_ONLY(this);
+
+ DT_CONTEXT * pContext;
+ LOG((LF_CORDB, LL_INFO10000, "CP::GTC: thread=0x%x\n", threadID));
+
+ RSLockHolder lockHolder(GetProcessLock());
+
+ if (contextSize != sizeof(DT_CONTEXT))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CP::GTC: thread=0x%x, context size is invalid.\n", threadID));
+ return E_INVALIDARG;
+ }
+
+ pContext = reinterpret_cast<DT_CONTEXT *>(context);
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(context, BYTE, contextSize, true, true);
+
+#if !defined(FEATURE_INTEROP_DEBUGGING)
+ return E_NOTIMPL;
+#else
+ // Find the unmanaged thread
+ CordbUnmanagedThread *ut = GetUnmanagedThread(threadID);
+
+ if (ut == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CP::GTC: thread=0x%x, thread id is invalid.\n", threadID));
+
+ return E_INVALIDARG;
+ }
+
+ return ut->GetThreadContext((DT_CONTEXT*)context);
+#endif // FEATURE_INTEROP_DEBUGGING
+}
+
+// Public implementation of ICorDebugProcess::SetThreadContext.
+// @dbgtodo interop-debugging: this should go away in V3. Use the data-target instead. This is
+// interop-debugging aware (and cooperates with hijacks)
+HRESULT CordbProcess::SetThreadContext(DWORD threadID, ULONG32 contextSize, BYTE context[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_MANAGED_ONLY(this);
+
+#if !defined(FEATURE_INTEROP_DEBUGGING)
+ return E_NOTIMPL;
+#else
+ HRESULT hr = S_OK;
+
+ RSLockHolder lockHolder(GetProcessLock());
+
+ CordbUnmanagedThread *ut = NULL;
+
+ if (contextSize != sizeof(DT_CONTEXT))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CP::STC: thread=0x%x, context size is invalid.\n", threadID));
+ hr = E_INVALIDARG;
+ goto Label_Done;
+ }
+
+ // @todo - could we look at the context flags and return E_INVALIDARG if they're bad?
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(context, BYTE, contextSize, true, true);
+
+ // Find the unmanaged thread
+ ut = GetUnmanagedThread(threadID);
+
+ if (ut == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CP::STC: thread=0x%x, thread is invalid.\n", threadID));
+ hr = E_INVALIDARG;
+ goto Label_Done;
+ }
+
+ hr = ut->SetThreadContext((DT_CONTEXT*)context);
+
+
+ // Update the register set for the leaf-unmanaged chain so that it's consistent w/ the context.
+ // We may not necessarily be synchronized, and so these frames may be stale. Even so, no harm done.
+ if (SUCCEEDED(hr))
+ {
+ // @dbgtodo stackwalk: this should all disappear with V3 stackwalker and getting rid of SetThreadContext.
+ EX_TRY
+ {
+ // Find the managed thread. Returns NULL if thread is not managed.
+ // If we don't have a thread prveiously cached, then there's no state to update.
+ CordbThread * pThread = TryLookupThreadByVolatileOSId(threadID);
+
+ if (pThread != NULL)
+ {
+ // In V2, we used to update the CONTEXT of the leaf chain if the chain is an unmanaged chain.
+ // In Arrowhead, we just force a cleanup of the stackwalk cache. This is a more correct
+ // thing to do anyway, since the CONTEXT being set could be anything.
+ pThread->CleanupStack();
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ }
+
+Label_Done:
+ return ErrWrapper(hr);
+
+#endif // FEATURE_INTEROP_DEBUGGING
+}
+
+
+// @dbgtodo ICDProcess - When we DACize this function, we should use code:DacReplacePatches
+HRESULT CordbProcess::ReadMemory(CORDB_ADDRESS address,
+ DWORD size,
+ BYTE buffer[],
+ SIZE_T *read)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ // A read of 0 bytes is okay.
+ if (size == 0)
+ return S_OK;
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(buffer, BYTE, size, true, true);
+ VALIDATE_POINTER_TO_OBJECT(buffer, SIZE_T *);
+
+ if (address == NULL)
+ return E_INVALIDARG;
+
+ // If no read parameter is supplied, we ignore it. This matches the semantics of kernel32!ReadProcessMemory.
+ SIZE_T dummyRead;
+ if (read == NULL)
+ {
+ read = &dummyRead;
+ }
+ *read = 0;
+
+ HRESULT hr = S_OK;
+
+ CORDBRequireProcessStateOK(this);
+
+ // Grab the memory we want to read
+ // Note that this will return success on a partial read
+ ULONG32 cbRead;
+ hr = GetDataTarget()->ReadVirtual(address, buffer, size, &cbRead);
+ if (FAILED(hr))
+ {
+ hr = CORDBG_E_READVIRTUAL_FAILURE;
+ goto LExit;
+ }
+
+ // Read at least one byte
+ *read = (SIZE_T) cbRead;
+
+ // There seem to be strange cases where ReadProcessMemory will return a seemingly negative number into *read, which
+ // is an unsigned value. So we check the sanity of *read by ensuring that its no bigger than the size we tried to
+ // read.
+ if ((*read > 0) && (*read <= size))
+ {
+ LOG((LF_CORDB, LL_INFO100000, "CP::RM: read %d bytes from 0x%08x, first byte is 0x%x\n",
+ *read, (DWORD)address, buffer[0]));
+
+ if (m_initialized)
+ {
+ RSLockHolder ch(&this->m_processMutex);
+
+ // If m_pPatchTable is NULL, then it's been cleaned out b/c of a Continue for the left side. Get the table
+ // again. Only do this, of course, if the managed state of the process is initialized.
+ if (m_pPatchTable == NULL)
+ {
+ hr = RefreshPatchTable(address, *read, buffer);
+ }
+ else
+ {
+ // The previously fetched table is still good, so run through it & see if any patches are applicable
+ hr = AdjustBuffer(address, *read, buffer, NULL, AB_READ);
+ }
+ }
+ }
+
+LExit:
+ if (FAILED(hr))
+ {
+ RSLockHolder ch(&this->m_processMutex);
+ ClearPatchTable();
+ }
+ else if (*read < size)
+ {
+ // Unlike the DT api, our API is supposed to return an error on partial read
+ hr = HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY);
+ }
+ return hr;
+}
+
+// Update patches & buffer to make the left-side's usage of patches transparent
+// to our client. Behavior depends on AB_MODE:
+// AB_READ:
+// - use the RS patch table structure to replace patch opcodes in buffer.
+// AB_WRITE:
+// - update the RS patch table structure w/ new replace-opcode values
+// if we've written over them. And put the int3 back in for write-memory.
+//
+// Note: If we're writing memory over top of a patch, then it must be JITted or stub code.
+// Writing over JITed or Stub code can be dangerous since the CLR may not expect it
+// (eg. JIT data structures about the code layout may be incorrect), but in certain
+// narrow cases it may be safe (eg. replacing a constant). VS says they wouldn't expect
+// this to work, but we'll keep the support in for legacy reasons.
+//
+// address, size - describe buffer in LS memory
+// buffer - local copy of buffer that will be read/written from/to LS.
+// bufferCopy - for writeprocessmemory, copy of original buffer (w/o injected patches)
+// pbUpdatePatchTable - flag if patchtable got dirty and needs to be updated.
+HRESULT CordbProcess::AdjustBuffer( CORDB_ADDRESS address,
+ SIZE_T size,
+ BYTE buffer[],
+ BYTE **bufferCopy,
+ AB_MODE mode,
+ BOOL *pbUpdatePatchTable)
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(m_initialized);
+ _ASSERTE(this->ThreadHoldsProcessLock());
+
+ if ( address == NULL
+ || size == NULL
+ || buffer == NULL
+ || (mode != AB_READ && mode != AB_WRITE) )
+ return E_INVALIDARG;
+
+ if (pbUpdatePatchTable != NULL )
+ *pbUpdatePatchTable = FALSE;
+
+ // If we don't have a patch table loaded, then return S_OK since there are no patches to adjust
+ if (m_pPatchTable == NULL)
+ return S_OK;
+
+ //is the requested memory completely out-of-range?
+ if ((m_minPatchAddr > (address + (size - 1))) ||
+ (m_maxPatchAddr < address))
+ {
+ return S_OK;
+ }
+
+ // Without runtime offsets, we can't adjust - this should only ever happen on dumps, where there's
+ // no W32ET to get the offsets, and so they stay zeroed
+ if (!m_runtimeOffsetsInitialized)
+ return S_OK;
+
+ LOG((LF_CORDB,LL_INFO10000, "CordbProcess::AdjustBuffer at addr 0x%p\n", address));
+
+ if (mode == AB_WRITE)
+ {
+ // We don't want to mess up the original copy of the buffer, so
+ // for right now, just copy it wholesale.
+ (*bufferCopy) = new (nothrow) BYTE[size];
+ if (NULL == (*bufferCopy))
+ return E_OUTOFMEMORY;
+
+ memmove((*bufferCopy), buffer, size);
+ }
+
+ ULONG iNextFree = m_iFirstPatch;
+ while( iNextFree != DPT_TERMINATING_INDEX )
+ {
+ BYTE *DebuggerControllerPatch = m_pPatchTable + m_runtimeOffsets.m_cbPatch*iNextFree;
+ PRD_TYPE opcode = *(PRD_TYPE *)(DebuggerControllerPatch + m_runtimeOffsets.m_offOpcode);
+ CORDB_ADDRESS patchAddress = PTR_TO_CORDB_ADDRESS(*(BYTE**)(DebuggerControllerPatch + m_runtimeOffsets.m_offAddr));
+
+ if (IsPatchInRequestedRange(address, size, patchAddress))
+ {
+ if (mode == AB_READ)
+ {
+ CORDbgSetInstructionEx(buffer, address, patchAddress, opcode, size);
+ }
+ else if (mode == AB_WRITE)
+ {
+ _ASSERTE( pbUpdatePatchTable != NULL );
+ _ASSERTE( bufferCopy != NULL );
+
+ //There can be multiple patches at the same address: we don't want 2nd+ patches to get the
+ // break opcode, so we read from the unmodified copy.
+ m_rgUncommitedOpcode[iNextFree] =
+ CORDbgGetInstructionEx(*bufferCopy, address, patchAddress, opcode, size);
+
+ //put the breakpoint into the memory itself
+ CORDbgInsertBreakpointEx(buffer, address, patchAddress, opcode, size);
+
+ *pbUpdatePatchTable = TRUE;
+ }
+ else
+ _ASSERTE( !"CordbProcess::AdjustBuffergiven non(Read|Write) mode!" );
+ }
+
+ iNextFree = m_rgNextPatch[iNextFree];
+ }
+
+ // If we created a copy of the buffer but didn't modify it, then free it now.
+ if( ( mode == AB_WRITE ) && ( !*pbUpdatePatchTable ) )
+ {
+ delete [] *bufferCopy;
+ *bufferCopy = NULL;
+ }
+
+ return S_OK;
+}
+
+
+void CordbProcess::CommitBufferAdjustments( CORDB_ADDRESS start,
+ CORDB_ADDRESS end )
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(m_initialized);
+ _ASSERTE(this->ThreadHoldsProcessLock());
+ _ASSERTE(m_runtimeOffsetsInitialized);
+
+ ULONG iPatch = m_iFirstPatch;
+ while( iPatch != DPT_TERMINATING_INDEX )
+ {
+ BYTE *DebuggerControllerPatch = m_pPatchTable +
+ m_runtimeOffsets.m_cbPatch*iPatch;
+
+ BYTE *patchAddress = *(BYTE**)(DebuggerControllerPatch + m_runtimeOffsets.m_offAddr);
+
+ if (IsPatchInRequestedRange(start, (SIZE_T)(end - start), PTR_TO_CORDB_ADDRESS(patchAddress)) &&
+ !PRDIsBreakInst(&(m_rgUncommitedOpcode[iPatch])))
+ {
+ //copy this back to the copy of the patch table
+ *(PRD_TYPE *)(DebuggerControllerPatch + m_runtimeOffsets.m_offOpcode) =
+ m_rgUncommitedOpcode[iPatch];
+ }
+
+ iPatch = m_rgNextPatch[iPatch];
+ }
+}
+
+void CordbProcess::ClearBufferAdjustments( )
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(this->ThreadHoldsProcessLock());
+
+ ULONG iPatch = m_iFirstPatch;
+ while( iPatch != DPT_TERMINATING_INDEX )
+ {
+ InitializePRDToBreakInst(&(m_rgUncommitedOpcode[iPatch]));
+ iPatch = m_rgNextPatch[iPatch];
+ }
+}
+
+void CordbProcess::ClearPatchTable(void )
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(this->ThreadHoldsProcessLock());
+
+ if (m_pPatchTable != NULL )
+ {
+ delete [] m_pPatchTable;
+ m_pPatchTable = NULL;
+
+ delete [] m_rgNextPatch;
+ m_rgNextPatch = NULL;
+
+ delete [] m_rgUncommitedOpcode;
+ m_rgUncommitedOpcode = NULL;
+
+ m_iFirstPatch = DPT_TERMINATING_INDEX;
+ m_minPatchAddr = MAX_ADDRESS;
+ m_maxPatchAddr = MIN_ADDRESS;
+ m_rgData = NULL;
+ m_cPatch = 0;
+ }
+}
+
+HRESULT CordbProcess::RefreshPatchTable(CORDB_ADDRESS address, SIZE_T size, BYTE buffer[])
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(m_initialized);
+ _ASSERTE(this->ThreadHoldsProcessLock());
+
+ HRESULT hr = S_OK;
+ BYTE *rgb = NULL;
+
+ // All of m_runtimeOffsets will be zeroed out if there's been no call to code:CordbProcess::GetRuntimeOffsets.
+ // Thus for things to work, we'd have to have a live target that went and got the real values.
+ // For dumps, things are still all zeroed out because we don't have any events sent to the W32ET, don't
+ // have a live process to investigate, etc.
+ if (!m_runtimeOffsetsInitialized)
+ return S_OK;
+
+ _ASSERTE( m_runtimeOffsets.m_cbOpcode == sizeof(PRD_TYPE) );
+
+ CORDBRequireProcessStateOK(this);
+
+ if (m_pPatchTable == NULL )
+ {
+ // First, check to be sure the patch table is valid on the Left Side. If its not, then we won't read it.
+ BOOL fPatchTableValid = FALSE;
+
+ hr = SafeReadStruct(PTR_TO_CORDB_ADDRESS(m_runtimeOffsets.m_pPatchTableValid), &fPatchTableValid);
+ if (FAILED(hr) || !fPatchTableValid)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Wont refresh patch table because its not valid now.\n"));
+ return S_OK;
+ }
+
+ SIZE_T offStart = 0;
+ SIZE_T offEnd = 0;
+ UINT cbTableSlice = 0;
+
+ // Grab the patch table info
+ offStart = min(m_runtimeOffsets.m_offRgData, m_runtimeOffsets.m_offCData);
+ offEnd = max(m_runtimeOffsets.m_offRgData, m_runtimeOffsets.m_offCData) + sizeof(SIZE_T);
+ cbTableSlice = (UINT)(offEnd - offStart);
+
+ if (cbTableSlice == 0)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Wont refresh patch table because its not valid now.\n"));
+ return S_OK;
+ }
+
+ EX_TRY
+ {
+ rgb = new BYTE[cbTableSlice]; // throws
+
+ TargetBuffer tbSlice((BYTE*)m_runtimeOffsets.m_pPatches + offStart, cbTableSlice);
+ this->SafeReadBuffer(tbSlice, rgb); // Throws;
+
+ // Note that rgData is a pointer in the left side address space
+ m_rgData = *(BYTE**)(rgb + m_runtimeOffsets.m_offRgData - offStart);
+ m_cPatch = *(ULONG*)(rgb + m_runtimeOffsets.m_offCData - offStart);
+
+ // Grab the patch table
+ UINT cbPatchTable = (UINT)(m_cPatch * m_runtimeOffsets.m_cbPatch);
+
+ if (cbPatchTable == 0)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Wont refresh patch table because its not valid now.\n"));
+ _ASSERTE(hr == S_OK);
+ goto LExit; // can't return since we're in a Try/Catch
+ }
+
+ // Throwing news
+ m_pPatchTable = new BYTE[ cbPatchTable ];
+ m_rgNextPatch = new ULONG[m_cPatch];
+ m_rgUncommitedOpcode = new PRD_TYPE[m_cPatch];
+
+ TargetBuffer tb(m_rgData, cbPatchTable);
+ this->SafeReadBuffer(tb, m_pPatchTable); // Throws
+
+ //As we go through the patch table we do a number of things:
+ //
+ // 1. collect min,max address seen for quick fail check
+ //
+ // 2. Link all valid entries into a linked list, the first entry of which is m_iFirstPatch
+ //
+ // 3. Initialize m_rgUncommitedOpcode, so that we can undo local patch table changes if WriteMemory can't write
+ // atomically.
+ //
+ // 4. If the patch is in the memory we grabbed, unapply it.
+
+ ULONG iDebuggerControllerPatchPrev = DPT_TERMINATING_INDEX;
+
+ m_minPatchAddr = MAX_ADDRESS;
+ m_maxPatchAddr = MIN_ADDRESS;
+ m_iFirstPatch = DPT_TERMINATING_INDEX;
+
+ for (ULONG iPatch = 0; iPatch < m_cPatch;iPatch++)
+ {
+ // <REVISIT_TODO>@todo port: we're making assumptions about the size of opcodes,address pointers, etc</REVISIT_TODO>
+ BYTE *DebuggerControllerPatch = m_pPatchTable + m_runtimeOffsets.m_cbPatch * iPatch;
+ PRD_TYPE opcode = *(PRD_TYPE*)(DebuggerControllerPatch + m_runtimeOffsets.m_offOpcode);
+ CORDB_ADDRESS patchAddress = PTR_TO_CORDB_ADDRESS(*(BYTE**)(DebuggerControllerPatch + m_runtimeOffsets.m_offAddr));
+
+ // A non-zero opcode indicates to us that this patch is valid.
+ if (!PRDIsEmpty(opcode))
+ {
+ _ASSERTE( patchAddress != 0 );
+
+ // (1), above
+ // Note that GetPatchEndAddr() returns the address immediately AFTER the patch,
+ // so we have to subtract 1 from it below.
+ if (m_minPatchAddr > patchAddress )
+ m_minPatchAddr = patchAddress;
+ if (m_maxPatchAddr < patchAddress )
+ m_maxPatchAddr = GetPatchEndAddr(patchAddress) - 1;
+
+ // (2), above
+ if ( m_iFirstPatch == DPT_TERMINATING_INDEX)
+ {
+ m_iFirstPatch = iPatch;
+ _ASSERTE( iPatch != DPT_TERMINATING_INDEX);
+ }
+
+ if (iDebuggerControllerPatchPrev != DPT_TERMINATING_INDEX)
+ {
+ m_rgNextPatch[iDebuggerControllerPatchPrev] = iPatch;
+ }
+
+ iDebuggerControllerPatchPrev = iPatch;
+
+ // (3), above
+ InitializePRDToBreakInst(&(m_rgUncommitedOpcode[iPatch]));
+
+ // (4), above
+ if (IsPatchInRequestedRange(address, size, patchAddress))
+ {
+ _ASSERTE( buffer != NULL );
+ _ASSERTE( size != NULL );
+
+
+ //unapply the patch here.
+ CORDbgSetInstructionEx(buffer, address, patchAddress, opcode, size);
+ }
+
+ }
+ }
+
+ if (iDebuggerControllerPatchPrev != DPT_TERMINATING_INDEX)
+ {
+ m_rgNextPatch[iDebuggerControllerPatchPrev] = DPT_TERMINATING_INDEX;
+ }
+ }
+LExit:
+ ;
+ EX_CATCH_HRESULT(hr);
+ }
+
+
+ if (rgb != NULL )
+ {
+ delete [] rgb;
+ }
+
+ if (FAILED( hr ) )
+ {
+ ClearPatchTable();
+ }
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Given an address, see if there is a patch in the patch table that matches it and return
+// if its an unmanaged patch or not.
+//
+// Arguments:
+// address - The address of an instruction to check in the target address space.
+// pfPatchFound - Space to store the result, TRUE if the address belongs to a
+// patch, FALSE if not. Only valid if this method returns a success code.
+// pfPatchIsUnmanaged - Space to store the result, TRUE if the address is a patch
+// and the patch is unmanaged, FALSE if not. Only valid if this method returns a
+// success code.
+//
+// Return Value:
+// Typical HRESULT symantics, nothing abnormal.
+//
+// Note: this method is pretty in-efficient. It refreshes the patch table, then scans it.
+// Refreshing the patch table involves a scan, too, so this method could be folded
+// with that.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::FindPatchByAddress(CORDB_ADDRESS address, bool *pfPatchFound, bool *pfPatchIsUnmanaged)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+ _ASSERTE((pfPatchFound != NULL) && (pfPatchIsUnmanaged != NULL));
+ _ASSERTE(m_runtimeOffsetsInitialized);
+ FAIL_IF_NEUTERED(this);
+
+ *pfPatchFound = false;
+ *pfPatchIsUnmanaged = false;
+
+ // First things first. If the process isn't initialized, then there can be no patch table, so we know the breakpoint
+ // doesn't belong to the Runtime.
+ if (!m_initialized)
+ {
+ return S_OK;
+ }
+
+ // This method is called from the main loop of the win32 event thread in response to a first chance breakpoint event
+ // that we know is not a flare. The process has been runnning, and it may have invalidated the patch table, so we'll
+ // flush it here before refreshing it to make sure we've got the right thing.
+ //
+ // Note: we really should have the Left Side mark the patch table dirty to help optimize this.
+ ClearPatchTable();
+
+ // Refresh the patch table.
+ HRESULT hr = RefreshPatchTable();
+
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::FPBA: failed to refresh the patch table\n"));
+ return hr;
+ }
+
+ // If there is no patch table yet, then we know there is no patch at the given address, so return S_OK with
+ // *patchFound = false.
+ if (m_pPatchTable == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::FPBA: no patch table\n"));
+ return S_OK;
+ }
+
+ // Scan the patch table for a matching patch.
+ for (ULONG iNextPatch = m_iFirstPatch; iNextPatch != DPT_TERMINATING_INDEX; iNextPatch = m_rgNextPatch[iNextPatch])
+ {
+ BYTE *patch = m_pPatchTable + (m_runtimeOffsets.m_cbPatch * iNextPatch);
+ BYTE *patchAddress = *(BYTE**)(patch + m_runtimeOffsets.m_offAddr);
+ DWORD traceType = *(DWORD*)(patch + m_runtimeOffsets.m_offTraceType);
+
+ if (address == PTR_TO_CORDB_ADDRESS(patchAddress))
+ {
+ *pfPatchFound = true;
+
+ if (traceType == m_runtimeOffsets.m_traceTypeUnmanaged)
+ {
+ *pfPatchIsUnmanaged = true;
+
+#if defined(_DEBUG)
+ HRESULT hrDac = S_OK;
+ EX_TRY
+ {
+ // We should be able to double check w/ DAC that this really is outside of the runtime.
+ IDacDbiInterface::AddressType addrType = GetDAC()->GetAddressType(address);
+ CONSISTENCY_CHECK_MSGF(addrType == IDacDbiInterface::kAddressUnrecognized, ("Bad address type = %d", addrType));
+ }
+ EX_CATCH_HRESULT(hrDac);
+ CONSISTENCY_CHECK_MSGF(SUCCEEDED(hrDac), ("DAC::GetAddressType failed, hr=0x%08x", hrDac));
+#endif
+ }
+
+ break;
+ }
+ }
+
+ // If we didn't find a patch, its actually still possible that this breakpoint exception belongs to us. There are
+ // races with very large numbers of threads entering the Runtime through the same managed function. We will have
+ // multiple threads adding and removing ref counts to an int 3 in the code stream. Sometimes, this count will go to
+ // zero and the int 3 will be removed, then it will come back up and the int 3 will be replaced. The in-process
+ // logic takes pains to ensure that such cases are handled properly, therefore we need to perform the same check
+ // here to make the correct decision. Basically, the check is to see if there is indeed an int 3 at the exception
+ // address. If there is _not_ an int 3 there, then we've hit this race. We will lie and say a managed patch was
+ // found to cover this case. This is tracking the logic in DebuggerController::ScanForTriggers, where we call
+ // IsPatched.
+ if (*pfPatchFound == false)
+ {
+ // Read one instruction from the faulting address...
+#if defined(DBG_TARGET_ARM) || defined(DBG_TARGET_ARM64)
+ PRD_TYPE TrapCheck = 0;
+#else
+ BYTE TrapCheck = 0;
+#endif
+
+ HRESULT hr2 = SafeReadStruct(address, &TrapCheck);
+
+ if (SUCCEEDED(hr2) && (TrapCheck != CORDbg_BREAK_INSTRUCTION))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::FPBA: patchFound=true based on odd missing int 3 case.\n"));
+
+ *pfPatchFound = true;
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::FPBA: patchFound=%d, patchIsUnmanaged=%d\n", *pfPatchFound, *pfPatchIsUnmanaged));
+
+ return S_OK;
+}
+
+HRESULT CordbProcess::WriteMemory(CORDB_ADDRESS address, DWORD size,
+ BYTE buffer[], SIZE_T *written)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ CORDBRequireProcessStateOK(this);
+ _ASSERTE(m_runtimeOffsetsInitialized);
+
+
+ if (size == 0 || address == NULL)
+ return E_INVALIDARG;
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(buffer, BYTE, size, true, true);
+ VALIDATE_POINTER_TO_OBJECT(written, SIZE_T *);
+
+
+#if defined(_DEBUG) && defined(FEATURE_INTEROP_DEBUGGING)
+ // Shouldn't be using this to write int3. Use UM BP API instead.
+ // This is technically legal (what if the '0xcc' is data or something), so we can't fail in retail.
+ // But we can add this debug-only check to help VS migrate to the new API.
+ static ConfigDWORD configCheckInt3;
+ DWORD fCheckInt3 = configCheckInt3.val(CLRConfig::INTERNAL_DbgCheckInt3);
+ if (fCheckInt3)
+ {
+#if defined(DBG_TARGET_X86) || defined(DBG_TARGET_AMD64)
+ if (size == 1 && buffer[0] == 0xCC)
+ {
+ CONSISTENCY_CHECK_MSGF(false,
+ ("You're using ICorDebugProcess::WriteMemory() to write an 'int3' (1 byte 0xCC) at address 0x%p.\n"
+ "If you're trying to set a breakpoint, you should be using ICorDebugProcess::SetUnmanagedBreakpoint() instead.\n"
+ "(This assert is only enabled under the COM+ knob DbgCheckInt3.)\n",
+ CORDB_ADDRESS_TO_PTR(address)));
+ }
+#endif // DBG_TARGET_X86 || DBG_TARGET_AMD64
+
+ // check if we're replaced an opcode.
+ if (size == 1)
+ {
+ RSLockHolder ch(&this->m_processMutex);
+
+ NativePatch * p = GetNativePatch(CORDB_ADDRESS_TO_PTR(address));
+ if (p != NULL)
+ {
+ CONSISTENCY_CHECK_MSGF(false,
+ ("You're using ICorDebugProcess::WriteMemory() to write an 'opcode (0x%x)' at address 0x%p.\n"
+ "There's already a native patch at that address from ICorDebugProcess::SetUnmanagedBreakpoint().\n"
+ "If you're trying to remove the breakpoint, use ICDProcess::ClearUnmanagedBreakpoint() instead.\n"
+ "(This assert is only enabled under the COM+ knob DbgCheckInt3.)\n",
+ (DWORD) (buffer[0]), CORDB_ADDRESS_TO_PTR(address)));
+ }
+ }
+ }
+#endif // _DEBUG && FEATURE_INTEROP_DEBUGGING
+
+
+ *written = 0;
+
+ HRESULT hr = S_OK;
+ HRESULT hrSaved = hr; // this will hold the 'real' hresult in case of a
+ // partially completed operation
+ HRESULT hrPartialCopy = HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY);
+
+ BOOL bUpdateOriginalPatchTable = FALSE;
+ BYTE *bufferCopy = NULL;
+
+ // Only update the patch table if the managed state of the process
+ // is initialized.
+ if (m_initialized)
+ {
+ RSLockHolder ch(&this->m_processMutex);
+
+ if (m_pPatchTable == NULL )
+ {
+ if (!SUCCEEDED( hr = RefreshPatchTable() ) )
+ {
+ goto LExit;
+ }
+ }
+
+ if ( !SUCCEEDED( hr = AdjustBuffer( address,
+ size,
+ buffer,
+ &bufferCopy,
+ AB_WRITE,
+ &bUpdateOriginalPatchTable)))
+ {
+ goto LExit;
+ }
+ }
+
+ //conveniently enough, SafeWriteBuffer will throw if it can't complete the entire operation
+ EX_TRY
+ {
+ TargetBuffer tb(address, size);
+ SafeWriteBuffer(tb, buffer); // throws
+ *written = tb.cbSize; // DT's Write does everything or fails.
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (FAILED(hr))
+ {
+ if(hr != hrPartialCopy)
+ goto LExit;
+ else
+ hrSaved = hr;
+ }
+
+
+ LOG((LF_CORDB, LL_INFO100000, "CP::WM: wrote %d bytes at 0x%08x, first byte is 0x%x\n",
+ *written, (DWORD)address, buffer[0]));
+
+ if (bUpdateOriginalPatchTable == TRUE )
+ {
+ {
+ RSLockHolder ch(&this->m_processMutex);
+
+ //don't tweak patch table for stuff that isn't written to LeftSide
+ CommitBufferAdjustments(address, address + *written);
+ }
+
+ // The only way this should be able to fail is if
+ //someone else fiddles with the memory protections on the
+ //left side while it's frozen
+ EX_TRY
+ {
+ TargetBuffer tb(m_rgData, (ULONG) (m_cPatch*m_runtimeOffsets.m_cbPatch));
+ SafeWriteBuffer(tb, m_pPatchTable);
+ }
+ EX_CATCH_HRESULT(hr);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ }
+
+ // Since we may have
+ // overwritten anything (objects, code, etc), we should mark
+ // everything as needing to be re-cached.
+ m_continueCounter++;
+
+ LExit:
+ if (m_initialized)
+ {
+ RSLockHolder ch(&this->m_processMutex);
+ ClearBufferAdjustments( );
+ }
+
+ //we messed up our local copy, so get a clean copy the next time
+ //we need it
+ if (bUpdateOriginalPatchTable==TRUE)
+ {
+ if (bufferCopy != NULL)
+ {
+ memmove(buffer, bufferCopy, size);
+ delete bufferCopy;
+ }
+ }
+
+ if (FAILED( hr ))
+ {
+ //we messed up our local copy, so get a clean copy the next time
+ //we need it
+ if (bUpdateOriginalPatchTable==TRUE)
+ {
+ RSLockHolder ch(&this->m_processMutex);
+ ClearPatchTable();
+ }
+ }
+ else if( FAILED(hrSaved) )
+ {
+ hr = hrSaved;
+ }
+
+ return hr;
+}
+
+HRESULT CordbProcess::ClearCurrentException(DWORD threadID)
+{
+#ifndef FEATURE_INTEROP_DEBUGGING
+ return E_INVALIDARG;
+#else
+ PUBLIC_API_ENTRY(this);
+
+ RSLockHolder lockHolder(GetProcessLock());
+
+ // There's something wrong if you're calling this an there are no queued unmanaged events.
+ if ((m_unmanagedEventQueue == NULL) && (m_outOfBandEventQueue == NULL))
+ return E_INVALIDARG;
+
+ // Grab the unmanaged thread object.
+ CordbUnmanagedThread *pUThread = GetUnmanagedThread(threadID);
+
+ if (pUThread == NULL)
+ return E_INVALIDARG;
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::CCE: tid=0x%x\n", threadID));
+
+ // We clear both the IB and OOB event.
+ if (pUThread->HasIBEvent() && !pUThread->IBEvent()->IsEventUserContinued())
+ {
+ pUThread->IBEvent()->SetState(CUES_ExceptionCleared);
+ }
+
+ if (pUThread->HasOOBEvent())
+ {
+ // must decide exception status _before_ we continue the event.
+ _ASSERTE(!pUThread->OOBEvent()->IsEventContinuedUnhijacked());
+ pUThread->OOBEvent()->SetState(CUES_ExceptionCleared);
+ }
+
+ // If the thread is hijacked, then set the thread's debugger word to 0 to indicate to it that the
+ // exception has been cleared.
+ if (pUThread->IsGenericHijacked())
+ {
+ HRESULT hr = pUThread->SetEEDebuggerWord(0);
+ _ASSERTE(SUCCEEDED(hr));
+ }
+
+ return S_OK;
+#endif // FEATURE_INTEROP_DEBUGGING
+}
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+CordbUnmanagedThread *CordbProcess::HandleUnmanagedCreateThread(DWORD dwThreadId, HANDLE hThread, void *lpThreadLocalBase)
+{
+ INTERNAL_API_ENTRY(this);
+ CordbUnmanagedThread *ut = new (nothrow) CordbUnmanagedThread(this, dwThreadId, hThread, lpThreadLocalBase);
+
+ if (ut != NULL)
+ {
+ HRESULT hr = m_unmanagedThreads.AddBase(ut); // InternalAddRef, release on EXIT_THREAD events.
+
+ if (!SUCCEEDED(hr))
+ {
+ delete ut;
+
+ LOG((LF_CORDB, LL_INFO10000, "Failed adding unmanaged thread to process!\n"));
+ CORDBSetUnrecoverableError(this, hr, 0);
+ }
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "New CordbThread failed!\n"));
+ CORDBSetUnrecoverableError(this, E_OUTOFMEMORY, 0);
+ }
+
+ return ut;
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+
+//-----------------------------------------------------------------------------
+// Initializes the DAC
+// Arguments: none--initializes the DAC for this CordbProcess instance
+// Note: Throws on error
+//-----------------------------------------------------------------------------
+void CordbProcess::InitDac()
+{
+ // Go-Go DAC power!!
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ InitializeDac();
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // We Need DAC to debug for both Managed & Interop.
+ if (FAILED(hr))
+ {
+ // We assert here b/c we're trying to be friendly. Most likely, the cause is either:
+ // - a bad installation
+ // - a CLR dev built mscorwks but didn't build DAC.
+ SIMPLIFYING_ASSUMPTION_MSGF(false, ("Failed to load DAC while for debugging. hr=0x%08x", hr));
+ ThrowHR(hr);
+ }
+} //CordbProcess::InitDac
+
+// Update the entire RS copy of the debugger control block by reading the LS copy. The RS copy is treated as
+// a throw-away temporary buffer, rather than a true cache. That is, we make no assumptions about the
+// validity of the information over time. Thus, before using any of the values, we need to update it. We
+// update everything for simplicity; any perf hit we take by doing this instead of updating the individual
+// fields we want at any given point isn't significant, particularly if we are updating multiple fields.
+
+// Arguments:
+// none, but reads process memory from the LS debugger control block
+// Return Value: none (copies from LS DCB to RS buffer GetDCB())
+// Note: throws if SafeReadBuffer fails
+void CordbProcess::UpdateRightSideDCB()
+{
+ IfFailThrow(m_pEventChannel->UpdateRightSideDCB());
+} // CordbProcess::UpdateRightSideDCB
+
+// Update a single field with a value stored in the RS copy of the DCB. We can't update the entire LS DCB
+// because in some cases, the LS and RS are simultaneously initializing the DCB. If we initialize a field on
+// the RS and write back the whole thing, we may overwrite something the LS has initialized in the interim.
+
+// Arguments:
+// input: rsFieldAddr - the address of the field in the RS copy of the DCB that we want to write back to
+// the LS DCB. We use this to compute the offset of the field from the beginning of the
+// DCB and then add this offset to the starting address of the LS DCB to get the LS
+// address of the field we are updating
+// size - the size of the field we're updating.
+// Return value: none
+// Note: throws if SafeWriteBuffer fails
+void CordbProcess::UpdateLeftSideDCBField(void * rsFieldAddr, SIZE_T size)
+{
+ IfFailThrow(m_pEventChannel->UpdateLeftSideDCBField(rsFieldAddr, size));
+} // CordbProcess::UpdateRightSideDCB
+
+
+//-----------------------------------------------------------------------------
+// Gets the remote address of the event block for the Target and verifies that it's valid.
+// We use this address when we need to read from or write to the debugger control block.
+// Also allocates the RS buffer used for temporary storage for information from the DCB and
+// copies the LS DCB into the RS buffer.
+// Arguments:
+// output: pfBlockExists - true iff the LS DCB has been successfully allocated. Note that
+// we need this information even if the function throws, so we can't simply send it back
+// as a return value.
+// Return value:
+// None, but allocates GetDCB() on success. If the LS DCB has not
+// been successfully initialized or if this throws, GetDCB() will be NULL.
+//
+// Notes:
+// Throws on error
+//
+//-----------------------------------------------------------------------------
+void CordbProcess::GetEventBlock(BOOL * pfBlockExists)
+{
+ if (GetDCB() == NULL) // we only need to do this once
+ {
+ _ASSERTE(m_pShim != NULL);
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ // This will Initialize the DAC/DBI interface.
+ BOOL fDacReady = TryInitializeDac();
+
+ if (fDacReady)
+ {
+ // Ensure that we have a DAC interface.
+ _ASSERTE(m_pDacPrimitives != NULL);
+
+ // This is not technically necessary for Mac debugging. The event channel doesn't rely on
+ // knowing the target address of the DCB on the LS.
+ CORDB_ADDRESS pLeftSideDCB = NULL;
+ pLeftSideDCB = (GetDAC()->GetDebuggerControlBlockAddress());
+ if (pLeftSideDCB == NULL)
+ {
+ *pfBlockExists = false;
+ ThrowHR(CORDBG_E_DEBUGGING_NOT_POSSIBLE);
+ }
+
+ IfFailThrow(NewEventChannelForThisPlatform(pLeftSideDCB,
+ m_pMutableDataTarget,
+ GetPid(),
+ m_pShim->GetMachineInfo(),
+ &m_pEventChannel));
+ _ASSERTE(m_pEventChannel != NULL);
+
+ // copy information from left side DCB
+ UpdateRightSideDCB();
+
+ // Verify that the control block is valid.
+ // This will throw on error.
+ VerifyControlBlock();
+
+ *pfBlockExists = true;
+ }
+ else
+ {
+ // we can't initialize the DAC, so we can't get the block
+ *pfBlockExists = false;
+ }
+ }
+ else // we got the block before
+ {
+ *pfBlockExists = true;
+ }
+
+} // CordbProcess::GetEventBlock()
+
+
+//
+// Verify that the version info in the control block matches what we expect. The minimum supported protocol from the
+// Left Side must be greater or equal to the minimum required protocol of the Right Side. Note: its the Left Side's job
+// to conform to whatever protocol the Right Side requires, so long as minimum is supported.
+//
+void CordbProcess::VerifyControlBlock()
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(m_pShim != NULL);
+
+ if (GetDCB()->m_DCBSize == 0)
+ {
+ // the LS is still initializing the DCB
+ ThrowHR(CORDBG_E_DEBUGGING_NOT_POSSIBLE);
+ }
+
+ // Fill in the protocol numbers for the Right Side and update the LS DCB.
+ GetDCB()->m_rightSideProtocolCurrent = CorDB_RightSideProtocolCurrent;
+ UpdateLeftSideDCBField(&(GetDCB()->m_rightSideProtocolCurrent), sizeof(GetDCB()->m_rightSideProtocolCurrent));
+
+ GetDCB()->m_rightSideProtocolMinSupported = CorDB_RightSideProtocolMinSupported;
+ UpdateLeftSideDCBField(&(GetDCB()->m_rightSideProtocolMinSupported),
+ sizeof(GetDCB()->m_rightSideProtocolMinSupported));
+
+ // For Telesto, Dbi and Wks have a more flexible versioning allowed, as described by the Debugger
+ // Version Protocol String in DEBUGGER_PROTOCOL_STRING in DbgIpcEvents.h. This allows different build
+ // numbers, but the other protocol numbers should still match.
+#if !defined(FEATURE_CORECLR)
+ bool fSkipVerCheck = false;
+#if _DEBUG
+ // In debug builds, allow us to disable the version check to help with applying hotfixes.
+ // The hotfix may be built against a compatible IPC protocol, but have a slightly different build number.
+ fSkipVerCheck = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgSkipVerCheck) != 0;
+#endif
+
+ if (!fSkipVerCheck)
+ {
+ //
+ // These asserts double check that the version of the Right Side matches the version of the left side.
+ //
+ // If you hit these asserts, it is probably because you rebuilt mscordbi without rebuilding mscorwks, or rebuilt
+ // mscorwks without rebuilding mscordbi. You might be able to ignore these asserts, but proceed at your own risk.
+ //
+ CONSISTENCY_CHECK_MSGF(VER_PRODUCTBUILD == GetDCB()->m_verMajor,
+ ("version of %s (%d) in the debuggee does not match version of mscordbi.dll (%d) in the debugger.\n"
+ "This means your setup is wrong. You can ignore this but proceed at your own risk.\n",
+ MAIN_CLR_DLL_NAME_A, GetDCB()->m_verMajor, VER_PRODUCTBUILD));
+ CONSISTENCY_CHECK_MSGF(VER_PRODUCTBUILD_QFE == GetDCB()->m_verMinor,
+ ("QFE version of %s (%d) in the debuggee does not match QFE version of mscordbi.dll (%d) in the debugger.\n"
+ "Both dlls have build # (%d).\n"
+ "This means your setup is wrong. You can ignore this but proceed at your own risk.\n",
+ MAIN_CLR_DLL_NAME_A, GetDCB()->m_verMinor, VER_PRODUCTBUILD_QFE, VER_PRODUCTBUILD));
+ }
+#endif // !FEATURE_CORECLR
+
+ // These assertions verify that the debug manager is behaving correctly.
+ // An assertion failure here means that the runtime version of the debuggee is different from the runtime version of
+ // the debugger is capable of debugging.
+
+ // The Debug Manager should properly match LS & RS, and thus guarantee that this assert should never fire.
+ // But just in case the installation is corrupted, we'll check it.
+ if (GetDCB()->m_DCBSize != sizeof(DebuggerIPCControlBlock))
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("DCB in LS is %d bytes, in RS is %d bytes. Version mismatch!!\n",
+ GetDCB()->m_DCBSize, sizeof(DebuggerIPCControlBlock)));
+ ThrowHR(CORDBG_E_INCOMPATIBLE_PROTOCOL);
+ }
+
+ // The Left Side has to support at least our minimum required protocol.
+ if (GetDCB()->m_leftSideProtocolCurrent < GetDCB()->m_rightSideProtocolMinSupported)
+ {
+ _ASSERTE(GetDCB()->m_leftSideProtocolCurrent >= GetDCB()->m_rightSideProtocolMinSupported);
+ ThrowHR(CORDBG_E_INCOMPATIBLE_PROTOCOL);
+ }
+
+ // The Left Side has to be able to emulate at least our minimum required protocol.
+ if (GetDCB()->m_leftSideProtocolMinSupported > GetDCB()->m_rightSideProtocolCurrent)
+ {
+ _ASSERTE(GetDCB()->m_leftSideProtocolMinSupported <= GetDCB()->m_rightSideProtocolCurrent);
+ ThrowHR(CORDBG_E_INCOMPATIBLE_PROTOCOL);
+ }
+
+#ifdef _DEBUG
+ char buf[MAX_LONGPATH];
+ DWORD len = GetEnvironmentVariableA("CORDBG_NotCompatibleTest", buf, sizeof(buf));
+ _ASSERTE(len < sizeof(buf));
+
+ if (len > 0)
+ ThrowHR(CORDBG_E_INCOMPATIBLE_PROTOCOL);
+#endif
+
+ if (GetDCB()->m_bHostingInFiber)
+ {
+ ThrowHR(CORDBG_E_CANNOT_DEBUG_FIBER_PROCESS);
+ }
+
+ _ASSERTE(!GetDCB()->m_rightSideShouldCreateHelperThread);
+} // CordbProcess::VerifyControlBlock
+
+//-----------------------------------------------------------------------------
+// This is the CordbProcess objects chance to inspect the DCB and intialize stuff
+//
+// Return Value:
+// Typical HRESULT return values, nothing abnormal.
+// If succeeded, then the block exists and is valid.
+//
+//-----------------------------------------------------------------------------
+HRESULT CordbProcess::GetRuntimeOffsets()
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(m_pShim != NULL);
+ UpdateRightSideDCB();
+
+ // Can't get a handle to the helper thread if the target is remote.
+
+ // If we got this far w/o failing, then we should be able to get the helper thread handle.
+ // RS will handle not having the helper-thread handle, so we just make a best effort here.
+ DWORD dwHelperTid = GetDCB()->m_realHelperThreadId;
+ _ASSERTE(dwHelperTid != 0);
+
+
+ {
+#if !defined FEATURE_CORESYSTEM
+ // kernel32!OpenThread does not exist on all platforms (missing on Win98).
+ // So we need to delay load it.
+ typedef HANDLE (WINAPI *FPOPENTHREAD)(DWORD dwDesiredAccess,
+ BOOL bInheritHandle,
+ DWORD dwThreadId);
+
+
+
+ HMODULE mod = WszGetModuleHandle(W("kernel32.dll"));
+
+ _ASSERTE(mod != NULL); // can't fail since Kernel32.dll is already loaded.
+
+ const FPOPENTHREAD pfnOpenThread = (FPOPENTHREAD)GetProcAddress(mod, "OpenThread");
+
+ if (pfnOpenThread != NULL)
+ {
+ m_hHelperThread = pfnOpenThread(SYNCHRONIZE, FALSE, dwHelperTid);
+ CONSISTENCY_CHECK_MSGF(m_hHelperThread != NULL, ("Failed to get helper-thread handle. tid=0x%x\n", dwHelperTid));
+ }
+#elif FEATURE_PAL
+ m_hHelperThread = NULL; //RS is supposed to be able to live without a helper thread handle.
+#else
+ m_hHelperThread = OpenThread(SYNCHRONIZE, FALSE, dwHelperTid);
+ CONSISTENCY_CHECK_MSGF(m_hHelperThread != NULL, ("Failed to get helper-thread handle. tid=0x%x\n", dwHelperTid));
+#endif
+ }
+
+ // get the remote address of the runtime offsets structure and read the structure itself
+ HRESULT hrRead = SafeReadStruct(PTR_TO_CORDB_ADDRESS(GetDCB()->m_pRuntimeOffsets), &m_runtimeOffsets);
+
+ if (FAILED(hrRead))
+ {
+ return hrRead;
+ }
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::GRO: got runtime offsets: \n"));
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ LOG((LF_CORDB, LL_INFO10000, " m_genericHijackFuncAddr= 0x%p\n",
+ m_runtimeOffsets.m_genericHijackFuncAddr));
+ LOG((LF_CORDB, LL_INFO10000, " m_signalHijackStartedBPAddr= 0x%p\n",
+ m_runtimeOffsets.m_signalHijackStartedBPAddr));
+ LOG((LF_CORDB, LL_INFO10000, " m_excepNotForRuntimeBPAddr= 0x%p\n",
+ m_runtimeOffsets.m_excepNotForRuntimeBPAddr));
+ LOG((LF_CORDB, LL_INFO10000, " m_notifyRSOfSyncCompleteBPAddr= 0x%p\n",
+ m_runtimeOffsets.m_notifyRSOfSyncCompleteBPAddr));
+ LOG((LF_CORDB, LL_INFO10000, " m_raiseException= 0x%p\n",
+ m_runtimeOffsets.m_raiseExceptionAddr));
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ LOG((LF_CORDB, LL_INFO10000, " m_TLSIndex= 0x%08x\n",
+ m_runtimeOffsets.m_TLSIndex));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadStateOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadStateOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadStateNCOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadStateNCOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadPGCDisabledOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadPGCDisabledOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadPGCDisabledValue= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadPGCDisabledValue));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadDebuggerWordOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadDebuggerWordOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadFrameOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadFrameOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadMaxNeededSize= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadMaxNeededSize));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadSteppingStateMask= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadSteppingStateMask));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEMaxFrameValue= 0x%08x\n",
+ m_runtimeOffsets.m_EEMaxFrameValue));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadDebuggerFilterContextOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadDebuggerFilterContextOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadCantStopOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadCantStopOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEFrameNextOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEFrameNextOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEIsManagedExceptionStateMask= 0x%08x\n",
+ m_runtimeOffsets.m_EEIsManagedExceptionStateMask));
+ LOG((LF_CORDB, LL_INFO10000, " m_pPatches= 0x%08x\n",
+ m_runtimeOffsets.m_pPatches));
+ LOG((LF_CORDB, LL_INFO10000, " m_offRgData= 0x%08x\n",
+ m_runtimeOffsets.m_offRgData));
+ LOG((LF_CORDB, LL_INFO10000, " m_offCData= 0x%08x\n",
+ m_runtimeOffsets.m_offCData));
+ LOG((LF_CORDB, LL_INFO10000, " m_cbPatch= 0x%08x\n",
+ m_runtimeOffsets.m_cbPatch));
+ LOG((LF_CORDB, LL_INFO10000, " m_offAddr= 0x%08x\n",
+ m_runtimeOffsets.m_offAddr));
+ LOG((LF_CORDB, LL_INFO10000, " m_offOpcode= 0x%08x\n",
+ m_runtimeOffsets.m_offOpcode));
+ LOG((LF_CORDB, LL_INFO10000, " m_cbOpcode= 0x%08x\n",
+ m_runtimeOffsets.m_cbOpcode));
+ LOG((LF_CORDB, LL_INFO10000, " m_offTraceType= 0x%08x\n",
+ m_runtimeOffsets.m_offTraceType));
+ LOG((LF_CORDB, LL_INFO10000, " m_traceTypeUnmanaged= 0x%08x\n",
+ m_runtimeOffsets.m_traceTypeUnmanaged));
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // Flares are only used for interop debugging.
+
+ // Do check that the flares are all at unique offsets.
+ // Since this is determined at link-time, we need a run-time check (an
+ // assert isn't good enough, since this would only happen in a super
+ // optimized / bbt run).
+ {
+ const void * flares[] = {
+ m_runtimeOffsets.m_signalHijackStartedBPAddr,
+ m_runtimeOffsets.m_excepForRuntimeHandoffStartBPAddr,
+ m_runtimeOffsets.m_excepForRuntimeHandoffCompleteBPAddr,
+ m_runtimeOffsets.m_signalHijackCompleteBPAddr,
+ m_runtimeOffsets.m_excepNotForRuntimeBPAddr,
+ m_runtimeOffsets.m_notifyRSOfSyncCompleteBPAddr,
+ };
+
+ const int NumFlares = NumItems(flares);
+
+ // Ensure that all of the flares are unique.
+ for(int i = 0; i < NumFlares; i++)
+ {
+ for(int j = i+1; j < NumFlares; j++)
+ {
+ if (flares[i] == flares[j])
+ {
+ // If we ever fail here, that means the LS build is busted.
+
+ // This assert is useful if we drop a checked RS onto a retail
+ // LS (that's legal).
+ _ASSERTE(!"LS has matching Flares.");
+ LOG((LF_CORDB, LL_ALWAYS, "Failing because of matching flares.\n"));
+ return CORDBG_E_INCOMPATIBLE_PROTOCOL;
+ }
+ }
+ }
+ }
+
+#endif // FEATURE_INTEROP_DEBUGGING
+ m_runtimeOffsetsInitialized = true;
+ return S_OK;
+}
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+
+//-----------------------------------------------------------------------------
+// Resume hijacked threads.
+//-----------------------------------------------------------------------------
+void CordbProcess::ResumeHijackedThreads()
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(m_pShim != NULL);
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::RHT: entered\n"));
+ if (this->m_state & (CordbProcess::PS_SOME_THREADS_SUSPENDED | CordbProcess::PS_HIJACKS_IN_PLACE))
+ {
+ // On XP, This will also resume the threads suspended for Sync.
+ this->ResumeUnmanagedThreads();
+ }
+
+ // Hijacks send their ownership flares and then wait on this event. By setting this
+ // we let the hijacks run free.
+ if (this->m_leftSideUnmanagedWaitEvent != NULL)
+ {
+ SetEvent(this->m_leftSideUnmanagedWaitEvent);
+ }
+ else
+ {
+ // Only reason we expect to not have this event is if the CLR hasn't been loaded yet.
+ // In that case, we won't hijack, so nobody's listening for this event either.
+ _ASSERTE(!m_initialized);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// For debugging support, record the win32 events.
+// Note that although this is for debugging, we want it in retail because we'll
+// be debugging retail most of the time :(
+// pEvent - the win32 debug event we just received
+// pUThread - our unmanaged thread object for the event. We could look it up
+// from pEvent->dwThreadId, but passed in for perf reasons.
+//-----------------------------------------------------------------------------
+void CordbProcess::DebugRecordWin32Event(const DEBUG_EVENT * pEvent, CordbUnmanagedThread * pUThread)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ // Although we could look up the Unmanaged thread, it's faster to have it just passed in.
+ // So here we do a consistency check.
+ _ASSERTE(pUThread != NULL);
+ _ASSERTE(pUThread->m_id == pEvent->dwThreadId);
+
+ m_DbgSupport.m_TotalNativeEvents++; // bump up the counter.
+
+ MiniDebugEvent * pMiniEvent = &m_DbgSupport.m_DebugEventQueue[m_DbgSupport.m_DebugEventQueueIdx];
+ pMiniEvent->code = (BYTE) pEvent->dwDebugEventCode;
+ pMiniEvent->pUThread = pUThread;
+
+ DWORD tid = pEvent->dwThreadId;
+
+ // Record debug-event specific data.
+ switch(pEvent->dwDebugEventCode)
+ {
+ case LOAD_DLL_DEBUG_EVENT:
+ pMiniEvent->u.ModuleData.pBaseAddress = pEvent->u.LoadDll.lpBaseOfDll;
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "Win32 Debug Event received: tid=0x%8x, Load Dll. Addr=%p\n",
+ tid,
+ pEvent->u.LoadDll.lpBaseOfDll);
+ break;
+ case UNLOAD_DLL_DEBUG_EVENT:
+ pMiniEvent->u.ModuleData.pBaseAddress = pEvent->u.UnloadDll.lpBaseOfDll;
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "Win32 Debug Event received: tid=0x%8x, Unload Dll. Addr=%p\n",
+ tid,
+ pEvent->u.UnloadDll.lpBaseOfDll);
+ break;
+ case EXCEPTION_DEBUG_EVENT:
+ pMiniEvent->u.ExceptionData.pAddress = pEvent->u.Exception.ExceptionRecord.ExceptionAddress;
+ pMiniEvent->u.ExceptionData.dwCode = pEvent->u.Exception.ExceptionRecord.ExceptionCode;
+
+ STRESS_LOG3(LF_CORDB, LL_INFO1000, "Win32 Debug Event received: tid=%8x, (1) Exception. Code=0x%08x, Addr=%p\n",
+ tid,
+ pMiniEvent->u.ExceptionData.dwCode,
+ pMiniEvent->u.ExceptionData.pAddress
+ );
+ break;
+ default:
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "Win32 Debug Event received tid=%8x, %d\n", tid, pEvent->dwDebugEventCode);
+ break;
+ }
+
+
+ // Go to the next entry in the queue.
+ m_DbgSupport.m_DebugEventQueueIdx = (m_DbgSupport.m_DebugEventQueueIdx + 1) % DEBUG_EVENTQUEUE_SIZE;
+}
+
+void CordbProcess::QueueUnmanagedEvent(CordbUnmanagedThread *pUThread, const DEBUG_EVENT *pEvent)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+ _ASSERTE(m_pShim != NULL);
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::QUE: queued unmanaged event %d for thread 0x%x\n",
+ pEvent->dwDebugEventCode, pUThread->m_id));
+
+
+ _ASSERTE(pEvent->dwDebugEventCode == EXCEPTION_DEBUG_EVENT);
+
+ // Copy the event into the given thread
+ CordbUnmanagedEvent *ue;
+
+ // Use the primary IB event slot unless this is the special stack overflow event case.
+ if (!pUThread->HasSpecialStackOverflowCase())
+ ue = pUThread->IBEvent();
+ else
+ ue = pUThread->IBEvent2();
+
+ if(pUThread->HasIBEvent() && !pUThread->HasSpecialStackOverflowCase())
+ {
+ // Any event being replaced should at least have been continued outside of the hijack
+ // We don't track whether or not we expect the exception to retrigger but if we are replacing
+ // the event then it did not.
+ _ASSERTE(ue->IsEventContinuedUnhijacked());
+ LOG((LF_CORDB, LL_INFO10000, "CP::QUE: A previously seen event is being discarded 0x%x 0x%p\n",
+ ue->m_currentDebugEvent.u.Exception.ExceptionRecord.ExceptionCode,
+ ue->m_currentDebugEvent.u.Exception.ExceptionRecord.ExceptionAddress));
+ DequeueUnmanagedEvent(ue->m_owner);
+ }
+
+ memcpy(&(ue->m_currentDebugEvent), pEvent, sizeof(DEBUG_EVENT));
+ ue->m_state = CUES_IsIBEvent;
+ ue->m_next = NULL;
+
+ // Enqueue the event.
+ pUThread->SetState(CUTS_HasIBEvent);
+
+ if (m_unmanagedEventQueue == NULL)
+ m_unmanagedEventQueue = ue;
+ else
+ m_lastQueuedUnmanagedEvent->m_next = ue;
+
+ m_lastQueuedUnmanagedEvent = ue;
+}
+
+void CordbProcess::DequeueUnmanagedEvent(CordbUnmanagedThread *ut)
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(m_unmanagedEventQueue != NULL);
+ _ASSERTE(ut->HasIBEvent() || ut->HasSpecialStackOverflowCase());
+ _ASSERTE(ThreadHoldsProcessLock());
+
+
+ CordbUnmanagedEvent *ue;
+
+ if (ut->HasIBEvent())
+ ue = ut->IBEvent();
+ else
+ {
+ ue = ut->IBEvent2();
+
+ // Since we're dequeuing the special stack overflow event, we're no longer in the special stack overflow case.
+ ut->ClearState(CUTS_HasSpecialStackOverflowCase);
+ }
+
+ DWORD ec = ue->m_currentDebugEvent.dwDebugEventCode;
+ LOG((LF_CORDB, LL_INFO10000, "CP::DUE: dequeue unmanaged event %d for thread 0x%x\n", ec, ut->m_id));
+
+ _ASSERTE(ec == EXCEPTION_DEBUG_EVENT);
+
+ CordbUnmanagedEvent **tmp = &m_unmanagedEventQueue;
+ CordbUnmanagedEvent **prev = NULL;
+
+ // Note: this supports out-of-order dequeing of unmanaged events. This is necessary because we queue events even if
+ // we're not clear on the ownership question. When we get the answer, and if the event belongs to the Runtime, we go
+ // ahead and yank the event out of the queue, wherever it may be.
+ while (*tmp && *tmp != ue)
+ {
+ prev = tmp;
+ tmp = &((*tmp)->m_next);
+ }
+
+ _ASSERTE(*tmp == ue);
+
+ *tmp = (*tmp)->m_next;
+
+ if (m_unmanagedEventQueue == NULL)
+ m_lastQueuedUnmanagedEvent = NULL;
+ else if (m_lastQueuedUnmanagedEvent == ue)
+ {
+ _ASSERTE(prev != NULL);
+ m_lastQueuedUnmanagedEvent = *prev;
+ }
+
+ ut->ClearState(CUTS_HasIBEvent);
+
+}
+
+void CordbProcess::QueueOOBUnmanagedEvent(CordbUnmanagedThread *pUThread, const DEBUG_EVENT * pEvent)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+ _ASSERTE(!pUThread->HasOOBEvent());
+ _ASSERTE(IsWin32EventThread());
+ _ASSERTE(m_pShim != NULL);
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::QUE: queued OOB unmanaged event %d for thread 0x%x\n",
+ pEvent->dwDebugEventCode, pUThread->m_id));
+
+ // Copy the event into the given thread
+ CordbUnmanagedEvent *ue = pUThread->OOBEvent();
+ memcpy(&(ue->m_currentDebugEvent), pEvent, sizeof(DEBUG_EVENT));
+ ue->m_state = CUES_None;
+ ue->m_next = NULL;
+
+ // Enqueue the event.
+ pUThread->SetState(CUTS_HasOOBEvent);
+
+ if (m_outOfBandEventQueue == NULL)
+ m_outOfBandEventQueue = ue;
+ else
+ m_lastQueuedOOBEvent->m_next = ue;
+
+ m_lastQueuedOOBEvent = ue;
+}
+
+void CordbProcess::DequeueOOBUnmanagedEvent(CordbUnmanagedThread *ut)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(m_outOfBandEventQueue != NULL);
+ _ASSERTE(ut->HasOOBEvent());
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ CordbUnmanagedEvent *ue = ut->OOBEvent();
+ DWORD ec = ue->m_currentDebugEvent.dwDebugEventCode;
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::DUE: dequeue OOB unmanaged event %d for thread 0x%x\n", ec, ut->m_id));
+
+ CordbUnmanagedEvent **tmp = &m_outOfBandEventQueue;
+ CordbUnmanagedEvent **prev = NULL;
+
+ // Note: this supports out-of-order dequeing of unmanaged events. This is necessary because we queue events even if
+ // we're not clear on the ownership question. When we get the answer, and if the event belongs to the Runtime, we go
+ // ahead and yank the event out of the queue, wherever it may be.
+ while (*tmp && *tmp != ue)
+ {
+ prev = tmp;
+ tmp = &((*tmp)->m_next);
+ }
+
+ _ASSERTE(*tmp == ue);
+
+ *tmp = (*tmp)->m_next;
+
+ if (m_outOfBandEventQueue == NULL)
+ m_lastQueuedOOBEvent = NULL;
+ else if (m_lastQueuedOOBEvent == ue)
+ {
+ _ASSERTE(prev != NULL);
+ m_lastQueuedOOBEvent = *prev;
+ }
+
+ ut->ClearState(CUTS_HasOOBEvent);
+}
+
+HRESULT CordbProcess::SuspendUnmanagedThreads()
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ // Iterate over all unmanaged threads...
+ CordbUnmanagedThread* ut;
+ HASHFIND find;
+
+ for (ut = m_unmanagedThreads.FindFirst(&find); ut != NULL; ut = m_unmanagedThreads.FindNext(&find))
+ {
+
+ // Don't suspend any thread in a can't stop region. This includes cooperative mode threads & preemptive
+ // threads that haven't pushed a NativeTransitionFrame. The ultimate problem here is that a thread
+ // in this state is effectively inside the runtime, and thus may take a lock that blocks the helper thread.
+ // IsCan'tStop also includes the helper thread & hijacked threads - which we shouldn't suspend anyways.
+
+ // Only suspend those unmanaged threads that aren't already suspended by us and that aren't already hijacked by
+ // us.
+
+ if (!ut->IsSuspended() &&
+ !ut->IsDeleted() &&
+ !ut->IsCantStop() &&
+ !ut->IsBlockingForSync()
+ )
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::SUT: suspending unmanaged thread 0x%x, handle 0x%x\n", ut->m_id, ut->m_handle));
+
+ DWORD succ = SuspendThread(ut->m_handle);
+
+ if (succ == 0xFFFFFFFF)
+ {
+ // This is okay... the thread may be dying after an ExitThread event.
+ LOG((LF_CORDB, LL_INFO1000, "CP::SUT: failed to suspend thread 0x%x\n", ut->m_id));
+ }
+ else
+ {
+ m_state |= PS_SOME_THREADS_SUSPENDED;
+
+ ut->SetState(CUTS_Suspended);
+ }
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT CordbProcess::ResumeUnmanagedThreads()
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+ FAIL_IF_NEUTERED(this);
+
+ // Iterate over all unmanaged threads...
+ CordbUnmanagedThread* ut;
+ HASHFIND find;
+
+ for (ut = m_unmanagedThreads.FindFirst(&find); ut != NULL; ut = m_unmanagedThreads.FindNext(&find))
+ {
+ // Only resume those unmanaged threads that were suspended by us.
+ if (ut->IsSuspended())
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::RUT: resuming unmanaged thread 0x%x\n", ut->m_id));
+
+ DWORD succ = ResumeThread(ut->m_handle);
+
+ if (succ == 0xFFFFFFFF)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::RUT: failed to resume thread 0x%x\n", ut->m_id));
+ }
+ else
+ ut->ClearState(CUTS_Suspended);
+ }
+ }
+
+ m_state &= ~PS_SOME_THREADS_SUSPENDED;
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// DispatchUnmanagedInBandEvent
+//
+// Handler for Win32 events already known to be Unmanaged and in-band.
+//-----------------------------------------------------------------------------
+void CordbProcess::DispatchUnmanagedInBandEvent()
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ // There should be no queued OOB events!!! If there are, then we have a breakdown in our protocol, since all OOB
+ // events should be dispatched before attempting to really continue from any in-band event.
+ _ASSERTE(m_outOfBandEventQueue == NULL);
+ _ASSERTE(m_cordb != NULL);
+ _ASSERTE(m_cordb->m_unmanagedCallback != NULL);
+ _ASSERTE(!m_dispatchingUnmanagedEvent);
+
+ CordbUnmanagedThread * pUnmanagedThread = NULL;
+ CordbUnmanagedEvent * pUnmanagedEvent = m_unmanagedEventQueue;
+
+ while (true)
+ {
+ // get the next queued event that isn't dispatched yet
+ while(pUnmanagedEvent != NULL && pUnmanagedEvent->IsDispatched())
+ {
+ pUnmanagedEvent = pUnmanagedEvent->m_next;
+ }
+
+ if(pUnmanagedEvent == NULL)
+ break;
+
+ // Get the thread for this event
+ pUnmanagedThread = pUnmanagedEvent->m_owner;
+
+ // We better not have dispatched it yet!
+ _ASSERTE(!pUnmanagedEvent->IsDispatched());
+
+ // We shouldn't be dispatching IB events on a thread that has exited.
+ // Though it's possible that the thread may exit *after* the IB event has been dispatched
+ // if it gets hijacked.
+ _ASSERTE(!pUnmanagedThread->IsDeleted());
+
+ // Make sure we keep the thread alive while we're playing with it.
+ pUnmanagedThread->InternalAddRef();
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::DUE: dispatching unmanaged event %d for thread 0x%x\n",
+ pUnmanagedEvent->m_currentDebugEvent.dwDebugEventCode, pUnmanagedThread->m_id));
+
+ m_dispatchingUnmanagedEvent = true;
+
+ // Add/Remove a reference which is scoped to the time that m_lastDispatchedIBEvent
+ // is set to pUnmanagedEvent (it is an interior pointer)
+ // see DevDiv issue 818301 for more details
+ if(m_lastDispatchedIBEvent != NULL)
+ {
+ m_lastDispatchedIBEvent->m_owner->InternalRelease();
+ m_lastDispatchedIBEvent = NULL;
+ }
+ pUnmanagedThread->InternalAddRef();
+ m_lastDispatchedIBEvent = pUnmanagedEvent;
+ pUnmanagedEvent->SetState(CUES_Dispatched);
+
+ IncStopCount();
+
+ Unlock();
+
+ {
+ // Interface is semantically const, but does not include const in signature.
+ DEBUG_EVENT * pEvent = const_cast<DEBUG_EVENT *> (&(pUnmanagedEvent->m_currentDebugEvent));
+ PUBLIC_WIN32_CALLBACK_IN_THIS_SCOPE(this,pEvent, FALSE);
+ m_cordb->m_unmanagedCallback->DebugEvent(pEvent, FALSE);
+ }
+
+ Lock();
+
+ // Calling IMDA::Continue() will set m_dispatchingUnmanagedEvent = false.
+ // So if Continue() was called && we have more events, we'll loop and dispatch more events.
+ // Else we'll break out of the while loop.
+ if(m_dispatchingUnmanagedEvent)
+ break;
+
+ // Continue was called in the dispatch callback, but that continue path just
+ // clears the dispatch flag and returns. The continue right here is the logical
+ // completion of the user's continue request
+ // Note it is sometimes the case that these events have already been continued because
+ // they had defered dispatching. At the time of deferal they were immediately continued.
+ // If the event is already continued then this continue becomes a no-op.
+ m_pShim->GetWin32EventThread()->DoDbgContinue(this, pUnmanagedEvent);
+
+ // Release our reference to the unmanaged thread that we dispatched
+ if (pUnmanagedThread)
+ {
+ // This event should have been continued long ago...
+ _ASSERTE(!pUnmanagedThread->IBEvent()->IsEventWaitingForContinue());
+ pUnmanagedThread->InternalRelease();
+ pUnmanagedThread = NULL;
+ }
+ }
+
+ m_dispatchingUnmanagedEvent = false;
+
+ // Release our reference to the last thread that we dispatched now...
+ if(pUnmanagedThread)
+ {
+ pUnmanagedThread->InternalRelease();
+ pUnmanagedThread = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// DispatchUnmanagedOOBEvent
+//
+// Handler for Win32 events already known to be Unmanaged and out-of-band.
+//-----------------------------------------------------------------------------
+void CordbProcess::DispatchUnmanagedOOBEvent()
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+ _ASSERTE(IsWin32EventThread());
+
+ // There should be OOB events queued...
+ _ASSERTE(m_outOfBandEventQueue != NULL);
+ _ASSERTE(m_cordb->m_unmanagedCallback != NULL);
+
+ do
+ {
+ // Get the first event in the OOB Queue...
+ CordbUnmanagedEvent * pUnmanagedEvent = m_outOfBandEventQueue;
+ CordbUnmanagedThread * pUnmanagedThread = pUnmanagedEvent->m_owner;
+
+ // Make sure we keep the thread alive while we're playing with it.
+ RSSmartPtr<CordbUnmanagedThread> pRef(pUnmanagedThread);
+
+ LOG((LF_CORDB, LL_INFO10000, "[%x] CP::DUE: dispatching OOB unmanaged event %d for thread 0x%x\n",
+ GetCurrentThreadId(), pUnmanagedEvent->m_currentDebugEvent.dwDebugEventCode, pUnmanagedThread->m_id));
+
+ m_dispatchingOOBEvent = true;
+ pUnmanagedEvent->SetState(CUES_Dispatched);
+ Unlock();
+
+ {
+ // Interface is semantically const, but does not include const in signature.
+ DEBUG_EVENT * pEvent = const_cast<DEBUG_EVENT *> (&(pUnmanagedEvent->m_currentDebugEvent));
+ PUBLIC_WIN32_CALLBACK_IN_THIS_SCOPE(this, pEvent, TRUE);
+ m_cordb->m_unmanagedCallback->DebugEvent(pEvent, TRUE);
+ }
+
+ Lock();
+
+ // If they called Continue from the callback, then continue the OOB event right now before dispatching the next
+ // one.
+ if (!m_dispatchingOOBEvent)
+ {
+ DequeueOOBUnmanagedEvent(pUnmanagedThread);
+
+ // Should not have continued from this debug event yet.
+ _ASSERTE(pUnmanagedEvent->IsEventWaitingForContinue());
+
+ // Do a little extra work if that was an OOB exception event...
+ HRESULT hr = pUnmanagedEvent->m_owner->FixupAfterOOBException(pUnmanagedEvent);
+ _ASSERTE(SUCCEEDED(hr));
+
+ // Go ahead and continue now...
+ this->m_pShim->GetWin32EventThread()->DoDbgContinue(this, pUnmanagedEvent);
+ }
+
+ // Implicit release of pUnmanagedThread via pRef
+ }
+ while (!m_dispatchingOOBEvent && (m_outOfBandEventQueue != NULL));
+
+ m_dispatchingOOBEvent = false;
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::DUE: done dispatching OOB events. Queue=0x%08x\n", m_outOfBandEventQueue));
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+//-----------------------------------------------------------------------------
+// StartSyncFromWin32Stop
+//
+// Get the process from a Fozen state or a Live state to a Synchronized State.
+// Note that Process Exit is considered to be synchronized.
+// This is a nop if we're not Interop Debugging.
+// If this function succeeds, we're in a synchronized state.
+//
+// Arguments:
+// pfAsyncBreakSent - returns if this method sent an async-break or not.
+//
+// Return value:
+// typical HRESULT return values, nothing sinister here.
+//-----------------------------------------------------------------------------
+HRESULT CordbProcess::StartSyncFromWin32Stop(BOOL * pfAsyncBreakSent)
+{
+ INTERNAL_API_ENTRY(this);
+ if (m_pShim == NULL) // This API is moved off to the shim
+ {
+ return E_NOTIMPL;
+ }
+
+ HRESULT hr = S_OK;
+
+ // Caller should have taken the stop-go lock. This prevents us from racing w/ a continue.
+ _ASSERTE(m_StopGoLock.HasLock());
+
+ // Process should be init before we try to sync it.
+ _ASSERTE(this->m_initialized);
+
+ // If nobody's listening for an AsyncBreak, and we're not stopped, then our caller
+ // doesn't know if we're sending an AsyncBreak or not; and thus we may not continue.
+ // Failing this assert means that we're stopping but we don't think we're going to get a continue
+ // down the road, and thus we're headed for a deadlock.
+ _ASSERTE((pfAsyncBreakSent != NULL) || (m_stopCount > 0));
+
+ if (pfAsyncBreakSent)
+ {
+ *pfAsyncBreakSent = FALSE;
+ }
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+
+ // If we're win32 stopped (but not out-of-band win32 stopped), or if we're running free on the Left Side but we're
+ // just not synchronized (and we're win32 attached), then go ahead and do an internal continue and send an async
+ // break event to get the Left Side sync'd up.
+ //
+ // The process can be running free as far as Win32 events are concerned, but still not synchronized as far as the
+ // Runtime is concerned. This can happen in a lot of cases where we end up with the Runtime not sync'd but with the
+ // process running free due to hijacking, etc...
+ if (((m_state & CordbProcess::PS_WIN32_STOPPED) && (m_outOfBandEventQueue == NULL)) ||
+ (!GetSynchronized() && IsInteropDebugging()))
+ {
+ Lock();
+
+ if (((m_state & CordbProcess::PS_WIN32_STOPPED) && (m_outOfBandEventQueue == NULL)) ||
+ (!GetSynchronized() && IsInteropDebugging()))
+ {
+ // This can't be the win32 ET b/c we need that thread to be alive and pumping win32 DE so that
+ // our Async Break can get across.
+ // So nobody should ever be calling this on the w32 ET. But they could, since we do trickle in from
+ // outside APIs. So we need a retail check.
+ if (IsWin32EventThread())
+ {
+ _ASSERTE(!"Don't call this API on the W32 Event Thread");
+
+ Unlock();
+ return ErrWrapper(CORDBG_E_CANT_CALL_ON_THIS_THREAD);
+ }
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "[%x] CP::SSFW32S: sending internal continue\n", GetCurrentThreadId());
+
+ // Can't do this on the win32 event thread.
+ _ASSERTE(!this->IsWin32EventThread());
+
+ // If the helper thread is already dead, then we just return as if we sync'd the process.
+ if (m_helperThreadDead)
+ {
+ if (pfAsyncBreakSent)
+ {
+ *pfAsyncBreakSent = TRUE;
+ }
+
+ // Mark the process as synchronized so no events will be dispatched until the thing is
+ // continued. However, the marking here is not a usual marking for synchronized. It has special
+ // semantics when we're interop debugging. We use m_oddSync to remember this so that we can take special
+ // action in Continue().
+ SetSynchronized(true);
+ m_oddSync = true;
+
+ // Get the RC Event Thread to stop listening to the process.
+ m_cordb->ProcessStateChanged();
+
+ Unlock();
+
+ return S_OK;
+ }
+
+ m_stopRequested = true;
+
+ // See ::Stop for why we defer this. The delayed events will be dispatched when some one calls continue.
+ // And we know they'll call continue b/c (stopCount > 0) || (our caller knows we're sending an AsyncBreak).
+ m_specialDeferment = true;
+
+ Unlock();
+
+ // If the process gets synchronized between the Unlock() and here, then SendUnmanagedContinue() will end up
+ // not doing anything at all since a) it holds the process lock when working and b) it gates everything on
+ // if the process is sync'd or not. This is exactly what we want.
+ hr = this->m_pShim->GetWin32EventThread()->SendUnmanagedContinue(this, cInternalUMContinue);
+
+ LOG((LF_CORDB, LL_INFO1000, "[%x] CP::SSFW32S: internal continue returned\n", GetCurrentThreadId()));
+
+ // Send an async break to the left side now that its running.
+ DebuggerIPCEvent * pEvent = (DebuggerIPCEvent *) _alloca(CorDBIPC_BUFFER_SIZE);
+ InitIPCEvent(pEvent, DB_IPCE_ASYNC_BREAK, false, VMPTR_AppDomain::NullPtr());
+
+ LOG((LF_CORDB, LL_INFO1000, "[%x] CP::SSFW32S: sending async stop\n", GetCurrentThreadId()));
+
+ // If the process gets synchronized between the Unlock() and here, then this message will do nothing (Left
+ // Side catches it) and we'll never get a response, and it won't hurt anything.
+ hr = m_cordb->SendIPCEvent(this, pEvent, CorDBIPC_BUFFER_SIZE);
+ // @Todo- how do we handle a failure here?
+
+ // If the send returns with the helper thread being dead, then we know we don't need to wait for the process
+ // to sync.
+ if (!m_helperThreadDead)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "[%x] CP::SSFW32S: sent async stop, waiting for event\n", GetCurrentThreadId());
+
+ // If we got synchronized between the Unlock() and here its okay since m_stopWaitEvent is still high
+ // from the last sync.
+ DWORD dwWaitResult = SafeWaitForSingleObject(this, m_stopWaitEvent, INFINITE);
+
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "[%x] CP::SSFW32S: got event, %d\n", GetCurrentThreadId(), dwWaitResult);
+
+ _ASSERTE(dwWaitResult == WAIT_OBJECT_0);
+ }
+
+ Lock();
+
+ m_specialDeferment = false;
+
+ if (pfAsyncBreakSent)
+ {
+ *pfAsyncBreakSent = TRUE;
+ }
+
+ // If the helper thread died while we were trying to send an event to it, then we just do the same odd sync
+ // logic we do above.
+ if (m_helperThreadDead)
+ {
+ SetSynchronized(true);
+ m_oddSync = true;
+ hr = S_OK;
+ }
+
+ m_stopRequested = false;
+ m_cordb->ProcessStateChanged();
+ }
+
+ Unlock();
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ return hr;
+}
+
+// Check if the left side has exited. If so, get the right-side
+// into shutdown mode. Only use this to avert us from going into
+// an unrecoverable error.
+bool CordbProcess::CheckIfLSExited()
+{
+// Check by waiting on the handle with no timeout.
+ if (WaitForSingleObject(m_handle, 0) == WAIT_OBJECT_0)
+ {
+ Lock();
+ m_terminated = true;
+ m_exiting = true;
+ Unlock();
+ }
+
+ LOG((LF_CORDB, LL_INFO10, "CP::IsLSExited() returning '%s'\n",
+ m_exiting ? "true" : "false"));
+
+ return m_exiting;
+}
+
+// Call this if something really bad happened and we can't do
+// anything meaningful with the CordbProcess.
+void CordbProcess::UnrecoverableError(HRESULT errorHR,
+ unsigned int errorCode,
+ const char *errorFile,
+ unsigned int errorLine)
+{
+ LOG((LF_CORDB, LL_INFO10, "[%x] CP::UE: unrecoverable error 0x%08x "
+ "(%d) %s:%d\n",
+ GetCurrentThreadId(),
+ errorHR, errorCode, errorFile, errorLine));
+
+ // We definitely want to know about any of these.
+ STRESS_LOG3(LF_CORDB, LL_EVERYTHING, "Unrecoverable Error:0x%08x, File=%s, line=%d\n", errorHR, errorFile, errorLine);
+
+ // It's possible for an unrecoverable error to occur if the user detaches the
+ // debugger while inside CordbProcess::DispatchRCEvent() (as that function deliberately
+ // calls Unlock() while calling into the Shim). Detect such cases here & bail before we
+ // try to access invalid fields on this CordbProcess.
+ //
+ // Normally, we'd need to take the cordb process lock around the IsNeutered check
+ // (and the code that follows). And perhaps this is a good thing to do in the
+ // future. But for now we're not for two reasons:
+ //
+ // 1) It's scary. We're in UnrecoverableError() for gosh sake. I don't know all
+ // the possible bad states we can be in to get here. Will taking the process lock
+ // have ordering issues? Will the process lock even be valid to take here (or might
+ // we AV)? Since this is error handling, we should probably be as light as we can
+ // not to cause more errors.
+ //
+ // 2) It's unnecessary. For the Watson dump I investigated that caused this fix in
+ // the first place, we already detached before entering UnrecoverableError()
+ // (indeed, the only reason we're in UnrecoverableError is that we already detached
+ // and that caused a prior API to fail). Thus, there's no timing issue (in that
+ // case, anyway), wrt to entering UnrecoverableError() and detaching / neutering.
+ if (IsNeutered())
+ return;
+
+#ifdef _DEBUG
+ // Ping our error trapping logic
+ HRESULT hrDummy;
+ hrDummy = ErrWrapper(errorHR);
+#endif
+
+ if (m_pShim == NULL)
+ {
+ // @dbgtodo - , shim: Once everything is hoisted, we can remove
+ // this code.
+ // In the v3 case, we should never get an unrecoverable error. Instead, the HR should be propogated
+ // and returned at the top-level public API.
+ _ASSERTE(!"Unrecoverable error dispatched in V3 case.");
+ }
+
+ CONSISTENCY_CHECK_MSGF(IsLegalFatalError(errorHR), ("Unrecoverable internal error: hr=0x%08x!", errorHR));
+
+ if (!IsLegalFatalError(errorHR) || (errorHR != CORDBG_E_CANNOT_DEBUG_FIBER_PROCESS))
+ {
+ // This will throw everything into a Zombie state. The ATT_ macros will check this and fail immediately.
+ m_unrecoverableError = true;
+
+ //
+ // Mark the process as no longer synchronized.
+ //
+ Lock();
+ SetSynchronized(false);
+ IncStopCount();
+ Unlock();
+ }
+
+ // Set the error flags in the process so that if parts of it are
+ // still alive, it will realize that its in this mode and do the
+ // right thing.
+ if (GetDCB() != NULL)
+ {
+ GetDCB()->m_errorHR = errorHR;
+ GetDCB()->m_errorCode = errorCode;
+ EX_TRY
+ {
+ UpdateLeftSideDCBField(&(GetDCB()->m_errorHR), sizeof(GetDCB()->m_errorHR));
+ UpdateLeftSideDCBField(&(GetDCB()->m_errorCode), sizeof(GetDCB()->m_errorCode));
+ }
+ EX_CATCH
+ {
+ _ASSERTE(!"Writing process memory failed, perhaps due to an unexpected disconnection from the target.");
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+ }
+
+ //
+ // Let the user know that we've hit an unrecoverable error.
+ //
+ if (m_cordb->m_managedCallback)
+ {
+ // We are about to send DebuggerError call back. The state of RS is undefined.
+ // So we use the special Public Callback. We may be holding locks and stuff.
+ // We may also be deeply nested within the RS.
+ PUBLIC_CALLBACK_IN_THIS_SCOPE_DEBUGGERERROR(this);
+ m_cordb->m_managedCallback->DebuggerError((ICorDebugProcess*) this,
+ errorHR,
+ errorCode);
+ }
+}
+
+
+HRESULT CordbProcess::CheckForUnrecoverableError()
+{
+ HRESULT hr = S_OK;
+
+ if (GetDCB() != NULL)
+ {
+ // be sure we have the latest information
+ UpdateRightSideDCB();
+
+ if (GetDCB()->m_errorHR != S_OK)
+ {
+ UnrecoverableError(GetDCB()->m_errorHR,
+ GetDCB()->m_errorCode,
+ __FILE__, __LINE__);
+
+ hr = GetDCB()->m_errorHR;
+ }
+ }
+
+ return hr;
+}
+
+
+/*
+ * EnableLogMessages enables/disables sending of log messages to the
+ * debugger for logging.
+ */
+HRESULT CordbProcess::EnableLogMessages(BOOL fOnOff)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+ HRESULT hr = S_OK;
+
+ DebuggerIPCEvent *event = (DebuggerIPCEvent*) _alloca(CorDBIPC_BUFFER_SIZE);
+ InitIPCEvent(event, DB_IPCE_ENABLE_LOG_MESSAGES, false, VMPTR_AppDomain::NullPtr());
+ event->LogSwitchSettingMessage.iLevel = (int)fOnOff;
+
+ hr = m_cordb->SendIPCEvent(this, event, CorDBIPC_BUFFER_SIZE);
+ hr = WORST_HR(hr, event->hr);
+
+ LOG((LF_CORDB, LL_INFO10000, "[%x] CP::EnableLogMessages: EnableLogMessages=%d sent.\n",
+ GetCurrentThreadId(), fOnOff));
+
+ return hr;
+}
+
+/*
+ * ModifyLogSwitch modifies the specified switch's severity level.
+ */
+COM_METHOD CordbProcess::ModifyLogSwitch(__in_z WCHAR *pLogSwitchName, LONG lLevel)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ HRESULT hr = S_OK;
+
+ _ASSERTE (pLogSwitchName != NULL);
+
+ DebuggerIPCEvent *event = (DebuggerIPCEvent*) _alloca(CorDBIPC_BUFFER_SIZE);
+ InitIPCEvent(event, DB_IPCE_MODIFY_LOGSWITCH, false, VMPTR_AppDomain::NullPtr());
+ event->LogSwitchSettingMessage.iLevel = lLevel;
+ event->LogSwitchSettingMessage.szSwitchName.SetStringTruncate(pLogSwitchName);
+
+ hr = m_cordb->SendIPCEvent(this, event, CorDBIPC_BUFFER_SIZE);
+ hr = WORST_HR(hr, event->hr);
+
+ LOG((LF_CORDB, LL_INFO10000, "[%x] CP::ModifyLogSwitch: ModifyLogSwitch sent.\n",
+ GetCurrentThreadId()));
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Writes a buffer from the target and performs checks similar to SafeWriteStruct
+//
+// Arguments:
+// tb - TargetBuffer which represents the target memory we want to write to
+// pLocalBuffer - local pointer into source buffer
+// cbSize - the size of local buffer
+//
+// Exceptions
+// On error throws the result of WriteVirtual unless a short write is performed,
+// in which case throws ERROR_PARTIAL_COPY
+//
+void CordbProcess::SafeWriteBuffer(TargetBuffer tb,
+ const BYTE * pLocalBuffer)
+{
+ _ASSERTE(m_pMutableDataTarget != NULL);
+ HRESULT hr = m_pMutableDataTarget->WriteVirtual(tb.pAddress,
+ pLocalBuffer,
+ tb.cbSize);
+ IfFailThrow(hr);
+}
+
+//-----------------------------------------------------------------------------
+// Reads a buffer from the target and performs checks similar to SafeWriteStruct
+//
+// Arguments:
+// tb - TargetBuffer which represents the target memory to read from
+// pLocalBuffer - local pointer into source buffer
+// cbSize - the size of the remote buffer
+// throwOnError - determines whether the function throws exceptions or returns HRESULTs
+// in failure cases
+//
+// Exceptions:
+// If throwOnError is TRUE
+// On error always throws the special CORDBG_E_READVIRTUAL_FAILURE, unless a short write is performed
+// in which case throws ERROR_PARTIAL_COPY
+// If throwOnError is FALSE
+// No exceptions are thrown, and instead the same error codes are returned as HRESULTs
+//
+HRESULT CordbProcess::SafeReadBuffer(TargetBuffer tb, BYTE * pLocalBuffer, BOOL throwOnError)
+{
+ ULONG32 cbRead;
+ HRESULT hr = m_pDACDataTarget->ReadVirtual(tb.pAddress,
+ pLocalBuffer,
+ tb.cbSize,
+ &cbRead);
+
+ if (FAILED(hr))
+ {
+ if (throwOnError)
+ ThrowHR(CORDBG_E_READVIRTUAL_FAILURE);
+ else
+ return CORDBG_E_READVIRTUAL_FAILURE;
+ }
+
+ if (cbRead != tb.cbSize)
+ {
+ if (throwOnError)
+ ThrowWin32(ERROR_PARTIAL_COPY);
+ else
+ return HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY);
+ }
+ return S_OK;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Lookup or create an appdomain.
+//
+// Arguments:
+// vmAppDomain - CLR appdomain to lookup
+//
+// Returns:
+// Instance of CordbAppDomain for the given appdomain. This is a cached instance.
+// If the CordbAppDomain does not yet exist, it will be created and added to the cache.
+// Never returns NULL. Throw on error.
+CordbAppDomain * CordbProcess::LookupOrCreateAppDomain(VMPTR_AppDomain vmAppDomain)
+{
+ CordbAppDomain * pAppDomain = m_appDomains.GetBase(VmPtrToCookie(vmAppDomain));
+ if (pAppDomain != NULL)
+ {
+ return pAppDomain;
+ }
+ return CacheAppDomain(vmAppDomain);
+}
+
+CordbAppDomain * CordbProcess::GetSharedAppDomain()
+{
+ if (m_sharedAppDomain == NULL)
+ {
+ CordbAppDomain *pAD = new CordbAppDomain(this, VMPTR_AppDomain::NullPtr());
+ if (InterlockedCompareExchangeT<CordbAppDomain*>(&m_sharedAppDomain, pAD, NULL) != NULL)
+ {
+ delete pAD;
+ }
+ m_sharedAppDomain->InternalAddRef();
+ }
+
+ return m_sharedAppDomain;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Add a new appdomain to the cache.
+//
+// Arguments:
+// vmAppDomain - appdomain to add.
+//
+// Return Value:
+// Pointer to newly created appdomain, which should be the normal case.
+// Throws on failure. Never returns null.
+//
+// Assumptions:
+// Caller ensure the appdomain is not already cached.
+// Caller should have stop-go lock, which provides thread-safety.
+//
+// Notes:
+// This sets unrecoverable error on failure.
+//
+//---------------------------------------------------------------------------------------
+CordbAppDomain * CordbProcess::CacheAppDomain(VMPTR_AppDomain vmAppDomain)
+{
+ INTERNAL_API_ENTRY(GetProcess());
+
+ _ASSERTE(GetProcessLock()->HasLock());
+
+ RSInitHolder<CordbAppDomain> pAppDomain;
+ pAppDomain.Assign(new CordbAppDomain(this, vmAppDomain)); // throws
+
+ // Add to the hash. This will addref the pAppDomain.
+ // Caller ensures we're not already cached.
+ // The cache will take ownership.
+ m_appDomains.AddBaseOrThrow(pAppDomain);
+
+ // see if this is the default AppDomain
+ IDacDbiInterface * pDac = m_pProcess->GetDAC();
+ BOOL fIsDefaultDomain = FALSE;
+
+ fIsDefaultDomain = pDac->IsDefaultDomain(vmAppDomain); // throws
+
+ if (fIsDefaultDomain)
+ {
+ // If this assert fires, then it likely means the target is corrupted.
+ TargetConsistencyCheck(m_pDefaultAppDomain == NULL);
+ m_pDefaultAppDomain = pAppDomain;
+ }
+
+ CordbAppDomain * pReturn = pAppDomain;
+ pAppDomain.ClearAndMarkDontNeuter();
+
+ _ASSERTE(pReturn != NULL);
+ return pReturn;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Callback for Appdomain enumeration.
+//
+// Arguments:
+// vmAppDomain - new appdomain to add to enumeration
+// pUserData - data passed with callback (a 'this' ptr for CordbProcess)
+//
+//
+// Assumptions:
+// Invoked as callback from code:CordbProcess::PrepopulateAppDomains
+//
+//
+//---------------------------------------------------------------------------------------
+
+// static
+void CordbProcess::AppDomainEnumerationCallback(VMPTR_AppDomain vmAppDomain, void * pUserData)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ CordbProcess * pProcess = static_cast<CordbProcess *> (pUserData);
+ INTERNAL_DAC_CALLBACK(pProcess);
+
+ pProcess->LookupOrCreateAppDomain(vmAppDomain);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Traverse appdomains in the target and build up our list.
+//
+// Arguments:
+//
+// Return Value:
+// returns on success.
+// Throws on error. AppDomain cache may be partially populated.
+//
+// Assumptions:
+// This is an non-invasive inspection operation called when the debuggee is stopped.
+//
+// Notes:
+// This can be called multiple times. If the list is non-empty, it will nop.
+//---------------------------------------------------------------------------------------
+void CordbProcess::PrepopulateAppDomainsOrThrow()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ INTERNAL_API_ENTRY(this);
+
+ if (!IsDacInitialized())
+ {
+ return;
+ }
+
+ // DD-primitive that invokes a callback. This may throw.
+ GetDAC()->EnumerateAppDomains(
+ CordbProcess::AppDomainEnumerationCallback,
+ this);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// EnumerateAppDomains enumerates all app domains in the process.
+//
+// Arguments:
+// ppAppDomains - get appdomain enumerator
+//
+// Return Value:
+// S_OK on success.
+//
+// Assumptions:
+//
+//
+// Notes:
+// This operation is non-invasive target.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::EnumerateAppDomains(ICorDebugAppDomainEnum **ppAppDomains)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ ValidateOrThrow(ppAppDomains);
+
+ // Ensure list is populated.
+ PrepopulateAppDomainsOrThrow();
+
+ RSInitHolder<CordbHashTableEnum> pEnum;
+ CordbHashTableEnum::BuildOrThrow(
+ this,
+ GetContinueNeuterList(),
+ &m_appDomains,
+ IID_ICorDebugAppDomainEnum,
+ pEnum.GetAddr());
+
+ *ppAppDomains = static_cast<ICorDebugAppDomainEnum*> (pEnum);
+ pEnum->ExternalAddRef();
+
+ pEnum.ClearAndMarkDontNeuter();
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+/*
+ * GetObject returns the runtime process object.
+ * Note: This method is not yet implemented.
+ */
+HRESULT CordbProcess::GetObject(ICorDebugValue **ppObject)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppObject, ICorDebugObjectValue **);
+
+ return E_NOTIMPL;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Given a taskid, finding the corresponding thread. The function can fail if we do not
+// find any thread with the given taskid
+//
+// Arguments:
+// taskId - The task ID to look for.
+// ppThread - OUT: Space for storing the thread corresponding to the taskId given.
+//
+// Return Value:
+// Typical HRESULT symantics, nothing abnormal.
+//
+HRESULT CordbProcess::GetThreadForTaskID(TASKID taskId, ICorDebugThread2 ** ppThread)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ RSLockHolder lockHolder(GetProcessLock());
+
+ if (ppThread == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ // On initialization, the task ID of every thread is INVALID_TASK_ID, unless a host is present and
+ // the host calls IClrTask::SetTaskIdentifier(). So we need to explicitly check for INVALID_TASK_ID
+ // here and return NULL if necessary. We return S_FALSE because that's the return value for the case
+ // where we can't find a thread for the specified task ID.
+ if (taskId == INVALID_TASK_ID)
+ {
+ *ppThread = NULL;
+ hr = S_FALSE;
+ }
+ else
+ {
+ PrepopulateThreadsOrThrow();
+
+ // now find the ICorDebugThread corresponding to it
+ CordbThread * pThread;
+ HASHFIND hashFind;
+
+
+ for (pThread = m_userThreads.FindFirst(&hashFind);
+ pThread != NULL;
+ pThread = m_userThreads.FindNext(&hashFind))
+ {
+ if (pThread->GetTaskID() == taskId)
+ {
+ break;
+ }
+ }
+
+ if (pThread == NULL)
+ {
+ *ppThread = NULL;
+ hr = S_FALSE;
+ }
+ else
+ {
+ *ppThread = pThread;
+ pThread->ExternalAddRef();
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbProcess::GetThreadForTaskid
+
+HRESULT
+CordbProcess::GetVersion(COR_VERSION* pVersion)
+{
+ if (NULL == pVersion)
+ {
+ return E_INVALIDARG;
+ }
+
+ //
+ // Because we require a matching version of mscordbi.dll to debug a certain version of the runtime,
+ // we can just use constants found in this particular mscordbi.dll to determine the version of the left side.
+ pVersion->dwMajor = VER_MAJORVERSION;
+ pVersion->dwMinor = VER_MINORVERSION;
+ pVersion->dwBuild = VER_PRODUCTBUILD;
+ pVersion->dwSubBuild = VER_PRODUCTBUILD_QFE;
+
+ return S_OK;
+}
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+//-----------------------------------------------------------------------------
+// Search for a native patch given the address. Return null if not found.
+// Since we return an address, this is only valid until the table is disturbed.
+//-----------------------------------------------------------------------------
+NativePatch * CordbProcess::GetNativePatch(const void * pAddress)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ int cTotal = m_NativePatchList.Count();
+ NativePatch * pTable = m_NativePatchList.Table();
+ if (pTable == NULL)
+ {
+ return NULL;
+ }
+
+ for(int i = 0; i < cTotal; i++)
+ {
+ if (pTable[i].pAddress == pAddress)
+ {
+ return &pTable[i];
+ }
+ }
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Is there an break-opcode (int3 on x86) at the address in the debuggee?
+//-----------------------------------------------------------------------------
+bool CordbProcess::IsBreakOpcodeAtAddress(const void * address)
+{
+ // There should have been an int3 there already. Since we already put it in there,
+ // we should be able to safely read it out.
+ BYTE opcodeTest = 0;
+
+ HRESULT hr = SafeReadStruct(PTR_TO_CORDB_ADDRESS(address), &opcodeTest);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ return (opcodeTest == CORDbg_BREAK_INSTRUCTION);
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+//-----------------------------------------------------------------------------
+// CordbProcess::SetUnmanagedBreakpoint
+// Called by a native debugger to add breakpoints during Interop.
+// address - remote address into the debuggee
+// bufsize, buffer[] - initial size & buffer for the opcode that we're replacing.
+// buflen - size of the buffer that we write to.
+//-----------------------------------------------------------------------------
+HRESULT
+CordbProcess::SetUnmanagedBreakpoint(CORDB_ADDRESS address, ULONG32 bufsize, BYTE buffer[], ULONG32 * bufLen)
+{
+ LOG((LF_CORDB, LL_INFO100, "CP::SetUnBP: pProcess=%x, address=%p.\n", this, CORDB_ADDRESS_TO_PTR(address)));
+#ifndef FEATURE_INTEROP_DEBUGGING
+ return E_NOTIMPL;
+#else
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ FAIL_IF_MANAGED_ONLY(this);
+ _ASSERTE(!ThreadHoldsProcessLock());
+ Lock();
+ HRESULT hr = SetUnmanagedBreakpointInternal(address, bufsize, buffer, bufLen);
+ Unlock();
+ return hr;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// CordbProcess::SetUnmanagedBreakpointInternal
+// The worker behind SetUnmanagedBreakpoint, this function can set both public
+// breakpoints used by the debugger and internal breakpoints used for utility
+// purposes in interop debugging.
+// address - remote address into the debuggee
+// bufsize, buffer[] - initial size & buffer for the opcode that we're replacing.
+// buflen - size of the buffer that we write to.
+//-----------------------------------------------------------------------------
+HRESULT
+CordbProcess::SetUnmanagedBreakpointInternal(CORDB_ADDRESS address, ULONG32 bufsize, BYTE buffer[], ULONG32 * bufLen)
+{
+ LOG((LF_CORDB, LL_INFO100, "CP::SetUnBPI: pProcess=%x, address=%p.\n", this, CORDB_ADDRESS_TO_PTR(address)));
+#ifndef FEATURE_INTEROP_DEBUGGING
+ return E_NOTIMPL;
+#else
+
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ FAIL_IF_MANAGED_ONLY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ HRESULT hr = S_OK;
+
+ NativePatch * p = NULL;
+#if defined(DBG_TARGET_X86) || defined(DBG_TARGET_AMD64)
+ const BYTE patch = CORDbg_BREAK_INSTRUCTION;
+ BYTE opcode;
+
+ // Make sure args are good
+ if ((buffer == NULL) || (bufsize < sizeof(patch)) || (bufLen == NULL))
+ {
+ hr = E_INVALIDARG;
+ goto ErrExit;
+ }
+
+ // Fail if there's already a patch at this address.
+ if (GetNativePatch(CORDB_ADDRESS_TO_PTR(address)) != NULL)
+ {
+ hr = CORDBG_E_NATIVE_PATCH_ALREADY_AT_ADDR;
+ goto ErrExit;
+ }
+
+ // Preallocate this now so that if are oom, we can fail before we get half-way through.
+ p = m_NativePatchList.Append();
+ if (p == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ goto ErrExit;
+ }
+
+
+ // Read out opcode. 1 byte on x86
+
+ hr = ApplyRemotePatch(this, CORDB_ADDRESS_TO_PTR(address), &p->opcode);
+ if (FAILED(hr))
+ goto ErrExit;
+
+ // It's all successful, so now update our out-params & internal bookkeaping.
+ opcode = (BYTE) p->opcode;
+ buffer[0] = opcode;
+ *bufLen = sizeof(opcode);
+
+ p->pAddress = CORDB_ADDRESS_TO_PTR(address);
+ p->opcode = opcode;
+
+ _ASSERTE(SUCCEEDED(hr));
+#elif defined(DBG_TARGET_WIN64)
+ PORTABILITY_ASSERT("NYI: CordbProcess::SetUnmanagedBreakpoint, interop debugging NYI on this platform");
+ hr = E_NOTIMPL;
+ goto ErrExit;
+#else
+ hr = E_NOTIMPL;
+ goto ErrExit;
+#endif // DBG_TARGET_X8_
+
+
+ErrExit:
+ // If we failed, then free the patch
+ if (FAILED(hr) && (p != NULL))
+ {
+ m_NativePatchList.Delete(*p);
+ }
+
+ return hr;
+
+#endif // FEATURE_INTEROP_DEBUGGING
+}
+
+
+//-----------------------------------------------------------------------------
+// CordbProcess::ClearUnmanagedBreakpoint
+// Called by a native debugger to remove breakpoints during Interop.
+// The patch is deleted even if the function fails.
+//-----------------------------------------------------------------------------
+HRESULT
+CordbProcess::ClearUnmanagedBreakpoint(CORDB_ADDRESS address)
+{
+ LOG((LF_CORDB, LL_INFO100, "CP::ClearUnBP: pProcess=%x, address=%p.\n", this, CORDB_ADDRESS_TO_PTR(address)));
+#ifndef FEATURE_INTEROP_DEBUGGING
+ return E_NOTIMPL;
+#else
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ FAIL_IF_MANAGED_ONLY(this);
+
+ _ASSERTE(!ThreadHoldsProcessLock());
+
+ HRESULT hr = S_OK;
+ PRD_TYPE opcode;
+
+ Lock();
+
+ // Make sure this is a valid patch.
+ int cTotal = m_NativePatchList.Count();
+ NativePatch * pTable = m_NativePatchList.Table();
+ if (pTable == NULL)
+ {
+ hr = CORDBG_E_NO_NATIVE_PATCH_AT_ADDR;
+ goto ErrExit;
+ }
+
+ int i;
+ for(i = 0; i < cTotal; i++)
+ {
+ if (pTable[i].pAddress == CORDB_ADDRESS_TO_PTR(address))
+ break;
+ }
+
+ if (i >= cTotal)
+ {
+ hr = CORDBG_E_NO_NATIVE_PATCH_AT_ADDR;
+ goto ErrExit;
+ }
+
+ // Found it! Remove it from our table. Note that this may shuffle table contents
+ // around, so don't keep pointers into the table.
+ opcode = pTable[i].opcode;
+
+ m_NativePatchList.Delete(pTable[i]);
+ _ASSERTE(m_NativePatchList.Count() == cTotal - 1);
+
+ // Now remove the patch.
+
+
+
+ // Just call through to Write ProcessMemory
+ hr = RemoveRemotePatch(this, CORDB_ADDRESS_TO_PTR(address), opcode);
+ if (FAILED(hr))
+ goto ErrExit;
+
+
+ // Our internal bookeaping was already updated to remove the patch, so now we're done.
+ // If we had a failure, we should have already bailed.
+ _ASSERTE(SUCCEEDED(hr));
+
+ErrExit:
+ Unlock();
+ return hr;
+#endif // FEATURE_INTEROP_DEBUGGING
+}
+
+
+//------------------------------------------------------------------------------------
+// StopCount, Sync, SyncReceived form our stop-status. This status is super-critical
+// to most hangs, so we stress log it.
+//------------------------------------------------------------------------------------
+void CordbProcess::SetSynchronized(bool fSynch)
+{
+ _ASSERTE(ThreadHoldsProcessLock() || !"Must have process lock to toggle SyncStatus");
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP:: set sync=%d\n", fSynch);
+ m_synchronized = fSynch;
+}
+
+bool CordbProcess::GetSynchronized()
+{
+ // This can be accessed whether we're Locked or not. This means that the result
+ // may change underneath us.
+ return m_synchronized;
+}
+
+void CordbProcess::IncStopCount()
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ m_stopCount++;
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP:: Inc StopCount=%d\n", m_stopCount);
+}
+void CordbProcess::DecStopCount()
+{
+ // We can inc w/ just the process lock (b/c we can dispatch events from the W32ET)
+ // But decrementing (eg, Continue), requires the stop-go lock.
+ // This if an operation takes the SG lock, it ensures we don't continue from underneath it.
+ ASSERT_SINGLE_THREAD_ONLY(HoldsLock(&m_StopGoLock));
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ m_stopCount--;
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP:: Dec StopCount=%d\n", m_stopCount);
+}
+
+// Just gets whether we're stopped or not (m_stopped > 0).
+// You only need the StopGo lock for this.
+bool CordbProcess::IsStopped()
+{
+ // We don't require the process-lock, just the SG-lock.
+ // Holding the SG lock prevents another thread from continuing underneath you.
+ // (see DecStopCount()).
+ // But you could still be running free, and have another thread stop-underneath you.
+ // Thus IsStopped() leans towards returning false.
+ ASSERT_SINGLE_THREAD_ONLY(HoldsLock(&m_StopGoLock));
+
+ return (m_stopCount > 0);
+}
+
+int CordbProcess::GetStopCount()
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ return m_stopCount;
+}
+
+bool CordbProcess::GetSyncCompleteRecv()
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ return m_syncCompleteReceived;
+}
+
+void CordbProcess::SetSyncCompleteRecv(bool fSyncRecv)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP:: set syncRecv=%d\n", fSyncRecv);
+ m_syncCompleteReceived = fSyncRecv;
+}
+
+// This can be used if we ever need the RS to emulate old behavior of previous versions.
+// This can not be used in QIs to deny queries for new interfaces.
+// QIs must be consistent across the lifetime of an object. Say CordbThread used this in a QI
+// do deny returning a ICorDebugThread2 interface when emulating v1.1. Once that Thread is neutered,
+// it no longer has a pointer to the process, and it no longer knows if it should be denying
+// the v2.0 query. An object's QI can't start returning new interfaces onces its neutered.
+bool CordbProcess::SupportsVersion(CorDebugInterfaceVersion featureVersion)
+{
+ _ASSERTE(featureVersion == CorDebugVersion_2_0);
+ return true;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Add an object to the process's Left-Side resource cleanup list
+//
+// Arguments:
+// pObject - non-null object to be added
+//
+// Notes:
+// This list tracks objects with process-scope that hold left-side
+// resources (like func-eval).
+// See code:CordbAppDomain::GetSweepableExitNeuterList for per-appdomain
+// objects with left-side resources.
+void CordbProcess::AddToLeftSideResourceCleanupList(CordbBase * pObject)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(pObject != NULL);
+
+ m_LeftSideResourceCleanupList.Add(this, pObject);
+}
+
+// This list will get actively swept (looking for objects w/ external ref = 0) between continues.
+void CordbProcess::AddToNeuterOnExitList(CordbBase *pObject)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(pObject != NULL);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ this->m_ExitNeuterList.Add(this, pObject);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+}
+
+// Mark that this object should be neutered the next time we Continue the process.
+void CordbProcess::AddToNeuterOnContinueList(CordbBase *pObject)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(pObject != NULL);
+
+ m_ContinueNeuterList.Add(this, pObject); // throws
+}
+
+
+/* ------------------------------------------------------------------------- *
+ * Runtime Controller Event Thread class
+ * ------------------------------------------------------------------------- */
+
+//
+// Constructor
+//
+CordbRCEventThread::CordbRCEventThread(Cordb* cordb)
+{
+ _ASSERTE(cordb != NULL);
+
+ m_cordb.Assign(cordb);
+ m_thread = NULL;
+ m_threadId = 0;
+ m_run = TRUE;
+ m_threadControlEvent = NULL;
+ m_processStateChanged = FALSE;
+
+ g_pRSDebuggingInfo->m_RCET = this;
+}
+
+
+//
+// Destructor. Cleans up all of the open handles and such.
+// This expects that the thread has been stopped and has terminated
+// before being called.
+//
+CordbRCEventThread::~CordbRCEventThread()
+{
+ if (m_threadControlEvent != NULL)
+ CloseHandle(m_threadControlEvent);
+
+ if (m_thread != NULL)
+ CloseHandle(m_thread);
+
+ g_pRSDebuggingInfo->m_RCET = NULL;
+}
+
+//
+// Init sets up all the objects that the thread will need to run.
+//
+HRESULT CordbRCEventThread::Init()
+{
+ if (m_cordb == NULL)
+ return E_INVALIDARG;
+
+ m_threadControlEvent = WszCreateEvent(NULL, FALSE, FALSE, NULL);
+
+ if (m_threadControlEvent == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ return S_OK;
+}
+
+
+#if defined(FEATURE_INTEROP_DEBUGGING)
+//
+// Helper to duplicate a handle or thorw
+//
+// Arguments:
+// pLocalHandle - handle to duplicate into the remote process
+// pRemoteHandle - RemoteHandle structure in IPC block to hold the remote handle.
+// Return value:
+// None. Throws on error.
+//
+void CordbProcess::DuplicateHandleToLocalProcess(HANDLE * pLocalHandle, RemoteHANDLE * pRemoteHandle)
+{
+ _ASSERTE(m_pShim != NULL);
+
+ // Dup RSEA and RSER into this process if we don't already have them.
+ // On Launch, we don't have them yet, but on attach we do.
+ if (*pLocalHandle == NULL)
+ {
+ BOOL fSuccess = pRemoteHandle->DuplicateToLocalProcess(m_handle, pLocalHandle);
+ if (!fSuccess)
+ {
+ ThrowLastError();
+ }
+ }
+
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+// Public entry wrapper for code:CordbProcess::FinishInitializeIPCChannelWorker
+void CordbProcess::FinishInitializeIPCChannel()
+{
+ // This is called directly from a shim callback.
+ PUBLIC_API_ENTRY_FOR_SHIM(this);
+ FinishInitializeIPCChannelWorker();
+}
+
+//
+// Initialize the IPC channel. After this, IPC events can flow in both ways.
+//
+// Return value:
+// Returns S_OK on success.
+//
+// Notes:
+// This will dispatch an UnrecoverableError callback if it fails.
+// This will also initialize key state in the CordbProcess object.
+//
+// @dbgtodo remove helper-thread: this should eventually go away once we get rid of IPC events.
+//
+void CordbProcess::FinishInitializeIPCChannelWorker()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+ _ASSERTE(m_pShim != NULL);
+
+ RSLockHolder lockHolder(&this->m_processMutex);
+
+ // If it's already initialized, then nothing left to do.
+ // this protects us if this function is called multiple times.
+ if (m_initialized)
+ {
+ _ASSERTE(GetDCB() != NULL);
+ return;
+ }
+
+ EX_TRY
+ {
+ LOG((LF_CORDB, LL_INFO1000, "[%x] RCET::HFRCE: first event..., process %p\n", GetCurrentThreadId(), this));
+
+ BOOL fBlockExists;
+ GetEventBlock(&fBlockExists); // throws on error
+
+ LOG((LF_CORDB, LL_EVERYTHING, "Size of CdbP is %d\n", sizeof(CordbProcess)));
+
+ m_pEventChannel->Init(m_handle);
+
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ DuplicateHandleToLocalProcess(&m_leftSideUnmanagedWaitEvent, &GetDCB()->m_leftSideUnmanagedWaitEvent);
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ // Read the Runtime Offsets struct out of the debuggee.
+ hr = GetRuntimeOffsets();
+ IfFailThrow(hr);
+
+ // we need to be careful here. The LS will have a thread running free that may be initializing
+ // fields of the DCB (specifically it may be setting up the helper thread), so we need to make sure
+ // we don't overwrite any fields that the LS is writing. We need to be sure we only write to RS
+ // status fields.
+ m_initialized = true;
+ GetDCB()->m_rightSideIsWin32Debugger = IsInteropDebugging();
+ UpdateLeftSideDCBField(&(GetDCB()->m_rightSideIsWin32Debugger), sizeof(GetDCB()->m_rightSideIsWin32Debugger));
+
+ LOG((LF_CORDB, LL_INFO1000, "[%x] RCET::HFRCE: ...went fine\n", GetCurrentThreadId()));
+ _ASSERTE(SUCCEEDED(hr));
+
+ } EX_CATCH_HRESULT(hr);
+ if (SUCCEEDED(hr))
+ {
+ return;
+ }
+
+ // We only land here on failure cases.
+ // We must have jumped to this label. Maybe we didn't set HR, so check now.
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "HFCR: FAILED hr=0x%08x\n", hr);
+
+ CloseIPCHandles();
+
+ // Rethrow
+ ThrowHR(hr);
+}
+
+
+//---------------------------------------------------------------------------------------
+// Marshals over a string buffer in a managed event
+//
+// Arguments:
+// pTarget - data-target for read the buffer from the LeftSide.
+//
+// Throws on error
+void Ls_Rs_BaseBuffer::CopyLSDataToRSWorker(ICorDebugDataTarget * pTarget)
+{
+ //
+ const DWORD cbCacheSize = m_cbSize;
+
+ // SHOULD not happen for more than once in well-behaved case.
+ if (m_pbRS != NULL)
+ {
+ SIMPLIFYING_ASSUMPTION(!"m_pbRS is non-null; is this a corrupted event?");
+ ThrowHR(E_INVALIDARG);
+ }
+
+ NewHolder<BYTE> pData(new BYTE[cbCacheSize]);
+
+ ULONG32 cbRead;
+ HRESULT hrRead = pTarget->ReadVirtual(PTR_TO_CORDB_ADDRESS(m_pbLS), pData, cbCacheSize , &cbRead);
+
+ if(FAILED(hrRead))
+ {
+ hrRead = CORDBG_E_READVIRTUAL_FAILURE;
+ }
+
+ if (SUCCEEDED(hrRead) && (cbCacheSize != cbRead))
+ {
+ hrRead = HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY);
+ }
+ IfFailThrow(hrRead);
+
+ // Now do Transfer
+ m_pbRS = pData;
+ pData.SuppressRelease();
+}
+
+//---------------------------------------------------------------------------------------
+// Marshals over a Byte buffer in a managed event
+//
+// Arguments:
+// pTarget - data-target for read the buffer from the LeftSide.
+//
+// Throws on error
+void Ls_Rs_ByteBuffer::CopyLSDataToRS(ICorDebugDataTarget * pTarget)
+{
+ CopyLSDataToRSWorker(pTarget);
+}
+
+//---------------------------------------------------------------------------------------
+// Marshals over a string buffer in a managed event
+//
+// Arguments:
+// pTarget - data-target for read the buffer from the LeftSide.
+//
+// Throws on error
+void Ls_Rs_StringBuffer::CopyLSDataToRS(ICorDebugDataTarget * pTarget)
+{
+ CopyLSDataToRSWorker(pTarget);
+
+ // Ensure we're a valid, well-formed string.
+ // @dbgtodo - this should only happen in corrupted scenarios. Perhaps a better HR here?
+ // - null terminated.
+ // - no embedded nulls.
+
+ const WCHAR * pString = GetString();
+ SIZE_T dwExpectedLenWithNull = m_cbSize / sizeof(WCHAR);
+
+ // Should at least have 1 character for the null-terminator.
+ if (dwExpectedLenWithNull == 0)
+ {
+ ThrowHR(CORDBG_E_TARGET_INCONSISTENT);
+ }
+
+ // Ensure that there's a null where we expect it to be.
+ if (pString[dwExpectedLenWithNull-1] != 0)
+ {
+ ThrowHR(CORDBG_E_TARGET_INCONSISTENT);
+ }
+
+ // Now we know it's safe to call wcslen. The buffer is local, so we know the pages are there.
+ // And we know there's a null capping the max length of the string.
+ SIZE_T dwActualLenWithNull = wcslen(pString) + 1;
+ if (dwActualLenWithNull != dwExpectedLenWithNull)
+ {
+ ThrowHR(CORDBG_E_TARGET_INCONSISTENT);
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// Marshals the arguments in a managed-debug event.
+//
+// Arguments:
+// pManagedEvent - (IN/OUT) debug event to marshal. Events are not usable in the host process
+// until they are marshalled. This will marshal the event in-place, and may convert
+// some target addresses to host addresses.
+//
+// Return Value:
+// S_OK on success. Else Error.
+//
+// Assumptions:
+// Target is currently stopped and inspectable.
+// After the event is marshalled, it has resources that must be cleaned up
+// by calling code:DeleteIPCEventHelper.
+//
+// Notes:
+// Call a Copy function (CopyManagedEventFromTarget, CopyRCEventFromIPCBlock)to
+// get the event to marshal.
+// This will marshal args from the target into the host.
+// The debug event is fixed size. But since the debuggee is stopped, this can copy
+// arbitrary-length buffers out of of the debuggee.
+//
+// This could be rolled into code:CordbProcess::RawDispatchEvent
+//---------------------------------------------------------------------------------------
+void CordbProcess::MarshalManagedEvent(DebuggerIPCEvent * pManagedEvent)
+{
+ CONTRACTL
+ {
+ THROWS;
+
+ // Event has already been copied, now we do some quick Marshalling.
+ // Thsi should be a private local copy, and not the one in the IPC block or Target.
+ PRECONDITION(CheckPointer(pManagedEvent));
+ }
+ CONTRACTL_END;
+
+ IfFailThrow(pManagedEvent->hr);
+
+ // This may throw part way through marshalling. But that's ok because
+ // code:DeleteIPCEventHelper can cleanup a partially-marshalled event.
+
+ // Do a pre-processing on the event
+ switch (pManagedEvent->type & DB_IPCE_TYPE_MASK)
+ {
+ case DB_IPCE_MDA_NOTIFICATION:
+ {
+ pManagedEvent->MDANotification.szName.CopyLSDataToRS(this->m_pDACDataTarget);
+ pManagedEvent->MDANotification.szDescription.CopyLSDataToRS(this->m_pDACDataTarget);
+ pManagedEvent->MDANotification.szXml.CopyLSDataToRS(this->m_pDACDataTarget);
+ break;
+ }
+
+ case DB_IPCE_FIRST_LOG_MESSAGE:
+ {
+ pManagedEvent->FirstLogMessage.szContent.CopyLSDataToRS(this->m_pDACDataTarget);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+
+}
+
+
+//---------------------------------------------------------------------------------------
+// Copy a managed debug event from the target process into this local process
+//
+// Arguments:
+// pRecord - native-debug event serving as the envelope for the managed event.
+// pLocalManagedEvent - (dst) required local buffer to hold managed event.
+//
+// Return Value:
+// * True if the event belongs to this runtime. This is very useful when multiple CLRs are
+// loaded into the target and all sending events wit the same exception code.
+// * False if this does not belong to this instance of ICorDebug. (perhaps it's an event
+// intended for another instance of the CLR in the target, or some rogue user code happening
+// to use our exception code).
+// In either case, the event can still be cleaned up via code:DeleteIPCEventHelper.
+//
+// Throws on error. In the error case, the contents of pLocalManagedEvent are undefined.
+// They may have been partially copied from the target. The local managed event does not own
+// any resources until it's marshalled, so the buffer can be ignored if this function fails.
+//
+// Assumptions:
+//
+// Notes:
+// The events are sent form the target via code:Debugger::SendRawEvent
+// This just does a raw Byte copy, but does not do any Marshalling.
+// This should always succeed in the well-behaved case. However, A bad debuggee can
+// always send a poor-formed debug event.
+// We don't distinguish between a badly formed event and an event that's not ours.
+// The event still needs to be Marshaled before being used. (see code:CordbProcess::MarshalManagedEvent)
+//
+//---------------------------------------------------------------------------------------
+#if defined(_MSC_VER) && defined(_TARGET_ARM_)
+// This is a temporary workaround for an ARM specific MS C++ compiler bug (internal LKG build 18.1).
+// Branch < if (ptrRemoteManagedEvent == NULL) > was always taken and the function always returned false.
+// TODO: It should be removed once the bug is fixed.
+#pragma optimize("", off)
+#endif
+bool CordbProcess::CopyManagedEventFromTarget(
+ const EXCEPTION_RECORD * pRecord,
+ DebuggerIPCEvent * pLocalManagedEvent)
+{
+ _ASSERTE(pRecord != NULL);
+ _ASSERTE(pLocalManagedEvent != NULL);
+
+ // Initialize the event enough such backout code can call code:DeleteIPCEventHelper.
+ pLocalManagedEvent->type = DB_IPCE_DEBUGGER_INVALID;
+
+ // Ensure we have a CLR instance ID by now. Either we had one already, or we're in
+ // V2 mode and this is the startup event, and so we'll set it now.
+ HRESULT hr = EnsureClrInstanceIdSet();
+ IfFailThrow(hr);
+ _ASSERTE(m_clrInstanceId != 0);
+
+ // Determine if the event is really a debug event, and for our instance.
+ CORDB_ADDRESS ptrRemoteManagedEvent = IsEventDebuggerNotification(pRecord, m_clrInstanceId);
+
+ if (ptrRemoteManagedEvent == NULL)
+ {
+ return false;
+ }
+
+ // What we are doing on Windows here is dangerous. Any buffer for IPC events must be at least
+ // CorDBIPC_BUFFER_SIZE big, but here we are only copying sizeof(DebuggerIPCEvent). Fortunately, the
+ // only case where an IPC event is bigger than sizeof(DebuggerIPCEvent) is for the second category
+ // described in the comment for code:IEventChannel. In this case, we are just transferring the IPC
+ // event from the native pipeline to the event channel, and the event channel will read it directly from
+ // the send buffer on the LS. See code:CordbRCEventThread::WaitForIPCEventFromProcess.
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ hr = SafeReadStruct(ptrRemoteManagedEvent, pLocalManagedEvent);
+#else
+ // For Mac remote debugging the address returned above is actually a local address.
+ // Also, we need to copy the entire buffer because once a debug event is read from the debugger
+ // transport, it won't be available afterwards.
+ memcpy(reinterpret_cast<BYTE *>(pLocalManagedEvent),
+ CORDB_ADDRESS_TO_PTR(ptrRemoteManagedEvent),
+ CorDBIPC_BUFFER_SIZE);
+ hr = S_OK;
+#endif
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+ IfFailThrow(hr);
+
+ return true;
+}
+#if defined(_MSC_VER) && defined(_TARGET_ARM_)
+#pragma optimize("", on)
+#endif
+
+//---------------------------------------------------------------------------------------
+// EnsureClrInstanceIdSet - Ensure we have a CLR Instance ID to debug
+//
+// In Arrowhead scenarios, the debugger is required to pass a valid CLR instance ID
+// to us in OpenVirtualProcess. In V2 scenarios, for compatibility, we'll allow a
+// CordbProcess object to exist for a process that doesn't yet have the CLR loaded.
+// In this case the CLR instance ID will start off as 0, but be filled in when we see the
+// startup exception indicating the CLR has been loaded.
+//
+// If we don't already have an instance ID, this function sets it to the only CLR in the
+// target process. This requires that a CLR be loaded in the target process.
+//
+// Return Value:
+// S_OK - if m_clrInstanceId was already set, or is now set to a valid CLR instance ID
+// an error HRESULT - if m_clrInstanceId was 0, and cannot be set to a valid value
+// (i.e. because we cannot find a CLR in the target process).
+//
+// Note that we need to probe for this on attach, and it's common to attach before the
+// CLR has been loaded, so we avoid using exceptions for this common case.
+//
+HRESULT CordbProcess::EnsureClrInstanceIdSet()
+{
+ // If we didn't expect a specific CLR, then attempt to attach to any.
+ if (m_clrInstanceId == 0)
+ {
+
+#ifdef FEATURE_CORESYSTEM
+ if(m_cordb->GetTargetCLR() != 0)
+ {
+ m_clrInstanceId = PTR_TO_CORDB_ADDRESS(m_cordb->GetTargetCLR());
+ return S_OK;
+ }
+#endif
+
+ // The only case in which we're allowed to request the "default" CLR instance
+ // ID is when we're running in V2 mode. In V3, the client is required to pass
+ // a non-zero value to OpenVirtualProcess.
+ _ASSERTE(m_pShim != NULL);
+
+ HRESULT hr = m_pShim->FindLoadedCLR(&m_clrInstanceId);
+ if (FAILED(hr))
+ {
+ // Couldn't find a loaded clr - no CLR instance ID yet
+ _ASSERTE(m_clrInstanceId == 0);
+ return hr;
+ }
+ }
+
+ // We've (now) got a valid CLR instance id
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+// // Copy event from IPC block into local.
+//
+// Arguments:
+// pLocalManagedEvent - required local buffer to hold managed event.
+//
+// Return Value:
+// None. Always succeeds.
+//
+// Assumptions:
+// The IPC block has already been opened and filled in with an event.
+//
+// Notes:
+// This is copying from a shared-memory block, which is treated as local memory.
+// This just does a raw Byte copy, but does not do any Marshalling.
+// This does no validation on the event.
+// The event still needs to be Marshaled before being used. (see code:CordbProcess::MarshalManagedEvent)
+//
+//---------------------------------------------------------------------------------------
+void inline CordbProcess::CopyRCEventFromIPCBlock(DebuggerIPCEvent * pLocalManagedEvent)
+{
+ _ASSERTE(pLocalManagedEvent != NULL);
+
+ IfFailThrow(m_pEventChannel->GetEventFromLeftSide(pLocalManagedEvent));
+}
+
+// Return true if this is the RCEvent thread, else false.
+bool CordbRCEventThread::IsRCEventThread()
+{
+ return (m_threadId == GetCurrentThreadId());
+}
+
+//---------------------------------------------------------------------------------------
+// Runtime assert, throws CORDBG_E_TARGET_INCONSISTENT if the expression is not true.
+//
+// Arguments:
+// fExpression - assert parameter. If true, this function is a nop. If false,
+// this will throw a CORDBG_E_TARGET_INCONSISTENT error.
+//
+// Notes:
+// Use this for runtime checks to validate assumptions about the data-target.
+// IcorDebug can't trust that data from the debugee is consistent (perhaps it's
+// corrupted).
+void CordbProcess::TargetConsistencyCheck(bool fExpression)
+{
+ if (!fExpression)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO10000, "Target consistency check failed");
+
+ // When debugging possibly corrupt targets, this failure may be expected. For debugging purposes,
+ // assert if we're not expecting any target inconsistencies.
+ CONSISTENCY_CHECK_MSG( !m_fAssertOnTargetInconsistency, "Target consistency check failed unexpectedly");
+
+ ThrowHR(CORDBG_E_TARGET_INCONSISTENT);
+ }
+}
+
+//
+// SendIPCEvent -- send an IPC event to the runtime controller. All this
+// really does is copy the event into the process's send buffer and sets
+// the RSEA then waits on RSER.
+//
+// Note: when sending a two-way event (replyRequired = true), the
+// eventSize must be large enough for both the event sent and the
+// result event.
+//
+// Returns whether the event was sent successfully. This is different than event->eventHr.
+//
+HRESULT CordbRCEventThread::SendIPCEvent(CordbProcess* process,
+ DebuggerIPCEvent* event,
+ SIZE_T eventSize)
+{
+
+ _ASSERTE(process != NULL);
+ _ASSERTE(event != NULL);
+ _ASSERTE(process->GetShim() != NULL);
+
+#ifdef _DEBUG
+ // We need to be synchronized whenever we're sending an IPC Event.
+ // This may require our callers' using a Stop-Continue holder.
+ // Attach + AsyncBreak are the only (obvious) exceptions.
+ // For continue, we set Sync-Status to false before sending, so we exclude that too.
+ // Everybody else should only be sending events when synced. We should never ever ever
+ // send an event from a CorbXYZ dtor (b/c that would be called at any random time). Instead,
+ // use a NeuterList.
+ switch (event->type)
+ {
+ case DB_IPCE_ATTACHING:
+ case DB_IPCE_ASYNC_BREAK:
+ case DB_IPCE_CONTINUE:
+ break;
+
+ default:
+ CONSISTENCY_CHECK_MSGF(process->GetSynchronized(), ("Must by synced while sending IPC event: %s (0x%x)",
+ IPCENames::GetName(event->type), event->type));
+ }
+#endif
+
+
+ LOG((LF_CORDB, LL_EVERYTHING, "SendIPCEvent in CordbRCEventThread called\n"));
+
+ // For simplicity sake, we have the following conservative invariants when sending IPC events:
+ // - Always hold the Stop-Go lock.
+ // - never on the W32ET.
+ // - Never hold the Process-lock (this allows the w32et to take that lock to pump)
+
+ // Must have the stop-go lock to send an IPC event.
+ CONSISTENCY_CHECK_MSGF(process->GetStopGoLock()->HasLock(), ("Must have stop-go lock to send event. proc=%p, event=%s",
+ process, IPCENames::GetName(event->type)));
+
+ // The w32 ET will need to take the process lock. So if we're holding it here, then we'll
+ // deadlock (since W32 ET is blocked on lock, which we would hold; and we're blocked on W32 ET
+ // to keep pumping.
+ _ASSERTE(!process->ThreadHoldsProcessLock() || !"Can't hold P-lock while sending blocking IPC event");
+
+
+ // Can't be on the w32 ET, or we can't be pumping.
+ // Although we can trickle in here from public APIs, our caller should have validated
+ // that we weren't on the w32et, so the assert here is justified. But just in case there's something we missed,
+ // we have a runtime check (as a final backstop against a deadlock).
+ _ASSERTE(!process->IsWin32EventThread());
+ CORDBFailIfOnWin32EventThread(process);
+
+
+ // If this is an async event, then we expect it to be sent while the process is locked.
+ if (event->asyncSend)
+ {
+ // This may be on the w32et, so we can't hold the stop-go lock.
+ _ASSERTE(event->type == DB_IPCE_ATTACHING); // only async event should be attaching.
+ }
+
+
+ // This will catch us if we've detached or exited.
+ // Note if we exited, then we should have been neutered and so shouldn't even be sending an IPC event,
+ // but just in case, we'll check.
+ CORDBRequireProcessStateOK(process);
+
+
+#ifdef _DEBUG
+ // We should never send an Async Break on the RCET. This will deadlock.
+ // - if we're on the RCET, we should be stopped, and thus Stop() should just bump up a stop count,
+ // and not actually send an AsyncBreak.
+ // - Delayed-Continues help enforce this.
+ // This is a special case of the deadlock check below.
+ if (IsRCEventThread())
+ {
+ _ASSERTE(event->type != DB_IPCE_ASYNC_BREAK);
+ }
+#endif
+
+#ifdef _DEBUG
+ // This assert protects us against a deadlock.
+ // 1) (RCET) blocked on (This function): If we're on the RCET, then the RCET is blocked until we return (duh).
+ // 2) (LS) blocked on (RCET): If the LS is not synchronized, then it may be sending an event to the RCET, and thus blocked on the RCET.
+ // 3) (Helper thread) blocked on (LS): That LS thread may be holding a lock that the helper thread needs, thus blocking the helper thread.
+ // 4) (This function) blocked on (Helper Thread): We block until the helper thread can process our IPC event.
+ // #4 is not true for async events.
+ //
+ // If we hit this assert, it means we may get the deadlock above and we're calling SendIPCEvent at a time we shouldn't.
+ // Note this race is as old as dirt.
+ if (IsRCEventThread() && !event->asyncSend)
+ {
+ // Note that w/ Continue & Attach, GetSynchronized() has a different meaning and the race above won't happen.
+ BOOL fPossibleDeadlock = process->GetSynchronized() || (event->type == DB_IPCE_CONTINUE) || (event->type == DB_IPCE_ATTACHING);
+ CONSISTENCY_CHECK_MSGF(fPossibleDeadlock, ("Possible deadlock while sending: '%s'\n", IPCENames::GetName(event->type)));
+ }
+#endif
+
+
+
+ // Cache this process into the MRU so that we can find it if we're debugging in retail.
+ g_pRSDebuggingInfo->m_MRUprocess = process;
+
+ HRESULT hr = S_OK;
+ HRESULT hrEvent = S_OK;
+ _ASSERTE(event != NULL);
+
+ // NOTE: the eventSize parameter is only so you can specify an event size that is SMALLER than the process send
+ // buffer size!!
+ if (eventSize > CorDBIPC_BUFFER_SIZE)
+ return E_INVALIDARG;
+
+ STRESS_LOG4(LF_CORDB, LL_INFO1000, "CRCET::SIPCE: sending %s to AD 0x%x, proc 0x%x(%d)\n",
+ IPCENames::GetName(event->type), VmPtrToCookie(event->vmAppDomain), process->m_id, process->m_id);
+
+ // For 2-way events, this check is unnecessary (since we already check for LS exit)
+ // But for async events, we need this.
+ // So just check it up here and make everyone's life easier.
+ if (process->m_terminated)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO10000, "CRCET::SIPCE: LS already terminated, shortcut exiting\n");
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ // If the helper thread has died, we can't send an IPC event (and it's never coming back either).
+ // Although we do wait on the thread's handle, there are strange windows where the thread's handle
+ // is not yet signaled even though we've continued from the exit-thread event for the helper.
+ if (process->m_helperThreadDead)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO10000, "CRCET::SIPCE: Helper-thread dead, shortcut exiting\n");
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ BOOL fUnrecoverableError = TRUE;
+ EX_TRY
+ {
+ hr = process->GetEventChannel()->SendEventToLeftSide(event, eventSize);
+ fUnrecoverableError = FALSE;
+ }
+ EX_CATCH_HRESULT(hr);
+
+
+ // If we're sending a Continue() event, then after this, the LS may run free.
+ // If this is the last managed event before the LS exits, (which is the case
+ // if we're responding to either an Exit-Thread or if we respond to a Detach)
+ // the LS may exit at anytime from here on, so we need to be careful.
+
+
+ if (fUnrecoverableError)
+ {
+ _ASSERTE(FAILED(hr));
+ CORDBSetUnrecoverableError(process, hr, 0);
+ }
+ else
+ {
+ // Get a handle to the target process - this call always succeeds
+ HANDLE hLSProcess = NULL;
+ process->GetHandle(&hLSProcess);
+
+ // We take locks to ensure that the CordbProcess object is still alive,
+ // even if the OS process exited.
+ _ASSERTE(hLSProcess != NULL);
+
+ // Check if Sending the IPC event failed
+ if (FAILED(hr))
+ {
+ // The failure to send an event may be due to the target process terminating
+ // (especially, but not exclusively, in the case of async events).
+ // There is a race here - we can't rely on any check above SendEventToLeftSide
+ // to tell us whether the process has exited yet.
+ // Check for that case and return an accurate hresult.
+ DWORD ret = WaitForSingleObject(hLSProcess, 0);
+ if (ret == WAIT_OBJECT_0)
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ // Some other failure sending the IPC event - just return it.
+ return hr;
+ }
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CRCET::SIPCE: sent...\n");
+
+ // If this is an async send, then don't wait for the left side to acknowledge that its read the event.
+ _ASSERTE(!event->asyncSend || !event->replyRequired);
+
+ if (process->GetEventChannel()->NeedToWaitForAck(event))
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000,"CRCET::SIPCE: waiting for left side to read event. (on RSER)\n");
+
+ DWORD ret;
+
+ // Wait for either a reply (common case) or the left side to go away.
+ // We can't detach while waiting for a reply (because detach needs to send events).
+ // All of the outcomes from this wait are completely disjoint.
+ // It's possible for the LS to reply and then exit normally (Thread_Detach, Process_Detach)
+ // and so ExitProcess may have been called, but it doesn't matter.
+
+ enum {
+ ID_RSER = WAIT_OBJECT_0,
+ ID_LSPROCESS,
+ ID_HELPERTHREAD,
+ };
+
+ // Only wait on the helper thread for cases where the process is stopped (and thus we don't expect it do exit on us).
+ // If the process is running and we lose our helper thread, it ought to be during shutdown and we ough to
+ // follow up with an exit.
+ // This includes when we've dispatch Native events, and it includes the AsyncBreak sent to get us from a
+ // win32 frozen state to a synchronized state).
+ HANDLE hHelperThread = NULL;
+ if (process->IsStopped())
+ {
+ hHelperThread = process->GetHelperThreadHandle();
+ }
+
+
+ // Note that in case of a tie (multiple handles signaled), WaitForMultipleObjects gives
+ // priority to the handle earlier in the array.
+ HANDLE waitSet[] = { process->GetEventChannel()->GetRightSideEventAckHandle(), hLSProcess, hHelperThread};
+ DWORD cWaitSet = NumItems(waitSet);
+ if (hHelperThread == NULL)
+ {
+ cWaitSet--;
+ }
+
+ do
+ {
+ ret = WaitForMultipleObjectsEx(cWaitSet, waitSet, FALSE, CordbGetWaitTimeout(), FALSE);
+ // If we timeout because we're waiting for an uncontinued OOB event, we need to just keep waiting.
+ } while ((ret == WAIT_TIMEOUT) && process->IsWaitingForOOBEvent());
+
+ switch(ret)
+ {
+ case ID_RSER:
+ // Normal reply from LS.
+ // This is set iff the LS replied to our event. The LS may have exited since it replied
+ // but we don't care. We still have the reply and we'll pass it on.
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CRCET::SIPCE: left side read the event.\n");
+
+ // If this was a two-way event, then the result is already ready for us. Simply copy the result back
+ // over the original event that was sent. Otherwise, the left side has simply read the event and is
+ // processing it...
+ if (event->replyRequired)
+ {
+ process->GetEventChannel()->GetReplyFromLeftSide(event, eventSize);
+ hrEvent = event->hr;
+ }
+ break;
+
+ case ID_LSPROCESS:
+ // Left side exited on us.
+ // ExitProcess may or may not have been called here (since it's on a different thread).
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CRCET::SIPCE: left side exiting while RS was waiting for reply.\n");
+ hr = CORDBG_E_PROCESS_TERMINATED;
+ break;
+
+ case ID_HELPERTHREAD:
+ // We can only send most IPC events while the LS is synchronized. We shouldn't lose our helper thread
+ // when synced under any sort of normal conditions.
+ // This won't fire if the process already exited, because LSPROCESS gets higher priority in the wait
+ // (since it was placed earlier).
+ // Thus the only "legitimate" window where this could happen would be in a shutdown scenario after
+ // the helper is dead but before the process has died. We shouldn't be synced in that scenario,
+ // so we shouldn't be sending IPC events during it.
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CRCET::SIPCE: lost helper thread.\n");
+
+
+ // Assert because we want to know if we ever actually hit this in any detectable scenario.
+ // However, shutdown can occur in preemptive mode. Thus if the RS does an AsyncBreak late
+ // enough, then the LS will appear to be stopped but may still shutdown.
+ // Since the debuggee can exit asynchronously at any time (eg, suppose somebody forcefully
+ // kills it with taskman), this doesn't introduce a new case.
+ // That aside, it would be great to be able to assert this:
+ //_ASSERTE(!"Potential deadlock - Randomly Lost helper thread");
+
+ // We'll piggy back this on the terminated case.
+ hr = CORDBG_E_PROCESS_TERMINATED;
+ break;
+
+ default:
+ {
+ // If we timed out/failed, check the left side to see if it is in the unrecoverable error mode. If it is,
+ // return the HR from the left side that caused the error. Otherwise, return that we timed out and that
+ // we don't really know why.
+ HRESULT realHR = (ret == WAIT_FAILED) ? HRESULT_FROM_GetLastError() : ErrWrapper(CORDBG_E_TIMEOUT);
+
+ hr = process->CheckForUnrecoverableError();
+
+ if (hr == S_OK)
+ {
+ CORDBSetUnrecoverableError(process, realHR, 0);
+ hr = realHR;
+ }
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CRCET::SIPCE: left side timeout/fail while RS waiting for reply. hr = 0x%08x\n", hr);
+ }
+ break;
+ }
+
+ // If the LS picked up RSEA, it will be reset (since it's an auto event).
+ // But in the case that the wait failed or that the LS exited, we need to explicitly reset RSEA
+ if (hr != S_OK)
+ {
+ process->GetEventChannel()->ClearEventForLeftSide();
+ }
+
+ // Done waiting for reply.
+
+ }
+ }
+
+ process->ForceDacFlush();
+
+ // The hr and hrEvent are 2 very different things.
+ // hr tells us whether the event was sent successfully.
+ // hrEvent tells us how the LS responded to it.
+ // if FAILED(hr), then hrEvent is useless b/c the LS never got it.
+ // But if SUCCEEDED(hr), then hrEvent may still have failed and that could be
+ // valuable information.
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// FlushQueuedEvents flushes a process's event queue.
+//
+// Arguments:
+// pProcess - non-null process object whose queue will be drained
+//
+// Notes:
+// @dbgtodo shim: this should be part of the shim.
+// This dispatches events that are queued up. The queue is populated by
+// the shim's proxy callback (see code:ShimProxyCallback). This will dispatch events
+// to the 'real' callback supplied by the debugger. This will dispatch events
+// as long as the debugger keeps calling continue.
+//
+// This requires that the process lock be held, although it will toggle the lock.
+void CordbRCEventThread::FlushQueuedEvents(CordbProcess* process)
+{
+ CONTRACTL
+ {
+ NOTHROW; // This is happening on the RCET thread, so there's no place to propogate an error back up.
+ }
+ CONTRACTL_END;
+
+ STRESS_LOG0(LF_CORDB,LL_INFO10000, "CRCET::FQE: Beginning to flush queue\n");
+
+ _ASSERTE(process->GetShim() != NULL);
+
+ // We should only call this is we already have queued events
+ _ASSERTE(!process->GetShim()->GetManagedEventQueue()->IsEmpty());
+
+ //
+ // Dispatch queued events so long as they keep calling Continue()
+ // before returning from their callback. If they call Continue(),
+ // process->m_synchronized will be false again and we know to
+ // loop around and dispatch the next event.
+ //
+ _ASSERTE(process->ThreadHoldsProcessLock());
+
+
+ // Give shim a chance to queue any faked attach events. Grab a pointer to the
+ // ShimProcess now, while we still hold the process lock. Once we release the lock,
+ // GetShim() may not work.
+ RSExtSmartPtr<ShimProcess> pShim(process->GetShim());
+
+ // Release lock before we call out to shim to Queue fake events.
+ {
+ RSInverseLockHolder inverseLockHolder(process->GetProcessLock());
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(pProcess);
+
+ // Because we've released the lock, at any point from here forward the
+ // CorDbProcess may suddenly get neutered if the user detaches the debugger.
+
+ pShim->QueueFakeAttachEventsIfNeeded(false);
+ }
+ }
+
+ // Now that we're holding the process lock again, we can safely check whether
+ // process has become neutered
+ if (process->IsNeutered())
+ {
+ return;
+ }
+
+ {
+
+ // Main dispatch loop here. DispatchRCEvent will take events out of the
+ // queue and invoke callbacks
+ do
+ {
+ // DispatchRCEvent will mark the process as stopped before dispatching.
+ process->DispatchRCEvent();
+
+ LOG((LF_CORDB,LL_INFO10000, "CRCET::FQE: Finished w/ "
+ "DispatchRCEvent\n"));
+ }
+ while (process->GetSyncCompleteRecv() &&
+ (process->GetSynchronized() == false) &&
+ (process->GetShim() != NULL) && // may have lost Shim if we detached while dispatch
+ (!process->GetShim()->GetManagedEventQueue()->IsEmpty()) &&
+ (process->m_unrecoverableError == false));
+ }
+
+ //
+ // If they returned from a callback without calling Continue() then
+ // the process is still synchronized, so let the rc event thread
+ // know that it need to update its process list and remove the
+ // process's event.
+ //
+ if (process->GetSynchronized())
+ {
+ ProcessStateChanged();
+ }
+
+ LOG((LF_CORDB,LL_INFO10000, "CRCET::FQE: finished\n"));
+}
+
+//---------------------------------------------------------------------------------------
+// Preliminary Handle an Notification event from the target. This may queue the event,
+// but does not actually dispatch the event.
+//
+// Arguments:
+// pManagedEvent - local managed-event. On success, this function assumes ownership of the
+// event and will delete its memory. Assumed that caller allocated via 'new'.
+// pCallback - callback obecjt to dispatch events on.
+//
+// Return Value:
+// None. Throws on error. On error, caller still owns the pManagedEvent and must free it.
+//
+// Assumptions:
+// This should be called once a notification event is received from the target.
+//
+// Notes:
+// HandleRCEvent -- handle an IPC event received from the runtime controller.
+// This will update ICorDebug state and immediately dispatch the event.
+//
+//---------------------------------------------------------------------------------------
+void CordbProcess::HandleRCEvent(
+ DebuggerIPCEvent * pManagedEvent,
+ RSLockHolder * pLockHolder,
+ ICorDebugManagedCallback * pCallback)
+{
+ CONTRACTL
+ {
+ THROWS;
+ PRECONDITION(CheckPointer(pManagedEvent));
+ PRECONDITION(CheckPointer(pCallback));
+ PRECONDITION(ThreadHoldsProcessLock());
+ }
+ CONTRACTL_END;
+
+ if (!this->IsSafeToSendEvents() || this->m_exiting)
+ {
+ return;
+ }
+
+ // Marshals over some standard data from event.
+ MarshalManagedEvent(pManagedEvent);
+
+ STRESS_LOG4(LF_CORDB, LL_INFO1000, "RCET::TP: Got %s for AD 0x%x, proc 0x%x(%d)\n",
+ IPCENames::GetName(pManagedEvent->type), VmPtrToCookie(pManagedEvent->vmAppDomain), this->m_id, this->m_id);
+
+ RSExtSmartPtr<ICorDebugManagedCallback2> pCallback2;
+ pCallback->QueryInterface(IID_ICorDebugManagedCallback2, reinterpret_cast<void **> (&pCallback2));
+
+ RSExtSmartPtr<ICorDebugManagedCallback3> pCallback3;
+ pCallback->QueryInterface(IID_ICorDebugManagedCallback3, reinterpret_cast<void **> (&pCallback3));
+
+ // Dispatch directly. May not necessarily dispatch an event.
+ // Toggles the lock to dispatch callbacks.
+ RawDispatchEvent(pManagedEvent, pLockHolder, pCallback, pCallback2, pCallback3);
+}
+
+//
+// ProcessStateChanged -- tell the rc event thread that the ICorDebug's
+// process list has changed by setting its flag and thread control event.
+// This will cause the rc event thread to update its set of handles to wait
+// on.
+//
+void CordbRCEventThread::ProcessStateChanged()
+{
+ m_cordb->LockProcessList();
+ STRESS_LOG0(LF_CORDB, LL_INFO100000, "CRCET::ProcessStateChanged\n");
+ m_processStateChanged = TRUE;
+ SetEvent(m_threadControlEvent);
+ m_cordb->UnlockProcessList();
+}
+
+
+//---------------------------------------------------------------------------------------
+// Primary loop of the Runtime Controller event thread. This routine loops during the
+// debug session taking IPC events from the IPC block and calling out to process them.
+//
+// Arguments:
+// None.
+//
+// Return Value:
+// None.
+//
+// Notes:
+// @dbgtodo shim: eventually hoist the entire RCET into the shim.
+//---------------------------------------------------------------------------------------
+void CordbRCEventThread::ThreadProc()
+{
+ HANDLE waitSet[MAXIMUM_WAIT_OBJECTS];
+ CordbProcess * rgProcessSet[MAXIMUM_WAIT_OBJECTS];
+ unsigned int waitCount;
+
+#ifdef _DEBUG
+ memset(&rgProcessSet, NULL, MAXIMUM_WAIT_OBJECTS * sizeof(CordbProcess *));
+ memset(&waitSet, NULL, MAXIMUM_WAIT_OBJECTS * sizeof(HANDLE));
+#endif
+
+
+ // First event to wait on is always the thread control event.
+ waitSet[0] = m_threadControlEvent;
+ rgProcessSet[0] = NULL;
+ waitCount = 1;
+
+ while (m_run)
+ {
+ DWORD dwStatus = WaitForMultipleObjectsEx(waitCount, waitSet, FALSE, 2000, FALSE);
+
+ if (dwStatus == WAIT_FAILED)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO10000, "CordbRCEventThread::ThreadProc WaitFor"
+ "MultipleObjects failed: 0x%x\n", GetLastError());
+ }
+#ifdef _DEBUG
+ else if ((dwStatus >= WAIT_OBJECT_0) && (dwStatus < WAIT_OBJECT_0 + waitCount) && m_run)
+ {
+ // Got an event. Figure out which process it came from.
+ unsigned int procNumber = dwStatus - WAIT_OBJECT_0;
+
+ if (procNumber != 0)
+ {
+ // @dbgtodo shim: rip all of this out. Leave the assert in for now to verify that we're not accidentally
+ // going down this codepath. Once we rip this out, we can also simplify some of the code below.
+ // Notification events (including Sync-complete) should be coming from Win32 event thread via
+ // V3 pipeline.
+ _ASSERTE(!"Shouldn't be here");
+
+ }
+ }
+#endif
+
+ // Empty any queued work items.
+ DrainWorkerQueue();
+
+ // Check a flag to see if we need to update our list of processes to wait on.
+ if (m_processStateChanged)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "RCET::TP: refreshing process list.\n");
+
+ unsigned int i;
+
+ //
+ // free the old wait list
+ //
+ for (i = 1; i < waitCount; i++)
+ {
+ rgProcessSet[i]->InternalRelease();
+ }
+
+ // Pass 1: iterate the hash of all processes and collect the unsynchronized ones into the wait list.
+ // Note that Stop / Continue can still be called on a different thread while we're doing this.
+ m_cordb->LockProcessList();
+ m_processStateChanged = FALSE;
+
+ waitCount = 1;
+
+ CordbSafeHashTable<CordbProcess> * pHashTable = m_cordb->GetProcessList();
+ HASHFIND hashFind;
+ CordbProcess * pProcess;
+
+ for (pProcess = pHashTable->FindFirst(&hashFind); pProcess != NULL; pProcess = pHashTable->FindNext(&hashFind))
+ {
+ _ASSERTE(waitCount < MAXIMUM_WAIT_OBJECTS);
+
+ if( waitCount >= MAXIMUM_WAIT_OBJECTS )
+ {
+ break;
+ }
+
+ // Only listen to unsynchronized processes. Processes that are synchronized will not send events without
+ // being asked by us first, so there is no need to async listen to them.
+ //
+ // Note: if a process is not synchronized then there is no way for it to transition to the syncrhonized
+ // state without this thread receiving an event and taking action. So there is no need to lock the
+ // per-process mutex when checking the process's synchronized flag here.
+ if (!pProcess->GetSynchronized() && pProcess->IsSafeToSendEvents())
+ {
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "RCET::TP: listening to process 0x%x(%d)\n",
+ pProcess->m_id, pProcess->m_id);
+
+ waitSet[waitCount] = pProcess->m_leftSideEventAvailable;
+ rgProcessSet[waitCount] = pProcess;
+ rgProcessSet[waitCount]->InternalAddRef();
+ waitCount++;
+ }
+ }
+
+ m_cordb->UnlockProcessList();
+
+ // Pass 2: for each process that we placed in the wait list, determine if there are any existing queued
+ // events that need to be flushed.
+
+ // Start i at 1 to skip the control event...
+ i = 1;
+
+ while(i < waitCount)
+ {
+ pProcess = rgProcessSet[i];
+
+ // Take the process lock so we can check the queue safely
+ pProcess->Lock();
+
+ // Now that we've just locked the processes, we can safely inspect it and dispatch events.
+ // The process may have changed since when we first added it to the process list in Pass 1,
+ // so we can't make any assumptions about whether it's sync, live, or exiting.
+
+ // Flush the queue if necessary. Note, we only do this if we've actually received a SyncComplete message
+ // from this process. If we haven't received a SyncComplete yet, then we don't attempt to drain any
+ // queued events yet. They'll be drained when the SyncComplete event is actually received.
+ if (pProcess->GetSyncCompleteRecv() &&
+ (pProcess->GetShim() != NULL) &&
+ !pProcess->GetSynchronized())
+ {
+ if (pProcess->GetShim()->GetManagedEventQueue()->IsEmpty())
+ {
+ // Effectively what we are doing here is to continue everything without actually
+ // handling an event. We can get here if the event raised by the LS is a duplicate
+ // creation event, which the shim discards without adding it to the event queue.
+ // See code:ShimProcess::IsDuplicateCreationEvent.
+ //
+ // To continue, we need to increment the stop count first. Also, we can't call
+ // Continue() while holding the process lock.
+ pProcess->SetSynchronized(true);
+ pProcess->IncStopCount();
+ pProcess->Unlock();
+ pProcess->ContinueInternal(FALSE);
+ pProcess->Lock();
+ }
+ else
+ {
+ // This may toggle the process-lock
+ FlushQueuedEvents(pProcess);
+ }
+ }
+
+ // Flushing could have left the process synchronized...
+ // Common case is if the callback didn't call Continue().
+ if (pProcess->GetSynchronized())
+ {
+ // remove the process from the wait list by moving all the other processes down one.
+ if ((i + 1) < waitCount)
+ {
+ memcpy(&rgProcessSet[i], &(rgProcessSet[i+1]), sizeof(rgProcessSet[0]) * (waitCount - i - 1));
+ memcpy(&waitSet[i], &waitSet[i+1], sizeof(waitSet[0]) * (waitCount - i - 1));
+ }
+
+ // drop the count of processes to wait on
+ waitCount--;
+
+ pProcess->Unlock();
+
+ // make sure to release the reference we added when the process was added to the wait list.
+ pProcess->InternalRelease();
+
+ // We don't have to increment i because we've copied the next element into
+ // the current value at i.
+ }
+ else
+ {
+ // Even after flushing, its still not syncd, so leave it in the wait list.
+ pProcess->Unlock();
+
+ // Increment i normally.
+ i++;
+ }
+ }
+ } // end ProcessStateChanged
+ } // while (m_run)
+
+#ifdef _DEBUG_IMPL
+ // We intentionally return while leaking some CordbProcess objects inside
+ // rgProcessSet, in some cases (e.g., I've seen this happen when detaching from a
+ // debuggee almost immediately after attaching to it). In the future, we should
+ // really consider not leaking these anymore. However, I'm unsure how safe it is to just
+ // go and InternalRelease() those guys, as above we intentionally DON'T release them when
+ // they're not synchronized. So for now, to make debug builds happy, exclude those
+ // references when we run CheckMemLeaks() later on. In our next side-by-side release,
+ // consider actually doing InternalRelease() on the remaining CordbProcesses on
+ // retail, and then we can remove the following loop.
+ for (UINT i=1; i < waitCount; i++)
+ {
+ InterlockedDecrement(&Cordb::s_DbgMemTotalOutstandingInternalRefs);
+ }
+#endif //_DEBUG_IMPL
+}
+
+
+//
+// This is the thread's real thread proc. It simply calls to the
+// thread proc on the given object.
+//
+/*static*/
+DWORD WINAPI CordbRCEventThread::ThreadProc(LPVOID parameter)
+{
+ CordbRCEventThread * pThread = (CordbRCEventThread *) parameter;
+
+ INTERNAL_THREAD_ENTRY(pThread);
+ pThread->ThreadProc();
+ return 0;
+}
+
+template<typename T>
+InterlockedStack<T>::InterlockedStack()
+{
+ m_pHead = NULL;
+}
+
+template<typename T>
+InterlockedStack<T>::~InterlockedStack()
+{
+ // This is an arbitrary choice. We expect the stacks be drained.
+ _ASSERTE(m_pHead == NULL);
+}
+
+// Thread safe pushes + pops.
+// Many threads can push simultaneously.
+// Only 1 thread can pop.
+template<typename T>
+void InterlockedStack<T>::Push(T * pItem)
+{
+ // InterlockedCompareExchangePointer(&dest, ex, comp).
+ // Really behaves like:
+ // val = *dest;
+ // if (*dest == comp) { *dest = ex; }
+ // return val;
+ //
+ // We can do a thread-safe assign { comp = dest; dest = ex } via:
+ // do { comp = dest } while (ICExPtr(&dest, ex, comp) != comp));
+
+
+ do
+ {
+ pItem->m_next = m_pHead;
+ }
+ while(InterlockedCompareExchangeT(&m_pHead, pItem, pItem->m_next) != pItem->m_next);
+}
+
+// Returns NULL on empty,
+// else returns the head of the list.
+template<typename T>
+T * InterlockedStack<T>::Pop()
+{
+ if (m_pHead == NULL)
+ {
+ return NULL;
+ }
+
+ // This allows 1 thread to Pop() and race against N threads doing a Push().
+ T * pItem = NULL;
+ do
+ {
+ pItem = m_pHead;
+ } while(InterlockedCompareExchangeT(&m_pHead, pItem->m_next, pItem) != pItem);
+
+ return pItem;
+}
+
+
+// RCET will take ownership of this item and delete it.
+// This can be done w/o taking any locks (thus it can be called from any lock context)
+// This may race w/ the RCET draining the queue.
+void CordbRCEventThread::QueueAsyncWorkItem(RCETWorkItem * pItem)
+{
+ // @todo -
+ // Non-blocking insert into queue.
+
+ _ASSERTE(pItem != NULL);
+
+ m_WorkerStack.Push(pItem);
+
+ // Ping the RCET so that it drains the queue.
+ SetEvent(m_threadControlEvent);
+}
+
+// Execute & delete all workitems in the queue.
+// This can be done w/o taking any locks. (though individual items may take locks).
+void CordbRCEventThread::DrainWorkerQueue()
+{
+ _ASSERTE(IsRCEventThread());
+
+ while(true)
+ {
+ RCETWorkItem* pCur = m_WorkerStack.Pop();
+ if (pCur == NULL)
+ {
+ break;
+ }
+
+ pCur->Do();
+ delete pCur;
+ }
+}
+
+
+//---------------------------------------------------------------------------------------
+// Wait for an reply from the debuggee.
+//
+// Arguments:
+// pProcess - process for debuggee.
+// pAppDomain - not used.
+// pEvent - caller-allocated event to be filled out.
+// This is expected to be at least as big as CorDBIPC_BUFFER_SIZE.
+//
+// Return Value:
+// S_OK on success. else failure.
+//
+// Assumptions:
+// Caller allocates
+//
+// Notes:
+// WaitForIPCEventFromProcess waits for an event from just the specified
+// process. This should only be called when the process is in a synchronized
+// state, which ensures that the RCEventThread isn't listening to the
+// process's event, too, which would get confusing.
+//
+// @dbgtodo - this function should eventually be obsolete once everything
+// is using DAC calls instead of helper-thread.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbRCEventThread::WaitForIPCEventFromProcess(CordbProcess * pProcess,
+ CordbAppDomain * pAppDomain,
+ DebuggerIPCEvent * pEvent)
+{
+ CORDBRequireProcessStateOKAndSync(pProcess, pAppDomain);
+
+ DWORD dwStatus;
+ HRESULT hr = S_OK;
+
+ do
+ {
+ dwStatus = SafeWaitForSingleObject(pProcess,
+ pProcess->m_leftSideEventAvailable,
+ CordbGetWaitTimeout());
+
+ if (pProcess->m_terminated)
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+ // If we timeout because we're waiting for an uncontinued OOB event, we need to just keep waiting.
+ } while ((dwStatus == WAIT_TIMEOUT) && pProcess->IsWaitingForOOBEvent());
+
+
+
+
+ if (dwStatus == WAIT_OBJECT_0)
+ {
+ pProcess->CopyRCEventFromIPCBlock(pEvent);
+
+ EX_TRY
+ {
+ pProcess->MarshalManagedEvent(pEvent);
+
+ STRESS_LOG4(LF_CORDB, LL_INFO1000, "CRCET::SIPCE: Got %s for AD 0x%x, proc 0x%x(%d)\n",
+ IPCENames::GetName(pEvent->type),
+ VmPtrToCookie(pEvent->vmAppDomain),
+ pProcess->m_id,
+ pProcess->m_id);
+
+ }
+ EX_CATCH_HRESULT(hr)
+
+ SetEvent(pProcess->m_leftSideEventRead);
+
+ return hr;
+ }
+ else if (dwStatus == WAIT_TIMEOUT)
+ {
+ //
+ // If we timed out, check the left side to see if it is in the
+ // unrecoverable error mode. If it is, return the HR from the
+ // left side that caused the error. Otherwise, return that we timed
+ // out and that we don't really know why.
+ //
+ HRESULT realHR = ErrWrapper(CORDBG_E_TIMEOUT);
+
+ hr = pProcess->CheckForUnrecoverableError();
+
+ if (hr == S_OK)
+ {
+ CORDBSetUnrecoverableError(pProcess, realHR, 0);
+ return realHR;
+ }
+ else
+ return hr;
+ }
+ else
+ {
+ _ASSERTE(dwStatus == WAIT_FAILED);
+
+ hr = HRESULT_FROM_GetLastError();
+
+ CORDBSetUnrecoverableError(pProcess, hr, 0);
+
+ return hr;
+ }
+}
+
+
+//
+// Start actually creates and starts the thread.
+//
+HRESULT CordbRCEventThread::Start()
+{
+ if (m_threadControlEvent == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ m_thread = CreateThread(NULL,
+ 0,
+ &CordbRCEventThread::ThreadProc,
+ (LPVOID) this,
+ 0,
+ &m_threadId);
+
+ if (m_thread == NULL)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ return S_OK;
+}
+
+
+//
+// Stop causes the thread to stop receiving events and exit. It
+// waits for it to exit before returning.
+//
+HRESULT CordbRCEventThread::Stop()
+{
+ if (m_thread != NULL)
+ {
+ LOG((LF_CORDB, LL_INFO100000, "CRCET::Stop\n"));
+
+ m_run = FALSE;
+
+ SetEvent(m_threadControlEvent);
+
+ DWORD ret = WaitForSingleObject(m_thread, INFINITE);
+
+ if (ret != WAIT_OBJECT_0)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+ }
+
+ m_cordb.Clear();
+
+ return S_OK;
+}
+
+
+/* ------------------------------------------------------------------------- *
+ * Win32 Event Thread class
+ * ------------------------------------------------------------------------- */
+
+enum
+{
+ W32ETA_NONE = 0,
+ W32ETA_CREATE_PROCESS = 1,
+ W32ETA_ATTACH_PROCESS = 2,
+ W32ETA_CONTINUE = 3,
+ W32ETA_DETACH = 4
+};
+
+
+
+//---------------------------------------------------------------------------------------
+// Constructor
+//
+// Arguments:
+// pCordb - Pointer to the owning cordb object for this event thread.
+// pShim - Pointer to the shim for supporting V2 debuggers on V3 architecture.
+//
+//---------------------------------------------------------------------------------------
+CordbWin32EventThread::CordbWin32EventThread(
+ Cordb * pCordb,
+ ShimProcess * pShim
+ ) :
+ m_thread(NULL), m_threadControlEvent(NULL),
+ m_actionTakenEvent(NULL), m_run(TRUE),
+ m_action(W32ETA_NONE)
+{
+ m_cordb.Assign(pCordb);
+ _ASSERTE(pCordb != NULL);
+
+ m_pShim = pShim;
+
+ m_pNativePipeline = NULL;
+}
+
+
+//
+// Destructor. Cleans up all of the open handles and such.
+// This expects that the thread has been stopped and has terminated
+// before being called.
+//
+CordbWin32EventThread::~CordbWin32EventThread()
+{
+ if (m_thread != NULL)
+ CloseHandle(m_thread);
+
+ if (m_threadControlEvent != NULL)
+ CloseHandle(m_threadControlEvent);
+
+ if (m_actionTakenEvent != NULL)
+ CloseHandle(m_actionTakenEvent);
+
+ if (m_pNativePipeline != NULL)
+ {
+ m_pNativePipeline->Delete();
+ m_pNativePipeline = NULL;
+ }
+
+ m_sendToWin32EventThreadMutex.Destroy();
+}
+
+
+//
+// Init sets up all the objects that the thread will need to run.
+//
+HRESULT CordbWin32EventThread::Init()
+{
+ if (m_cordb == NULL)
+ return E_INVALIDARG;
+
+ m_sendToWin32EventThreadMutex.Init("Win32-Send lock", RSLock::cLockFlat, RSLock::LL_WIN32_SEND_LOCK);
+
+ m_threadControlEvent = WszCreateEvent(NULL, FALSE, FALSE, NULL);
+ if (m_threadControlEvent == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ m_actionTakenEvent = WszCreateEvent(NULL, FALSE, FALSE, NULL);
+ if (m_actionTakenEvent == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ m_pNativePipeline = NewPipelineWithDebugChecks();
+ if (m_pNativePipeline == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ return S_OK;
+}
+
+//
+// Main function of the Win32 Event Thread
+//
+void CordbWin32EventThread::ThreadProc()
+{
+#if defined(RSCONTRACTS)
+ DbgRSThread::GetThread()->SetThreadType(DbgRSThread::cW32ET);
+
+ // The win32 ET conceptually holds a lock (all threads do).
+ DbgRSThread::GetThread()->TakeVirtualLock(RSLock::LL_WIN32_EVENT_THREAD);
+#endif
+
+ // In V2, the debuggee decides what to do if the debugger rudely exits / detaches. (This is
+ // handled by host policy). With the OS native-debuggging pipeline, the debugger by default
+ // kills the debuggee if it exits. To emulate V2 behavior, we need to override that default.
+ BOOL fOk = m_pNativePipeline->DebugSetProcessKillOnExit(FALSE);
+ (void)fOk; //prevent "unused variable" error from GCC
+ _ASSERTE(fOk);
+
+
+ // Run the top-level event loop.
+ Win32EventLoop();
+
+#if defined(RSCONTRACTS)
+ // The win32 ET conceptually holds a lock (all threads do).
+ DbgRSThread::GetThread()->ReleaseVirtualLock(RSLock::LL_WIN32_EVENT_THREAD);
+#endif
+}
+
+// Define a holder that calls code:DeleteIPCEventHelper
+NEW_WRAPPER_TEMPLATE1(DeleteIPCEventHolderHelper, DeleteIPCEventHelper);
+typedef DeleteIPCEventHolderHelper<DebuggerIPCEvent> DeleteIPCEventHolder;
+
+//---------------------------------------------------------------------------------------
+//
+// Helper to clean up IPCEvent before deleting it.
+// This must be called after an event is marshalled via code:CordbProcess::MarshalManagedEvent
+//
+// Arguments:
+// pManagedEvent - managed event to delete.
+//
+// Notes:
+// This can delete a partially marshalled event.
+//
+void DeleteIPCEventHelper(DebuggerIPCEvent *pManagedEvent)
+{
+ CONTRACTL
+ {
+ // This is backout code that shouldn't need to throw.
+ NOTHROW;
+ }
+ CONTRACTL_END;
+ if (pManagedEvent == NULL)
+ {
+ return;
+ }
+ switch (pManagedEvent->type & DB_IPCE_TYPE_MASK)
+ {
+ // so far only this event need to cleanup.
+ case DB_IPCE_MDA_NOTIFICATION:
+ pManagedEvent->MDANotification.szName.CleanUp();
+ pManagedEvent->MDANotification.szDescription.CleanUp();
+ pManagedEvent->MDANotification.szXml.CleanUp();
+ break;
+
+ case DB_IPCE_FIRST_LOG_MESSAGE:
+ pManagedEvent->FirstLogMessage.szContent.CleanUp();
+ break;
+
+ default:
+ break;
+ }
+ delete [] (BYTE *)pManagedEvent;
+}
+
+//---------------------------------------------------------------------------------------
+// Handle a CLR specific notification event.
+//
+// Arguments:
+// pManagedEvent - non-null pointer to a managed event.
+// pLockHolder - hold to process lock that gets toggled if this dispatches an event.
+// pCallback - callback to dispatch potential managed events.
+//
+// Return Value:
+// Throws on error.
+//
+// Assumptions:
+// Target is stopped. Record was already determined to be a CLR event.
+//
+// Notes:
+// This is called after caller does WaitForDebugEvent.
+// Any exception this Filter does not recognize is treated as kNotClr.
+// Currently, this includes both managed-exceptions and unmanaged ones.
+// For interop-debugging, the interop logic will handle all kNotClr and triage if
+// it's really a non-CLR exception.
+//
+//---------------------------------------------------------------------------------------
+void CordbProcess::FilterClrNotification(
+ DebuggerIPCEvent * pManagedEvent,
+ RSLockHolder * pLockHolder,
+ ICorDebugManagedCallback * pCallback)
+{
+ CONTRACTL
+ {
+ THROWS;
+ PRECONDITION(CheckPointer(pManagedEvent));
+ PRECONDITION(CheckPointer(pCallback));
+ PRECONDITION(ThreadHoldsProcessLock());
+ }
+ CONTRACTL_END;
+
+ // There are 3 types of events from the LS:
+ // 1) Replies (eg, corresponding to WaitForIPCEvent)
+ // we need to set LSEA/wait on LSER.
+ // 2) Sync-Complete (kind of like a special notification)
+ // Ping the helper
+ // 3) Notifications (eg, Module-load):
+ // these are dispatched immediately.
+ // 4) Left-side Startup event
+
+
+ // IF we're synced, then we must be getting a "Reply".
+ bool fReply = this->GetSynchronized();
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::FCN - Received event %s; fReply: %d\n",
+ IPCENames::GetName(pManagedEvent->type),
+ fReply));
+
+ if (fReply)
+ {
+ //
+ _ASSERTE(m_pShim != NULL);
+ //
+ // Case 1: Reply
+ //
+
+ pLockHolder->Release();
+ _ASSERTE(!ThreadHoldsProcessLock());
+
+ // Save the IPC event and wake up the thread which is waiting for it from the LS.
+ GetEventChannel()->SaveEventFromLeftSide(pManagedEvent);
+ SetEvent(this->m_leftSideEventAvailable);
+
+ // Some other thread called code:CordbRCEventThread::WaitForIPCEventFromProcess, and
+ // that will respond here and set the event.
+
+ DWORD dwResult = WaitForSingleObject(this->m_leftSideEventRead, CordbGetWaitTimeout());
+ pLockHolder->Acquire();
+ if (dwResult != WAIT_OBJECT_0)
+ {
+ // The wait failed. This is probably WAIT_TIMEOUT which suggests a deadlock/assert on
+ // the RCEventThread.
+ CONSISTENCY_CHECK_MSGF(false, ("WaitForSingleObject failed: %d", dwResult));
+ ThrowHR(CORDBG_E_TIMEOUT);
+ }
+ }
+ else
+ {
+ if (pManagedEvent->type == DB_IPCE_LEFTSIDE_STARTUP)
+ {
+ //
+ // Case 4: Left-side startup event. We'll mark that we're attached from oop.
+ //
+
+ // Now that LS is started, we should definitely be able to instantiate DAC.
+ InitializeDac();
+
+ // @dbgtodo 'attach-bit': we don't want the debugger automatically invading the process.
+ GetDAC()->MarkDebuggerAttached(TRUE);
+ }
+ else if (pManagedEvent->type == DB_IPCE_SYNC_COMPLETE)
+ {
+ // Since V3 doesn't request syncs, it shouldn't get sync-complete.
+ // @dbgtodo sync: this changes when V3 can explicitly request an AsyncBreak.
+ _ASSERTE(m_pShim != NULL);
+
+ //
+ // Case 2: Sync Complete
+ //
+
+ HandleSyncCompleteRecieved();
+ }
+ else
+ {
+ //
+ // Case 3: Notification. This will dispatch the event immediately.
+ //
+
+ // Toggles the process-lock if it dispatches callbacks.
+ HandleRCEvent(pManagedEvent, pLockHolder, pCallback);
+
+ } // end Notification
+ }
+}
+
+
+
+//
+// If the thread has an unhandled managed exception, hijack it.
+//
+// Arguments:
+// dwThreadId - OS Thread id.
+//
+// Returns:
+// True if hijacked; false if not.
+//
+// Notes:
+// This is called from shim to emulate being synchronized at an unhandled
+// exception.
+// Other ICorDebug operations could calls this (eg, func-eval at 2nd chance).
+BOOL CordbProcess::HijackThreadForUnhandledExceptionIfNeeded(DWORD dwThreadId)
+{
+ PUBLIC_API_ENTRY(this); // from Shim
+
+ BOOL fHijacked = FALSE;
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ RSLockHolder lockHolder(GetProcessLock());
+
+ // OS will not execute the Unhandled Exception Filter under native debugger, so
+ // we need to hijack the thread to get it to execute the UEF, which will then do
+ // work for unhandled managed exceptions.
+ CordbThread * pThread = TryLookupOrCreateThreadByVolatileOSId(dwThreadId);
+ if (pThread != NULL)
+ {
+ // If the thread has a managed exception, then we should have a pThread object.
+
+ if (pThread->HasUnhandledNativeException())
+ {
+ _ASSERTE(pThread->IsThreadExceptionManaged()); // should have been marked earlier
+
+ pThread->HijackForUnhandledException();
+ fHijacked = TRUE;
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+
+ return fHijacked;
+}
+
+//---------------------------------------------------------------------------------------
+// Validate the given exception record or throw.
+//
+// Arguments:
+// pRawRecord - non-null raw bytes of the exception
+// countBytes - number of bytes in pRawRecord buffer.
+// format - format of pRawRecord
+//
+// Returns:
+// A type-safe exception record from the raw buffer.
+//
+// Notes:
+// This is a helper for code:CordbProcess::Filter.
+// This can do consistency checks on the incoming parameters such as:
+// * verify countBytes matches the expected size for the given format.
+// * verify the format is supported.
+//
+// If we let a given ICD understand multiple formats (eg, have x86 understand both Exr32 and
+// Exr64), this would be the spot to allow the conversion.
+//
+const EXCEPTION_RECORD * CordbProcess::ValidateExceptionRecord(
+ const BYTE pRawRecord[],
+ DWORD countBytes,
+ CorDebugRecordFormat format)
+{
+ ValidateOrThrow(pRawRecord);
+
+ //
+ // Check format against expected platform.
+ //
+
+ // @dbgtodo - , cross-plat: Once we do cross-plat, these should be based off target-architecture not host's.
+#if defined(_WIN64)
+ if (format != FORMAT_WINDOWS_EXCEPTIONRECORD64)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+#else
+ if (format != FORMAT_WINDOWS_EXCEPTIONRECORD32)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+#endif
+
+ // @dbgtodo cross-plat: once we do cross-plat, need to use correct EXCEPTION_RECORD variant.
+ if (countBytes != sizeof(EXCEPTION_RECORD))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+
+ const EXCEPTION_RECORD * pRecord = reinterpret_cast<const EXCEPTION_RECORD *> (pRawRecord);
+
+ return pRecord;
+};
+
+// Return value: S_OK or indication that no more room exists for enabled types
+HRESULT CordbProcess::SetEnableCustomNotification(ICorDebugClass * pClass, BOOL fEnable)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this); // takes the lock
+
+ ValidateOrThrow(pClass);
+
+ ((CordbClass *)pClass)->SetCustomNotifications(fEnable);
+
+ PUBLIC_API_END(hr);
+ return hr;
+} // CordbProcess::SetEnableCustomNotification
+
+//---------------------------------------------------------------------------------------
+// Public implementation of ICDProcess4::Filter
+//
+// Arguments:
+// pRawRecord - non-null raw bytes of the exception
+// countBytes - number of bytes in pRawRecord buffer.
+// format - format of pRawRecord
+// dwFlags - flags providing auxillary info for exception record.
+// dwThreadId - thread that exception occurred on.
+// pCallback - callback to dispatch potential managed events on.
+// pContinueStatus - Continuation status for exception. This dictates what
+// to pass to kernel32!ContinueDebugEvent().
+//
+// Return Value:
+// S_OK on success.
+//
+// Assumptions:
+// Target is stopped.
+//
+// Notes:
+// The exception could be anything, including:
+// - a CLR notification,
+// - a random managed exception (both from managed code or the runtime),
+// - a non-CLR exception
+//
+// This is cross-platform. The {pRawRecord, countBytes, format} describe events
+// on an arbitrary target architecture. On windows, this will be an EXCEPTION_RECORD.
+//
+HRESULT CordbProcess::Filter(
+ const BYTE pRawRecord[],
+ DWORD countBytes,
+ CorDebugRecordFormat format,
+ DWORD dwFlags,
+ DWORD dwThreadId,
+ ICorDebugManagedCallback * pCallback,
+ DWORD * pContinueStatus
+)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this); // takes the lock
+ {
+ //
+ // Validate parameters
+ //
+
+ // If we don't care about the continue status, we leave it untouched.
+ ValidateOrThrow(pContinueStatus);
+ ValidateOrThrow(pCallback);
+
+ const EXCEPTION_RECORD * pRecord = ValidateExceptionRecord(pRawRecord, countBytes, format);
+
+ DWORD dwFirstChance = (dwFlags & IS_FIRST_CHANCE);
+
+ //
+ // Deal with 2nd-chance exceptions. Don't actually hijack now (that's too invasive),
+ // but mark that we have the exception in case a future operation (eg, func-eval) needs to hijack.
+ //
+ if (!dwFirstChance)
+ {
+ CordbThread * pThread = TryLookupOrCreateThreadByVolatileOSId(dwThreadId);
+
+ // If we don't have a managed-thread object, then it certainly can't have a throwable.
+ // It's possible this is still an exception from the native portion of the runtime,
+ // but that's ok, we'll just treat it as a native exception.
+ // This could be expensive, don't want to do it often... (definitely not on every Filter).
+
+
+ // OS will not execute the Unhandled Exception Filter under native debugger, so
+ // we need to hijack the thread to get it to execute the UEF, which will then do
+ // work for unhandled managed exceptions.
+ if ((pThread != NULL) && pThread->IsThreadExceptionManaged())
+ {
+ // Copy exception record for future use in case we decide to hijack.
+ pThread->SetUnhandledNativeException(pRecord);
+ }
+ // we don't care about 2nd-chance exceptions, unless we decide to hijack it later.
+ }
+
+ //
+ // Deal with CLR notifications
+ //
+ else if (pRecord->ExceptionCode == CLRDBG_NOTIFICATION_EXCEPTION_CODE) // Special event code
+ {
+ //
+ // This may not be for us, or we may not have a managed thread object:
+ // 1. Anybody can raise an exception with this exception code, so can't assume this belongs to us yet.
+ // 2. Notifications may come on unmanaged threads if they're coming from MDAs or CLR internal events
+ // fired before the thread is created.
+ //
+ BYTE * pManagedEventBuffer = new BYTE[CorDBIPC_BUFFER_SIZE];
+ DeleteIPCEventHolder pManagedEvent(reinterpret_cast<DebuggerIPCEvent *>(pManagedEventBuffer));
+
+ bool fOwner = CopyManagedEventFromTarget(pRecord, pManagedEvent);
+ if (fOwner)
+ {
+ // This toggles the lock if it dispatches callbacks
+ FilterClrNotification(pManagedEvent, GET_PUBLIC_LOCK_HOLDER(), pCallback);
+
+ // Cancel any notification events from target. These are just supposed to notify ICD and not
+ // actually be real exceptions in the target.
+ // Canceling here also prevents a VectoredExceptionHandler in the target from picking
+ // up exceptions for the CLR.
+ *pContinueStatus = DBG_CONTINUE;
+ }
+
+ // holder will invoke DeleteIPCEventHelper(pManagedEvent).
+ }
+
+ }
+ PUBLIC_API_END(hr);
+ // we may not find the correct mscordacwks so fail gracefully
+ _ASSERTE(SUCCEEDED(hr) || (hr != HRESULT_FROM_WIN32(ERROR_MOD_NOT_FOUND)));
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Wrapper to invoke ICorDebugMutableDataTarget::ContinueStatusChanged
+//
+// Arguments:
+// dwContinueStatus - new continue status
+//
+// Returns:
+// None. Throw on error.
+//
+// Notes:
+// Initial continue status is returned from code:CordbProcess::Filter.
+// Some operations (mainly hijacking on a 2nd-chance exception), may need to
+// override that continue status.
+// ICorDebug operations invoke a callback on the data-target to notify the debugger
+// of a change in status. Debugger may fail the request.
+//
+void CordbProcess::ContinueStatusChanged(DWORD dwThreadId, CORDB_CONTINUE_STATUS dwContinueStatus)
+{
+ HRESULT hr = m_pMutableDataTarget->ContinueStatusChanged(dwThreadId, dwContinueStatus);
+ IfFailThrow(hr);
+}
+
+//---------------------------------------------------------------------------------------
+// Request a synchronization to occur after a debug event is dispatched.
+//
+// Note:
+// This is called in response to a managed debug event, and so we know that we have
+// a worker thread in the process (the one that just sent the event!)
+// This can not be called asynchronously.
+//---------------------------------------------------------------------------------------
+void CordbProcess::RequestSyncAtEvent()
+{
+ GetDAC()->RequestSyncAtEvent();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Primary loop of the Win32 debug event thread.
+//
+//
+// Arguments:
+// None.
+//
+// Return Value:
+// None.
+//
+// Notes:
+// This is it, you've found it, the main guy. This function loops as long as the
+// debugger is around calling the OS WaitForDebugEvent() API. It takes the OS Debug
+// Event and filters it thru the right-side, continuing the process if not recognized.
+//
+// @dbgtodo shim: this will become part of the shim.
+//---------------------------------------------------------------------------------------
+void CordbWin32EventThread::Win32EventLoop()
+{
+ // This must be called from the win32 event thread.
+ _ASSERTE(IsWin32EventThread());
+
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::W32EL: entered win32 event loop\n"));
+
+
+ DEBUG_EVENT event;
+
+ // Allow the timeout for WFDE to be adjustable. Default to 25 ms based off perf numbers (see issue VSWhidbey 132368).
+ DWORD dwWFDETimeout = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_DbgWFDETimeout);
+
+ while (m_run)
+ {
+ BOOL fEventAvailable = FALSE;
+
+ // We should not have any locks right now.
+
+
+ // Have to wait on 2 sources:
+ // WaitForMultipleObjects - ping for messages (create, attach, Continue, detach) and also
+ // process exits in the managed-only case.
+ // Native Debug Events - This is a huge perf hit so we want to avoid it whenever we can.
+ // Only wait on these if we're interop debugging and if the process is not frozen.
+ // A frozen process can't send any debug events, so don't bother looking for them.
+
+
+ unsigned int cWaitCount = 1;
+
+ HANDLE rghWaitSet[2];
+
+ rghWaitSet[0] = m_threadControlEvent;
+
+ DWORD dwWaitTimeout = INFINITE;
+
+ if (m_pProcess != NULL)
+ {
+ // Process is always built on Native debugging pipeline, so it needs to always be prepared to call WFDE
+ // As an optimization, if the target is stopped, then we can avoid calling WFDE.
+ {
+#ifndef FEATURE_INTEROP_DEBUGGING
+ // Managed-only, never win32 stopped, so always check for an event.
+ dwWaitTimeout = 0;
+ fEventAvailable = m_pNativePipeline->WaitForDebugEvent(&event, dwWFDETimeout, m_pProcess);
+#else
+ // Wait for a Win32 debug event from any processes that we may be attached to as the Win32 debugger.
+ const bool fIsWin32Stopped = (m_pProcess->m_state & CordbProcess::PS_WIN32_STOPPED) != 0;
+ const bool fSkipWFDE = fIsWin32Stopped;
+
+
+ const bool fIsInteropDebugging = m_pProcess->IsInteropDebugging();
+ (void)fIsInteropDebugging; //prevent "unused variable" error from GCC
+
+ // Assert checks
+ _ASSERTE(fIsInteropDebugging == m_pShim->IsInteropDebugging());
+
+ if (!fSkipWFDE)
+ {
+ dwWaitTimeout = 0;
+ fEventAvailable = m_pNativePipeline->WaitForDebugEvent(&event, dwWFDETimeout, m_pProcess);
+ }
+ else
+ {
+ // If we're managed-only debugging, then the process should always be running,
+ // which means we always need to be calling WFDE to pump potential debug events.
+ // If we're interop-debugging, then the process can be stopped at a native-debug event,
+ // in which case we don't have to call WFDE until we resume it again.
+ // So we can only skip the WFDE when we're interop-debugging.
+ _ASSERTE(fIsInteropDebugging);
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+ }
+
+
+ } // end m_pProcess != NULL
+
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ // While interop-debugging, the process may get killed rudely underneath us, even if we haven't
+ // continued the last debug event. In such cases, The process object will get signalled normally.
+ // If we didn't just get a native-exitProcess event, then listen on the process handle for exit.
+ // (this includes all managed-only debugging)
+ // It's very important to establish this before we go into the WaitForMutlipleObjects below
+ // because the debuggee may exit while we're sitting in that loop (waiting for the debugger to call Continue).
+ bool fDidNotJustGetExitProcessEvent = !fEventAvailable || (event.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT);
+#else
+ // In non-interop scenarios, we'll never get any native debug events, let alone an ExitProcess native event.
+ bool fDidNotJustGetExitProcessEvent = true;
+#endif // FEATURE_INTEROP_DEBUGGING
+
+
+ // The m_pProcess handle will get nulled out after we process the ExitProcess event, and
+ // that will ensure that we only wait for an Exit event once.
+ if ((m_pProcess != NULL) && fDidNotJustGetExitProcessEvent)
+ {
+ rghWaitSet[1] = m_pProcess->UnsafeGetProcessHandle();
+ cWaitCount = 2;
+ }
+
+ // See if any process that we aren't attached to as the Win32 debugger have exited. (Note: this is a
+ // polling action if we are also waiting for Win32 debugger events. We're also looking at the thread
+ // control event here, too, to see if we're supposed to do something, like attach.
+ DWORD dwStatus = WaitForMultipleObjectsEx(cWaitCount, rghWaitSet, FALSE, dwWaitTimeout, FALSE);
+
+ _ASSERTE((dwStatus == WAIT_TIMEOUT) || (dwStatus < cWaitCount));
+
+ if (!m_run)
+ {
+ _ASSERTE(m_action == W32ETA_NONE);
+ break;
+ }
+
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL - got event , ret=%d, has w32 dbg event=%d\n",
+ dwStatus, fEventAvailable));
+
+ // If we haven't timed out, or if it wasn't the thread control event
+ // that was set, then a process has
+ // exited...
+ if ((dwStatus != WAIT_TIMEOUT) && (dwStatus != WAIT_OBJECT_0))
+ {
+ // Grab the process that exited.
+ _ASSERTE((dwStatus - WAIT_OBJECT_0) == 1);
+ ExitProcess(false); // not detach
+ fEventAvailable = false;
+ }
+ // Should we create a process?
+ else if (m_action == W32ETA_CREATE_PROCESS)
+ {
+ CreateProcess();
+ }
+ // Should we attach to a process?
+ else if (m_action == W32ETA_ATTACH_PROCESS)
+ {
+ AttachProcess();
+ }
+ // Should we detach from a process?
+ else if (m_action == W32ETA_DETACH)
+ {
+ ExitProcess(true); // detach case
+
+ // Once we detach, we don't need to continue any outstanding event.
+ // So act like we never got the event.
+ fEventAvailable = false;
+ PREFIX_ASSUME(m_pProcess == NULL); // W32 cleared process pointer
+ }
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // Should we continue the process?
+ else if (m_action == W32ETA_CONTINUE)
+ {
+ HandleUnmanagedContinue();
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ // We don't need to sweep the FCH threads since we never hijack a thread in cooperative mode.
+
+
+ // Only process an event if one is available.
+ if (!fEventAvailable)
+ {
+ continue;
+ }
+
+ // The only ref we have is the one in the ProcessList hash;
+ // If we dispatch an ExitProcess event, we may even lose that.
+ // But since the CordbProcess is our parent object, we know it won't go away until
+ // it neuters us, so we can safely proceed.
+ // Find the process this event is for.
+ PREFIX_ASSUME(m_pProcess != NULL);
+ _ASSERTE(m_pProcess->m_id == GetProcessId(&event)); // should only get events for our proc
+ g_pRSDebuggingInfo->m_MRUprocess = m_pProcess;
+
+ // Must flush the dac cache since we were just running.
+ m_pProcess->ForceDacFlush();
+
+ // So we've filtered out CLR events.
+ // Let the shim handle the remaining events. This will call back into Filter() if appropriate.
+ // This will also ensure the debug event gets continued.
+ HRESULT hrShim = S_OK;
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(NULL);
+ hrShim = m_pShim->HandleWin32DebugEvent(&event);
+ }
+ // Any errors from the shim (eg. failure to load DAC) are unrecoverable
+ SetUnrecoverableIfFailed(m_pProcess, hrShim);
+
+ } // loop
+
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::W32EL: exiting event loop\n"));
+
+ return;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Returns if the current thread is the win32 thread.
+//
+// Return Value:
+// true iff this is the win32 event thread.
+//
+//---------------------------------------------------------------------------------------
+bool CordbProcess::IsWin32EventThread()
+{
+ _ASSERTE((m_pShim != NULL) || !"Don't check win32 event thread in V3 cases");
+ return m_pShim->IsWin32EventThread();
+}
+
+//---------------------------------------------------------------------------------------
+// Call when the sync complete event is received and can be processed.
+//
+// Notes:
+// This is called when the RS gets the sync-complete from the LS and can process it.
+//
+// This has a somewhat elaborate contract to fill between Interop-debugging, Async-Break, draining the
+// managed event-queue, and coordinating with the dispatch thread (RCET).
+//
+// @dbgtodo - this should eventually get hoisted into the shim.
+void CordbProcess::HandleSyncCompleteRecieved()
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ this->SetSyncCompleteRecv(true);
+
+ // If some thread is waiting for the process to sync, notify that it can go now.
+ if (this->m_stopRequested)
+ {
+ this->SetSynchronized(true);
+ SetEvent(this->m_stopWaitEvent);
+ }
+ else
+ {
+ // Note: we set the m_stopWaitEvent all the time and leave it high while we're stopped. This
+ // must be done after we've checked m_stopRequested.
+ SetEvent(this->m_stopWaitEvent);
+
+ // Otherwise, simply mark that the state of the process has changed and let the
+ // managed event dispatch logic take over.
+ //
+ // Note: process->m_synchronized remains false, which indicates to the RC event
+ // thread that it can dispatch the next managed event.
+ m_cordb->ProcessStateChanged();
+ }
+}
+
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+
+// Get a Thread's _user_ starting address (the real starting address may be some
+// OS shim.)
+// This may return NULL for the Async-Break thread.
+void* GetThreadUserStartAddr(const DEBUG_EVENT* pCreateThreadEvent)
+{
+ // On Win7 and above, we can trust the lpStartAddress field of the CREATE_THREAD_DEBUG_EVENT
+ // to be the user start address (the actual OS start address is an implementation detail that
+ // doesn't need to be exposed to users). Note that we are assuming that the target process
+ // is running on Win7 if mscordbi is. If we ever have some remoting scenario where the target
+ // can run on a different windows machine with a different OS version we will need a way to
+ // determine the target's OS version
+ if(RunningOnWin7())
+ {
+ return pCreateThreadEvent->u.CreateThread.lpStartAddress;
+ }
+
+ // On pre-Win7 OSes, we rely on an OS implementation detail to get the real user thread start:
+ // it exists in EAX at thread start time.
+ // Note that for a brief period of time there was a GetThreadStartInformation API in Longhorn
+ // we could use for this, but it was removed during the Longhorn reset.
+ HANDLE hThread = pCreateThreadEvent->u.CreateThread.hThread;
+#if defined(DBG_TARGET_X86)
+ // Grab the thread's context.
+ DT_CONTEXT c;
+ c.ContextFlags = DT_CONTEXT_FULL;
+ BOOL succ = DbiGetThreadContext(hThread, &c);
+
+ if (succ)
+ {
+ return (void*) c.Eax;
+ }
+#elif defined(DBG_TARGET_AMD64)
+ DT_CONTEXT c;
+ c.ContextFlags = DT_CONTEXT_FULL;
+ BOOL succ = DbiGetThreadContext(hThread, &c);
+
+ if (succ)
+ {
+ return (void*) c.Rcx;
+ }
+#else
+ PORTABILITY_ASSERT("port GetThreadUserStartAddr");
+#endif
+
+ return NULL;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Get (create if needed) the unmanaged thread for an unmanaged debug event.
+//
+// Arguments:
+// event - native debug event.
+//
+// Return Value:
+// Unmanaged thread corresponding to the native debug event.
+//
+//
+// Notes:
+// Thread may be newly allocated, or may be existing. CordbProcess holds
+// list of all CordbUnmanagedThreads, and will handle freeing memory.
+//
+//---------------------------------------------------------------------------------------
+CordbUnmanagedThread * CordbProcess::GetUnmanagedThreadFromEvent(const DEBUG_EVENT * pEvent)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ HRESULT hr;
+
+ CordbUnmanagedThread * pUnmanagedThread = NULL;
+
+ // Remember newly created threads.
+ if (pEvent->dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT)
+ {
+ // We absolutely should have an unmanaged callback by this point.
+ // That means that the client debugger should have called ICorDebug::SetUnmanagedHandler by now.
+ // However, we can't actually enforce that (see comment in ICorDebug::SetUnmanagedHandler for details),
+ // so we do a runtime check to check this.
+ // This is an extremely gross API misuse and an issue in the client if the callback is not set yet.
+ // Without the unmanaged callback, we absolutely can't do interop-debugging. We assert (checked builds) and
+ // dispatch unrecoverable error (retail builds) to avoid an AV.
+
+
+ if (this->m_cordb->m_unmanagedCallback == NULL)
+ {
+ CONSISTENCY_CHECK_MSGF((this->m_cordb->m_unmanagedCallback != NULL),
+ ("GROSS API misuse!!\nNo unmanaged callback set by the time we've received CreateProcess debug event for proces 0x%x.\n",
+ pEvent->dwProcessId));
+
+ CORDBSetUnrecoverableError(this, CORDBG_E_INTEROP_NOT_SUPPORTED, 0);
+
+ // Returning NULL will tell caller not to dispatch event to client. We have no callback object to dispatch upon.
+ return NULL;
+ }
+
+ pUnmanagedThread = this->HandleUnmanagedCreateThread(pEvent->dwThreadId,
+ pEvent->u.CreateProcessInfo.hThread,
+ pEvent->u.CreateProcessInfo.lpThreadLocalBase);
+
+ // Managed-attach won't start until after Cordbg continues from the loader-bp.
+ }
+ else if (pEvent->dwDebugEventCode == CREATE_THREAD_DEBUG_EVENT)
+ {
+ pUnmanagedThread = this->HandleUnmanagedCreateThread(pEvent->dwThreadId,
+ pEvent->u.CreateThread.hThread,
+ pEvent->u.CreateThread.lpThreadLocalBase);
+
+ BOOL fBlockExists = FALSE;
+ hr = S_OK;
+ EX_TRY
+ {
+ // See if we have the debugger control block yet...
+
+ this->GetEventBlock(&fBlockExists);
+
+ // If we have the debugger control block, and if that control block has the address of the thread proc for
+ // the helper thread, then we're initialized enough on the Left Side to recgonize the helper thread based on
+ // its thread proc's address.
+ if (this->GetDCB() != NULL)
+ {
+ // get the latest LS DCB information
+ UpdateRightSideDCB();
+ if ((this->GetDCB()->m_helperThreadStartAddr != NULL) && (pUnmanagedThread != NULL))
+ {
+ void * pStartAddr = GetThreadUserStartAddr(pEvent);
+
+ if (pStartAddr == this->GetDCB()->m_helperThreadStartAddr)
+ {
+ // Remember the ID of the helper thread.
+ this->m_helperThreadId = pEvent->dwThreadId;
+
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::W32EL: Left Side Helper Thread is 0x%x\n", pEvent->dwThreadId));
+ }
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr)
+ {
+ if (fBlockExists && FAILED(hr))
+ {
+ _ASSERTE(IsLegalFatalError(hr));
+ // Send up the DebuggerError event
+ this->UnrecoverableError(hr, 0, NULL, 0);
+
+ // Kill the process.
+ // RS will pump events until we LS process exits.
+ TerminateProcess(this->m_handle, hr);
+
+ return pUnmanagedThread;
+ }
+ }
+ }
+ else
+ {
+ // Find the unmanaged thread that this event is for.
+ pUnmanagedThread = this->GetUnmanagedThread(pEvent->dwThreadId);
+ }
+
+ return pUnmanagedThread;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Handle a native-debug event representing a managed sync-complete event.
+//
+//
+// Return Value:
+// Reaction telling caller how to respond to the native-debug event.
+//
+// Assumptions:
+// Called within the Triage process after receiving a native-debug event.
+//
+//---------------------------------------------------------------------------------------
+Reaction CordbProcess::TriageSyncComplete()
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::TSC: received 'sync complete' flare.\n");
+
+ _ASSERTE(IsInteropDebugging());
+
+ // Note: we really don't need to be suspending Runtime threads that we know have tripped
+ // here. If we ever end up with a nice, quick way to know that about each unmanaged thread, then
+ // we should put that to good use here.
+ this->SuspendUnmanagedThreads();
+
+ this->HandleSyncCompleteRecieved();
+
+ // Let the process run free.
+ return REACTION(cIgnore);
+
+ // At this point, all managed threads are stopped at safe places and all unmanaged
+ // threads are either suspended or hijacked. All stopped managed threads are also hard
+ // suspended (due to the call to SuspendUnmanagedThreads above) except for the thread
+ // that sent the sync complete flare.
+
+ // We've handled this exception, so skip all further processing.
+ UNREACHABLE();
+}
+
+//-----------------------------------------------------------------------------
+// Triage a breakpoint (non-flare) on a "normal" thread.
+//-----------------------------------------------------------------------------
+Reaction CordbProcess::TriageBreakpoint(CordbUnmanagedThread * pUnmanagedThread, const DEBUG_EVENT * pEvent)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ HRESULT hr = S_OK;
+
+ DWORD dwExCode = pEvent->u.Exception.ExceptionRecord.ExceptionCode;
+ const void * pExAddress = pEvent->u.Exception.ExceptionRecord.ExceptionAddress;
+
+ _ASSERTE(dwExCode == STATUS_BREAKPOINT);
+
+ // There are three cases here:
+ //
+ // 1. The breakpoint definetly belongs to the Runtime. (I.e., a BP in our patch table that
+ // is in managed code.) In this case, we continue the process with
+ // DBG_EXCEPTION_NOT_HANDLED, which lets the in-process exception logic kick in as if we
+ // weren't here.
+ //
+ // 2. The breakpoint is definetly not ours. (I.e., a BP that is not in our patch table.) We
+ // pass these up as regular exception events, doing the can't stop check as usual.
+ //
+ // 3. We're not sure. (I.e., a BP in our patch table, but set in unmangaed code.) In this
+ // case, we hijack as usual, also with can't stop check as usual.
+
+ bool fPatchFound = false;
+ bool fPatchIsUnmanaged = false;
+
+ hr = this->FindPatchByAddress(PTR_TO_CORDB_ADDRESS(pExAddress),
+ &fPatchFound,
+ &fPatchIsUnmanaged);
+
+ if (SUCCEEDED(hr))
+ {
+ if (fPatchFound)
+ {
+#ifdef _DEBUG
+ // What if managed & native patch the same address? That could happen on a step out M --> U.
+ {
+ NativePatch * pNativePatch = GetNativePatch(pExAddress);
+ SIMPLIFYING_ASSUMPTION_MSGF(pNativePatch == NULL, ("Have Managed & native patch at 0x%p", pExAddress));
+ }
+#endif
+
+ // BP could be ours... if its unmanaged, then we still need to hijack, so fall
+ // through to that logic. Otherwise, its ours.
+ if (!fPatchIsUnmanaged)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::W32EL: breakpoint exception "
+ "belongs to runtime due to patch table match.\n"));
+
+ return REACTION(cCLR);
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::W32EL: breakpoint exception "
+ "matched in patch table, but its unmanaged so might hijack anyway.\n"));
+
+ // If we're in cooperative mode, then we must have a inproc handler, and don't need to hijack
+ // One way this can happen is the patch placed for a func-eval complete is hit in coop-mode.
+ if (pUnmanagedThread->GetEEPGCDisabled())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Already in coop-mode, don't need to hijack\n"));
+ return REACTION(cCLR);
+ }
+ else
+ {
+ return REACTION(cBreakpointRequiringHijack);
+ }
+ }
+
+ UNREACHABLE();
+ }
+ else // Patch not found
+ {
+ // If we're here, then we have a BP that's not in the managed patch table, and not
+ // in the native patch list. This should be rare. Perhaps an int3 / DebugBreak() / Assert in
+ // the native code stream.
+ // Anyway, we don't know about this patch so we can't skip it. The only thing we can do
+ // is chuck it up to Cordbg and hope they can help us. Note that this is the same case
+ // we were in w. V1.
+
+ // BP doesn't belong to CLR ... so dispatch it to Cordbg as either make it IB or OOB.
+ // @todo - make the runtime 1 giant Can't stop region.
+ bool fCantStop = pUnmanagedThread->IsCantStop();
+
+#ifdef _DEBUG
+ // We rarely expect a raw int3 here. Add a debug check that will assert.
+ // Tests that know they don't have raw int3 can enable this regkey to get
+ // extra coverage.
+ static DWORD s_fBreakOnRawInt3 = -1;
+
+ if (s_fBreakOnRawInt3 == -1)
+ s_fBreakOnRawInt3 = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgBreakOnRawInt3);
+
+ if (s_fBreakOnRawInt3)
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("Unexpected Raw int3 at:%p on tid 0x%x (%d). CantStop=%d."
+ "This assert is used by specific tests to get extra checks."
+ "For normal cases it's ignorable and is enabled by setting DbgBreakOnRawInt3==1.",
+ pExAddress, pEvent->dwThreadId, pEvent->dwThreadId, fCantStop));
+ }
+#endif
+
+ if (fCantStop)
+ {
+ // If we're in a can't stop region, then its OOB no matter what at this point.
+ return REACTION(cOOB);
+ }
+ else
+ {
+ // PGC must be enabled if we're going to stop for an IB event.
+ bool PGCDisabled = pUnmanagedThread->GetEEPGCDisabled();
+ _ASSERTE(!PGCDisabled);
+
+ // Bp is definitely not ours, and PGC is not disabled, so in-band exception.
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::W32EL: breakpoint exception "
+ "does not belong to the runtime due to failed patch table match.\n"));
+
+ return REACTION(cInband);
+ }
+
+ UNREACHABLE();
+ }
+
+ UNREACHABLE();
+ }
+ else
+ {
+ // Patch table lookup failed? Only on OOM or if ReadProcessMemory fails...
+ _ASSERTE(!"Patch table lookup failed!");
+ CORDBSetUnrecoverableError(this, hr, 0);
+ return REACTION(cOOB);
+ }
+
+ UNREACHABLE();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Triage a "normal" 1st chance exception on a "normal" thread.
+// Not hijacked, not the helper thread, not a flare, etc.. This is the common
+// case for a native exception from native code.
+//
+// Arguments:
+// pUnmanagedThread - Pointer to the CordbUnmanagedThread object that we want to hijack.
+// pEvent - Pointer to the debug event which contains the exception code and address.
+//
+// Return Value:
+// The Reaction tells if the event is in-band, out-of-band, CLR specific or ignorable.
+//
+//---------------------------------------------------------------------------------------
+Reaction CordbProcess::Triage1stChanceNonSpecial(CordbUnmanagedThread * pUnmanagedThread, const DEBUG_EVENT * pEvent)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+
+ DWORD dwExCode = pEvent->u.Exception.ExceptionRecord.ExceptionCode;
+ const void * pExAddress = pEvent->u.Exception.ExceptionRecord.ExceptionAddress;
+
+ // This had better not be a flare. If it is, that means we have some race that unmarked
+ // the hijacks.
+ _ASSERTE(!ExceptionIsFlare(dwExCode, pExAddress));
+
+ // Any first chance exception could belong to the Runtime, so long as the Runtime has actually been
+ // initialized. Here we'll setup a first-chance hijack for this thread so that it can give us the
+ // true answer that we need.
+
+ // But none of those exceptions could possibly be ours unless we have a managed thread to go with
+ // this unmanaged thread. A non-NULL EEThreadPtr tells us that there is indeed a managed thread for
+ // this unmanaged thread, even if the Right Side hasn't received a managed ThreadCreate message yet.
+ REMOTE_PTR pEEThread;
+ hr = pUnmanagedThread->GetEEThreadPtr(&pEEThread);
+ _ASSERTE(SUCCEEDED(hr));
+
+ if (pEEThread == NULL)
+ {
+ // No managed thread, so it can't possibly belong to the runtime!
+ // But it may still be in a can't-stop region (think some goofy shutdown case).
+ if (pUnmanagedThread->IsCantStop())
+ {
+ return REACTION(cOOB);
+ }
+ else
+ {
+ return REACTION(cInband);
+ }
+ }
+
+
+
+ // We have to be careful here. A Runtime thread may be in a place where we cannot let an
+ // unmanaged exception stop it. For instance, an unmanaged user breakpoint set on
+ // WaitForSingleObject will prevent Runtime threads from sending events to the Right Side. So at
+ // various points below, we check to see if this Runtime thread is in a place were we can't let
+ // it stop, and if so then we jump over to the out-of-band dispatch logic and treat this
+ // exception as out-of-band. The debugger is supposed to continue from the out-of-band event
+ // properly and help us avoid this problem altogether.
+
+ // Grab a few flags from the thread's state...
+ bool fThreadStepping = false;
+ bool fSpecialManagedException = false;
+
+ pUnmanagedThread->GetEEState(&fThreadStepping, &fSpecialManagedException);
+
+ // If we've got a single step exception, and if the Left Side has indicated that it was
+ // stepping the thread, then the exception is ours.
+ if (dwExCode == STATUS_SINGLE_STEP)
+ {
+ if (fThreadStepping)
+ {
+ // Yup, its the Left Side that was stepping the thread...
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "W32ET::W32EL: single step exception belongs to the runtime.\n");
+
+ return REACTION(cCLR);
+ }
+
+ // Any single step that is triggered when the thread's state doesn't indicate that
+ // we were stepping the thread automatically gets passed out as an unmanged event.
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "W32ET::W32EL: single step exception "
+ "does not belong to the runtime.\n");
+
+ if (pUnmanagedThread->IsCantStop())
+ {
+ return REACTION(cOOB);
+ }
+ else
+ {
+ return REACTION(cInband);
+ }
+
+ UNREACHABLE();
+ }
+
+#ifdef CorDB_Short_Circuit_First_Chance_Ownership
+ // If the runtime indicates that this is a special exception being thrown within the runtime,
+ // then its ours no matter what.
+ else if (fSpecialManagedException)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "W32ET::W32EL: exception belongs to the runtime due to "
+ "special managed exception marking.\n");
+
+ return REACTION(cCLR);
+ }
+ else if ((dwExCode == EXCEPTION_COMPLUS) || (dwExCode == EXCEPTION_HIJACK))
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000,
+ "W32ET::W32EL: exception belongs to Runtime due to match on built in exception code\n");
+
+ return REACTION(cCLR);
+ }
+ else if (dwExCode == EXCEPTION_MSVC)
+ {
+ // The runtime may use C++ exceptions internally. We can still report these
+ // to the debugger as long as we're outside of a can't-stop region.
+ if (pUnmanagedThread->IsCantStop())
+ {
+ return REACTION(cCLR);
+ }
+ else
+ {
+ return REACTION(cInband);
+ }
+ }
+ else if (dwExCode == STATUS_BREAKPOINT)
+ {
+ return TriageBreakpoint(pUnmanagedThread, pEvent);
+ }// end BP case
+#endif
+
+ // It's not a breakpoint or single-step. Now it just comes down to the address from where
+ // the exception is coming from. If it's managed, we give it back to the CLR. If it's
+ // from native, then we dispatch to Cordbg.
+ // We can use DAC to figure this out from Out-of-process.
+ _ASSERTE(dwExCode != STATUS_BREAKPOINT); // BP were already handled.
+
+
+ // Use DAC to decide if it's ours or not w/o going inproc.
+ CORDB_ADDRESS address = PTR_TO_CORDB_ADDRESS(pExAddress);
+
+ IDacDbiInterface::AddressType addrType;
+
+ addrType = GetDAC()->GetAddressType(address);
+ bool fIsCorCode =((addrType == IDacDbiInterface::kAddressManagedMethod) ||
+ (addrType == IDacDbiInterface::kAddressRuntimeManagedCode) ||
+ (addrType == IDacDbiInterface::kAddressRuntimeUnmanagedCode));
+
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "W32ET::W32EL: IsCorCode(0x%I64p)=%d\n", address, fIsCorCode);
+
+
+ if (fIsCorCode)
+ {
+ return REACTION(cCLR);
+ }
+ else
+ {
+ if (pUnmanagedThread->IsCantStop())
+ {
+ return REACTION(cOOB);
+ }
+ else
+ {
+ return REACTION(cInband);
+ }
+ }
+
+ UNREACHABLE();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Triage a 1st-chance exception when the CLR is initialized.
+//
+// Arguments:
+// pUnmanagedThread - thread that the event has occurred on.
+// pEvent - native debug event for the exception that occurred that this is triaging.
+//
+// Return Value:
+// Reaction for how to handle this event.
+//
+// Assumptions:
+// Called when receiving a debug event when the process is stopped.
+//
+// Notes:
+// A 1st-chance event has a wide spectrum of possibility including:
+// - It may be unmanaged or managed.
+// - Or it may be an execution control exception for managed-exceution
+// - thread skipping an OOB event.
+//
+//---------------------------------------------------------------------------------------
+Reaction CordbProcess::TriageExcep1stChanceAndInit(CordbUnmanagedThread * pUnmanagedThread,
+ const DEBUG_EVENT * pEvent)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ _ASSERTE(m_runtimeOffsetsInitialized);
+
+ NativePatch * pNativePatch = NULL;
+ DebuggerIPCRuntimeOffsets * pIPCRuntimeOffsets = &(this->m_runtimeOffsets);
+
+ DWORD dwExCode = pEvent->u.Exception.ExceptionRecord.ExceptionCode;
+ const void * pExAddress = pEvent->u.Exception.ExceptionRecord.ExceptionAddress;
+
+
+#ifdef _DEBUG
+ // Some Interop bugs involve threads that land at a crazy IP. Since we're interop-debugging, we can't
+ // attach a debugger to the LS. So we have some debug mode where we enable the SS flag and thus
+ // produce a trace of where a thread is going.
+ if (pUnmanagedThread->IsDEBUGTrace() && (dwExCode == STATUS_SINGLE_STEP))
+ {
+ pUnmanagedThread->ClearState(CUTS_DEBUG_SingleStep);
+ LOG((LF_CORDB, LL_INFO10000, "DEBUG TRACE, thread %4x at IP: 0x%p\n", pUnmanagedThread->m_id, pExAddress));
+
+ // Clear the exception and pretend this never happened.
+ return REACTION(cIgnore);
+ }
+#endif
+
+ // If we were stepping for exception retrigger and got the single step and it should be hidden then just ignore it.
+ // Anything that isn't cInbandExceptionRetrigger will cause the debug event to be dequeued, stepping turned off, and
+ // it will count as not retriggering
+ // TODO: I don't think the IsSSFlagNeeded() check is needed here though it doesn't break anything
+ if (pUnmanagedThread->IsSSFlagNeeded() && pUnmanagedThread->IsSSFlagHidden() && (dwExCode == STATUS_SINGLE_STEP))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CP::TE1stCAI: ignoring hidden single step\n"));
+ return REACTION(cIgnore);
+ }
+
+ // Is this a breakpoint indicating that the Left Side is now synchronized?
+ if ((dwExCode == STATUS_BREAKPOINT) &&
+ (pExAddress == pIPCRuntimeOffsets->m_notifyRSOfSyncCompleteBPAddr))
+ {
+ return TriageSyncComplete();
+ }
+ else if ((dwExCode == STATUS_BREAKPOINT) &&
+ (pExAddress == pIPCRuntimeOffsets->m_excepForRuntimeHandoffCompleteBPAddr))
+ {
+ _ASSERTE(!"This should be unused now");
+
+ // This notification means that a thread that had been first-chance hijacked is now
+ // finally leaving the hijack.
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::TE1stCAI: received 'first chance hijack handoff complete' flare.\n");
+
+ // Let the process run.
+ return REACTION(cIgnore);
+ }
+ else if ((dwExCode == STATUS_BREAKPOINT) &&
+ (pExAddress == pIPCRuntimeOffsets->m_signalHijackCompleteBPAddr))
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::TE1stCAI: received 'hijack complete' flare.\n");
+ return REACTION(cInbandHijackComplete);
+ }
+ else if ((dwExCode == STATUS_BREAKPOINT) &&
+ (pExAddress == m_runtimeOffsets.m_signalHijackStartedBPAddr))
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::TE1stCAI: received 'hijack started' flare.\n");
+ return REACTION(cFirstChanceHijackStarted);
+ }
+ else if ((dwExCode == STATUS_BREAKPOINT) && ((pNativePatch = GetNativePatch(pExAddress)) != NULL) )
+ {
+ // We hit a native BP placed by Cordbg. This could happen on any thread (including helper)
+ bool fCantStop = pUnmanagedThread->IsCantStop();
+
+ // REVISIT_TODO: if the user also set a breakpoint here then we should dispatch to the debugger
+ // and rely on the debugger to get us past this. Should be a rare case though.
+ if (fCantStop)
+ {
+ // Need to skip it completely; never dispatch.
+ pUnmanagedThread->SetupForSkipBreakpoint(pNativePatch);
+
+ // Debuggee will single step over the patch, and fire a SS exception.
+ // We'll then call FixupForSkipBreakpoint, and continue the process.
+ return REACTION(cIgnore);
+ }
+ else
+ {
+ // Native patch in native code. A very common scenario.
+ // Dispatch as an IB event to Cordbg.
+ STRESS_LOG1(LF_CORDB, LL_INFO10000, "Native patch in native code (at %p), dispatching as IB event.\n", pExAddress);
+ return REACTION(cInband);
+ }
+
+ UNREACHABLE();
+ }
+
+ else if ((dwExCode == STATUS_BREAKPOINT) && !IsBreakOpcodeAtAddress(pExAddress))
+ {
+ // If we got an int3 exception, but there's not actually an int3 at the address, then just reset the IP
+ // to the address. This can happen if the int 3 is cleared after the thread has dispatched it (in which case
+ // WFDE will pick it up) but before we realize it's one of ours.
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "CP::TE1stCAI: Phantom Int3: Tid=0x%x, addr=%p\n", pEvent->dwThreadId, pExAddress);
+
+ DT_CONTEXT context;
+
+ context.ContextFlags = DT_CONTEXT_FULL;
+
+ BOOL fSuccess = DbiGetThreadContext(pUnmanagedThread->m_handle, &context);
+
+ _ASSERTE(fSuccess);
+
+ if (fSuccess)
+ {
+ // Backup IP to point to the instruction we need to execute. Continuing from a breakpoint exception
+ // continues execution at the instruction after the breakpoint, but we need to continue where the
+ // breakpoint was.
+ CORDbgSetIP(&context, (LPVOID) pExAddress);
+
+ fSuccess = DbiSetThreadContext(pUnmanagedThread->m_handle, &context);
+ _ASSERTE(fSuccess);
+ }
+
+ return REACTION(cIgnore);
+ }
+ else if (pUnmanagedThread->IsSkippingNativePatch())
+ {
+ // If we Single-Step over an exception, then the OS never gives us the single-step event.
+ // Thus if we're skipping a native patch, we don't care what exception event we got.
+ LOG((LF_CORDB, LL_INFO100000, "Done skipping native patch. Ex=0x%x\n, IsSS=%d",
+ dwExCode,
+ (dwExCode == STATUS_SINGLE_STEP)));
+
+ // This is the 2nd half of skipping a native patch.
+ // This could happen on any thread (including helper)
+ // We've already removed the opcode and now we just finished a single-step over it.
+ // So put the patch back in, and continue the process.
+ pUnmanagedThread->FixupForSkipBreakpoint();
+
+ return REACTION(cIgnore);
+ }
+ else if (this->IsHelperThreadWorked(pUnmanagedThread->GetOSTid()))
+ {
+ // We should never ever get a single-step event from the helper thread.
+ CONSISTENCY_CHECK_MSGF(dwExCode != STATUS_SINGLE_STEP, (
+ "Single-Step exception on helper thread (tid=0x%x/%d) in debuggee process (pid=0x%x/%d).\n"
+ "For more information, attach a debuggee non-invasively to the LS to get the callstack.\n",
+ pUnmanagedThread->m_id,
+ pUnmanagedThread->m_id,
+ this->m_id,
+ this->m_id));
+
+ // We ignore any first chance exceptions from the helper thread. There are lots of places
+ // on the left side where we attempt to dereference bad object refs and such that will be
+ // handled by exception handlers already in place.
+ //
+ // Note: we check this after checking for the sync complete notification, since that can
+ // come from the helper thread.
+ //
+ // Note: we do let single step and breakpoint exceptions go through to the debugger for processing.
+ if ((dwExCode != STATUS_BREAKPOINT) && (dwExCode != STATUS_SINGLE_STEP))
+ {
+ return REACTION(cCLR);
+ }
+ else
+ {
+ // Since the helper thread is part of the "can't stop" region, we should have already
+ // skipped any BPs on it.
+ // However, any Assert on the helper thread will hit this case.
+ CONSISTENCY_CHECK_MSGF((dwExCode != STATUS_BREAKPOINT), (
+ "Assert on helper thread (tid=0x%x/%d) in debuggee process (pid=0x%x/%d).\n"
+ "For more information, attach a debuggee non-invasively to the LS to get the callstack.\n",
+ pUnmanagedThread->m_id,
+ pUnmanagedThread->m_id,
+ this->m_id,
+ this->m_id));
+
+ // These breakpoint and single step exceptions have to be dispatched to the debugger as
+ // out-of-band events. This tells the debugger that they must continue from these events
+ // immediatly, and that no interaction with the Left Side is allowed until they do so. This
+ // makes sense, since these events are on the helper thread.
+ return REACTION(cOOB);
+ }
+ UNREACHABLE();
+ }
+ else if (pUnmanagedThread->IsFirstChanceHijacked() && this->ExceptionIsFlare(dwExCode, pExAddress))
+ {
+ _ASSERTE(!"This should be unused now");
+ }
+ else if (pUnmanagedThread->IsGenericHijacked())
+ {
+ if (this->ExceptionIsFlare(dwExCode, pExAddress))
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::TE1stCAI: fixing up from generic hijack.\n");
+
+ _ASSERTE(dwExCode == STATUS_BREAKPOINT);
+
+ // Fixup the thread from the generic hijack.
+ pUnmanagedThread->FixupFromGenericHijack();
+
+ // We force continue from this flare, since its only purpose was to notify us that we had to
+ // fixup the thread from a generic hijack.
+ return REACTION(cIgnore);
+ }
+ else
+ {
+ // We might reach here due to the stack overflow issue, due to target
+ // memory corruption, or even due to an exception thrown during hijacking
+
+ BOOL bStackOverflow = FALSE;
+
+ if (dwExCode == STATUS_ACCESS_VIOLATION || dwExCode == STATUS_STACK_OVERFLOW)
+ {
+ CORDB_ADDRESS stackLimit;
+ CORDB_ADDRESS stackBase;
+ if (pUnmanagedThread->GetStackRange(&stackBase, &stackLimit))
+ {
+ TADDR addr = pEvent->u.Exception.ExceptionRecord.ExceptionInformation[1];
+ if (stackLimit <= addr && addr < stackBase)
+ bStackOverflow = TRUE;
+ }
+ else
+ {
+ // to limit the impact of the change we'll consider failure to retrieve the stack
+ // bounds as stack overflow as well
+ bStackOverflow = TRUE;
+ }
+ }
+
+ if (!bStackOverflow)
+ {
+ // generic hijack means we're in CantStop, so return cOOB
+ return REACTION(cOOB);
+ }
+
+ // If generichijacked and its not a flare, and the address referenced is on the stack then we've
+ // got our special stack overflow case. Take off generic hijacked, mark that the helper thread
+ // is dead, throw this event on the floor, and pop anyone in SendIPCEvent out of their wait.
+ pUnmanagedThread->ClearState(CUTS_GenericHijacked);
+
+ this->m_helperThreadDead = true;
+
+ // This only works on Windows, not on Mac. We don't support interop-debugging on Mac anyway.
+ SetEvent(m_pEventChannel->GetRightSideEventAckHandle());
+
+ // Note: we remember that this was a second chance event from one of the special stack overflow
+ // cases with CUES_ExceptionUnclearable. This tells us to force the process to terminate when we
+ // continue from the event. Since for some odd reason the OS decides to re-raise this exception
+ // (first chance then second chance) infinitely.
+
+ _ASSERTE(pUnmanagedThread->HasIBEvent());
+
+ pUnmanagedThread->IBEvent()->SetState(CUES_ExceptionUnclearable);
+
+ //newEvent = false;
+ return REACTION(cInband_NotNewEvent);
+ }
+ }
+ else
+ {
+ Reaction r(REACTION(cOOB));
+ HRESULT hrCheck = S_OK;;
+ EX_TRY
+ {
+ r = Triage1stChanceNonSpecial(pUnmanagedThread, pEvent);
+ }
+ EX_CATCH_HRESULT(hrCheck);
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hrCheck));
+ SetUnrecoverableIfFailed(this, hrCheck);
+
+ return r;
+
+ }
+
+ // At this point, any first-chance exceptions that could be special have been handled. Any
+ // first-chance exception that we're still processing at this point is destined to be
+ // dispatched as an unmanaged event.
+ UNREACHABLE();
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Triage a 2nd-chance exception when the CLR is initialized.
+//
+// Arguments:
+// pUnmanagedThread - thread that the event has occurred on.
+// pEvent - native debug event for the exception that occurred that this is triaging.
+//
+// Return Value:
+// Reaction for how to handle this event.
+//
+// Assumptions:
+// Called when receiving a debug event when the process is stopped.
+//
+// Notes:
+// We already hijacked 2nd-chance managed exceptions, so this is just handling
+// some V2 Interop corner cases.
+// @dbgtodo interop: this should eventually completely go away with the V3 design.
+//
+//---------------------------------------------------------------------------------------
+Reaction CordbProcess::TriageExcep2ndChanceAndInit(CordbUnmanagedThread * pUnmanagedThread, const DEBUG_EVENT * pEvent)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ DWORD dwExCode = pEvent->u.Exception.ExceptionRecord.ExceptionCode;
+
+#ifdef _DEBUG
+ // For debugging, add an extra knob that let us break on any 2nd chance exceptions.
+ // Most tests don't throw 2nd-chance, so we could have this enabled most of the time and
+ // catch bogus 2nd chance exceptions
+ static DWORD dwNo2ndChance = -1;
+
+ if (dwNo2ndChance == -1)
+ {
+ dwNo2ndChance = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgNo2ndChance);
+ }
+
+ if (dwNo2ndChance)
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("2nd chance exception occurred on LS thread=0x%x, code=0x%08x, address=0x%p\n"
+ "This assert is firing b/c you explicitly requested it by having the 'DbgNo2ndChance' knob enabled.\n"
+ "Disable it to avoid asserts on 2nd chance.",
+ pUnmanagedThread->m_id,
+ dwExCode,
+ pEvent->u.Exception.ExceptionRecord.ExceptionAddress));
+ }
+#endif
+
+
+ // Second chance exception, Runtime initialized. It could belong to the Runtime, so we'll check. If it
+ // does, then we'll hijack the thread. Otherwise, well just fall through and let it get
+ // dispatched. Note: we do this so that the CLR's unhandled exception logic gets a chance to run even
+ // though we've got a win32 debugger attached. But the unhandled exception logic never touches
+ // breakpoint or single step exceptions, so we ignore those here, too.
+
+ // There are strange cases with stack overflow exceptions. If a nieve application catches a stack
+ // overflow exception and handles it, without resetting the guard page, then the app will get an AV when
+ // it overflows the stack a second time. We will get the first chance AV, but when we continue from it the
+ // OS won't run any SEH handlers, so our FCH won't actually work. Instead, we'll get the AV back on
+ // second chance right away, and we'll end up right here.
+ if (this->IsSpecialStackOverflowCase(pUnmanagedThread, pEvent))
+ {
+ // IsSpecialStackOverflowCase will queue the event for us, so its no longer a "new event". Setting
+ // newEvent = false here basically prevents us from playing with the event anymore and we fall down
+ // to the dispatch logic below, which will get our already queued first chance AV dispatched for
+ // this thread.
+ //newEvent = false;
+ return REACTION(cInband_NotNewEvent);
+ }
+ else if (this->IsHelperThreadWorked(pUnmanagedThread->GetOSTid()))
+ {
+ // A second chance exception from the helper thread. This is pretty bad... we just force continue
+ // from them and hope for the best.
+ return REACTION(cCLR);
+ }
+
+ if(pUnmanagedThread->IsCantStop())
+ {
+ return REACTION(cOOB);
+ }
+ else
+ {
+ return REACTION(cInband);
+ }
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Triage a win32 Debug event to get a reaction
+//
+// Arguments:
+// pUnmanagedThread - thread that the event has occurred on.
+// pEvent - native debug event for the exception that occurred that this is triaging.
+//
+// Return Value:
+// Reaction for how to handle this event.
+//
+// Assumptions:
+// Called when receiving a debug event when the process is stopped.
+//
+// Notes:
+// This is the main triage routine for Win32 debug events, this delegates to the
+// 1st and 2nd chance routines above appropriately.
+//
+//---------------------------------------------------------------------------------------
+Reaction CordbProcess::TriageWin32DebugEvent(CordbUnmanagedThread * pUnmanagedThread, const DEBUG_EVENT * pEvent)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ // Lots of special cases for exception events. The vast majority of hybrid debugging work that takes
+ // place is in response to exception events. The work below will consider certian exception events
+ // special cases and rather than letting them be queued and dispatched, they will be handled right
+ // here.
+ if (pEvent->dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
+ {
+ STRESS_LOG4(LF_CORDB, LL_INFO1000, "CP::TW32DE: unmanaged exception on "
+ "tid 0x%x, code 0x%08x, addr 0x%08x, chance %d\n",
+ pEvent->dwThreadId,
+ pEvent->u.Exception.ExceptionRecord.ExceptionCode,
+ pEvent->u.Exception.ExceptionRecord.ExceptionAddress,
+ 2-pEvent->u.Exception.dwFirstChance);
+
+#ifdef LOGGING
+ if (pEvent->u.Exception.ExceptionRecord.ExceptionCode == STATUS_ACCESS_VIOLATION)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "\t<%s> address 0x%08x\n",
+ pEvent->u.Exception.ExceptionRecord.ExceptionInformation[0] ? "write to" : "read from",
+ pEvent->u.Exception.ExceptionRecord.ExceptionInformation[1]));
+ }
+#endif
+
+ // Mark the loader bp for kicks. We won't start managed attach until native attach is finished.
+ if (!this->m_loaderBPReceived)
+ {
+ // If its a first chance breakpoint, and its the first one, then its the loader breakpoint.
+ if (pEvent->u.Exception.dwFirstChance &&
+ (pEvent->u.Exception.ExceptionRecord.ExceptionCode == STATUS_BREAKPOINT))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::TW32DE: loader breakpoint received.\n"));
+
+ // Remember that we've received the loader BP event.
+ this->m_loaderBPReceived = true;
+
+ // We never hijack the loader BP anymore (CLR 2.0+).
+ // This is b/c w/ interop-attach, we don't start the managed-attach until _after_ Cordbg
+ // continues from the loader-bp.
+ }
+ } // end of loader bp.
+
+ // This event might be the retriggering of an event we already saw but previously had to hijack
+ if(pUnmanagedThread->HasIBEvent())
+ {
+ const EXCEPTION_RECORD* pRecord1 = &(pEvent->u.Exception.ExceptionRecord);
+ const EXCEPTION_RECORD* pRecord2 = &(pUnmanagedThread->IBEvent()->m_currentDebugEvent.u.Exception.ExceptionRecord);
+ if(pRecord1->ExceptionCode == pRecord2->ExceptionCode &&
+ pRecord1->ExceptionFlags == pRecord2->ExceptionFlags &&
+ pRecord1->ExceptionAddress == pRecord2->ExceptionAddress)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::TW32DE: event is continuation of previously hijacked event.\n");
+ // if we continued from the hijack then we should have already dispatched this event
+ _ASSERTE(pUnmanagedThread->IBEvent()->IsDispatched());
+ return REACTION(cInbandExceptionRetrigger);
+ }
+ }
+
+ // We only care about exception events if they are first chance events and if the Runtime is
+ // initialized within the process. Otherwise, we don't do anything special with them.
+ if (pEvent->u.Exception.dwFirstChance && this->m_initialized)
+ {
+ return TriageExcep1stChanceAndInit(pUnmanagedThread, pEvent);
+ }
+ else if (!pEvent->u.Exception.dwFirstChance && this->m_initialized)
+ {
+ return TriageExcep2ndChanceAndInit(pUnmanagedThread, pEvent);
+ }
+ else
+ {
+ // An exception event, but the Runtime hasn't been initialize. I.e., its an exception event
+ // that we will never try to hijack.
+ return REACTION(cInband);
+ }
+
+ UNREACHABLE();
+ }
+ else
+ // OOB
+ {
+ return REACTION(cOOB);
+ }
+
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Top-level handler for a win32 debug event during Interop-debugging.
+//
+// Arguments:
+// event - native debug event to handle.
+//
+// Assumptions:
+// The process just got a native debug event via WaitForDebugEvent
+//
+// Notes:
+// The function will Triage the excpetion and then handle it based on the
+// appropriate reaction (see: code:Reaction).
+//
+// @dbgtodo interop: this should all go into the shim.
+//---------------------------------------------------------------------------------------
+void CordbProcess::HandleDebugEventForInteropDebugging(const DEBUG_EVENT * pEvent)
+{
+ PUBLIC_API_ENTRY_FOR_SHIM(this);
+ _ASSERTE(IsInteropDebugging() || !"Only do this in real interop handling path");
+
+
+ STRESS_LOG3(LF_CORDB, LL_INFO1000, "W32ET::W32EL: got unmanaged event %d on thread 0x%x, proc 0x%x\n",
+ pEvent->dwDebugEventCode, pEvent->dwThreadId, pEvent->dwProcessId);
+
+ // Get the Lock.
+ _ASSERTE(!this->ThreadHoldsProcessLock());
+
+ RSSmartPtr<CordbProcess> pRef(this); // make sure we're alive...
+
+ RSLockHolder processLockHolder(&this->m_processMutex);
+
+ // If we get a new Win32 Debug event, then we need to flush any cached oop data structures.
+ // This includes refreshing DAC and our patch table.
+ ForceDacFlush();
+ ClearPatchTable();
+
+#ifdef _DEBUG
+ // We want to detect if we've deadlocked. Unfortunately, w/ interop debugging, there can be a lot of
+ // deadtime since we need to wait for a debug event. Thus the CPU usage may appear to be at 0%, but
+ // we're not deadlocked b/c we're still receiving debug events.
+ // So ping every X debug events.
+ static int s_cCount = 0;
+ static int s_iPingLevel = -1;
+ if (s_iPingLevel == -1)
+ {
+ s_iPingLevel = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgPingInterop);
+ }
+ if (s_iPingLevel != 0)
+ {
+ s_cCount++;
+ if (s_cCount >= s_iPingLevel)
+ {
+ s_cCount = 0;
+ ::Beep(1000,100);
+
+ // Refresh so we can adjust ping level midstream.
+ s_iPingLevel = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgPingInterop);
+ }
+ }
+#endif
+
+ bool fNewEvent = true;
+
+ // Mark the process as stopped.
+ this->m_state |= CordbProcess::PS_WIN32_STOPPED;
+
+ CordbUnmanagedThread * pUnmanagedThread = GetUnmanagedThreadFromEvent(pEvent);
+
+ // In retail, if there is no unmanaged thread then we just continue and loop back around. UnrecoverableError has
+ // already been set in this case. Note: there is an issue in the Win32 debugging API that can cause duplicate
+ // ExitThread events. We therefore must handle not finding an unmanaged thread gracefully.
+
+ _ASSERTE((pUnmanagedThread != NULL) || (pEvent->dwDebugEventCode == EXIT_THREAD_DEBUG_EVENT));
+
+ if (pUnmanagedThread == NULL)
+ {
+ // Note: we use ContinueDebugEvent directly here since our continue is very simple and all of our other
+ // continue mechanisms rely on having an UnmanagedThread object to play with ;)
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "W32ET::W32EL: Continuing without thread on tid 0x%x, code=0x%x\n",
+ pEvent->dwThreadId,
+ pEvent->dwDebugEventCode);
+
+ this->m_state &= ~CordbProcess::PS_WIN32_STOPPED;
+
+ BOOL fOk = ContinueDebugEvent(pEvent->dwProcessId, pEvent->dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
+
+ _ASSERTE(fOk || !"ContinueDebugEvent failed when he have no thread. Debuggee is likely hung");
+
+ return;
+ }
+
+ // There's an innate race such that we can get a Debug Event even after we've suspended a thread.
+ // This can happen if the thread has already dispatched the debug event but we haven't called WFDE to pick it up
+ // yet. This is sufficiently goofy that we want to stress log it.
+ if (pUnmanagedThread->IsSuspended())
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "W32ET::W32EL: Thread 0x%x is suspended\n", pEvent->dwThreadId);
+ }
+
+ // For debugging crazy races in retail, we'll keep a rolling queue of win32 debug events.
+ this->DebugRecordWin32Event(pEvent, pUnmanagedThread);
+
+
+ // Check to see if shutdown of the in-proc debugging services has begun. If it has, then we know we'll no longer
+ // be running any managed code, and we know that we can stop hijacking threads. We remember this by setting
+ // m_initialized to false, thus preventing most things from happening elsewhere.
+ // Don't even bother checking the DCB fields until it's been verified (m_initialized == true)
+ if (this->m_initialized && (this->GetDCB() != NULL))
+ {
+ UpdateRightSideDCB();
+ if (this->GetDCB()->m_shutdownBegun)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "W32ET::W32EL: shutdown begun...\n");
+ this->m_initialized = false;
+ }
+ }
+
+#ifdef _DEBUG
+ //Verify that GetThreadContext agrees with the exception address
+ if (pEvent->dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
+ {
+ DT_CONTEXT tempDebugContext;
+ tempDebugContext.ContextFlags = DT_CONTEXT_FULL;
+ DbiGetThreadContext(pUnmanagedThread->m_handle, &tempDebugContext);
+ CordbUnmanagedThread::LogContext(&tempDebugContext);
+ _ASSERTE(CORDbgGetIP(&tempDebugContext) == pEvent->u.Exception.ExceptionRecord.ExceptionAddress ||
+ (DWORD)CORDbgGetIP(&tempDebugContext) == ((DWORD)pEvent->u.Exception.ExceptionRecord.ExceptionAddress)+1);
+ }
+#endif
+
+ // This call will decide what to do w/ the the win32 event we just got. It does a lot of work.
+ Reaction reaction = TriageWin32DebugEvent(pUnmanagedThread, pEvent);
+
+
+ // Stress-log the reaction.
+#ifdef _DEBUG
+ STRESS_LOG3(LF_CORDB, LL_INFO1000, "Reaction: %d (%s), line=%d\n",
+ reaction.GetType(),
+ reaction.GetReactionName(),
+ reaction.GetLine());
+#else
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "Reaction: %d\n", reaction.GetType());
+#endif
+
+ // Make sure the lock wasn't accidentally released.
+ _ASSERTE(ThreadHoldsProcessLock());
+ CordbWin32EventThread * pW32EventThread = this->m_pShim->GetWin32EventThread();
+ _ASSERTE(pW32EventThread != NULL);
+
+ // if we were waiting for a retriggered exception but recieved any other event then turn
+ // off the single stepping and dequeue the IB event. Right now we only use the SS flag internally
+ // for stepping during possible retrigger.
+ if(reaction.GetType() != Reaction::cInbandExceptionRetrigger && pUnmanagedThread->IsSSFlagNeeded())
+ {
+ _ASSERTE(pUnmanagedThread->HasIBEvent());
+ CordbUnmanagedEvent* pUnmanagedEvent = pUnmanagedThread->IBEvent();
+ _ASSERTE(pUnmanagedEvent->IsIBEvent());
+ _ASSERTE(pUnmanagedEvent->IsEventContinuedUnhijacked());
+ _ASSERTE(pUnmanagedEvent->IsDispatched());
+ LOG((LF_CORDB, LL_INFO100000, "CP::HDEFID: IB event did not retrigger ue=0x%p\n", pUnmanagedEvent));
+
+ DequeueUnmanagedEvent(pUnmanagedThread);
+ pUnmanagedThread->EndStepping();
+ }
+
+ switch(reaction.GetType())
+ {
+ // Common for flares.
+ case Reaction::cIgnore:
+
+ // Shouldn't be suspending in the first place with outstanding flares.
+ _ASSERTE(!pUnmanagedThread->IsSuspended());
+
+ pW32EventThread->ForceDbgContinue(this, pUnmanagedThread, DBG_CONTINUE, false);
+ goto LDone;
+
+ case Reaction::cCLR:
+ // Don't care if thread is suspended here. We'll just let the thread continue whatever it's doing.
+
+ this->m_DbgSupport.m_TotalCLR++;
+
+ // If this is for the CLR, then we just continue unhandled and know that the CLR has
+ // a handler inplace to deal w/ this exception.
+ pW32EventThread->ForceDbgContinue(this, pUnmanagedThread, DBG_EXCEPTION_NOT_HANDLED, false);
+ goto LDone;
+
+
+ case Reaction::cInband_NotNewEvent:
+ fNewEvent = false;
+
+ // fall through to Inband case...
+
+ case Reaction::cInband:
+ {
+ this->m_DbgSupport.m_TotalIB++;
+
+ // Hijack in-band events (exception events, exit threads) if there is already an event at the head
+ // of the queue or if the process is currently synchronized. Of course, we only do this if the
+ // process is initialized.
+ //
+ // Note: we also hijack these left over in-band events if we're activley trying to send the
+ // managed continue message to the Left Side. This is controlled by m_specialDeferment below.
+
+ // Only exceptions can be IB events - everything else is OOB.
+ _ASSERTE(pEvent->dwDebugEventCode == EXCEPTION_DEBUG_EVENT);
+
+ // CLR internal exceptions should be sent back to the CLR and never treated as inband events.
+ // If this assert fires, the event was triaged wrong.
+ CONSISTENCY_CHECK_MSGF((pEvent->u.Exception.ExceptionRecord.ExceptionCode != EXCEPTION_COMPLUS),
+ ("Attempting to dispatch a CLR internal exception as an Inband event. Reaction line=%d\n",
+ reaction.GetLine()));
+
+
+ _ASSERTE(!pUnmanagedThread->IsCantStop());
+
+ // We need to decide whether or not to dispatch this event immediately
+ // We defer it to enforce that we only dispatch 1 IB event at a time (managed events are
+ // considered IB here).
+ // This means if:
+ // 1) there's already an outstanding unmanaged inband event (an event the user has not continued from)
+ // 2) If the process is synchronized (since that means we've already dispatched a managed event).
+ // 3) If we've received a SyncComplete event, but aren't yet Sync. This will almost always be the same as
+ // whether we're synced, but has a distict quality. It's always set by the w32 event thread in Interop,
+ // and so it's guaranteed to be serialized against this check here (also on the w32et).
+ // 4) Special deferment - This covers the region where we're sending a Stop/Continue IPC event across.
+ // We defer it here to keep the Helper thread alive so that it can handle these IPC events.
+ // Queued events will be dispatched when continue is called.
+ BOOL fHasUserUncontinuedNativeEvents = HasUserUncontinuedNativeEvents();
+ bool fDeferInbandEvent = (fHasUserUncontinuedNativeEvents ||
+ GetSynchronized() ||
+ GetSyncCompleteRecv() ||
+ m_specialDeferment);
+
+ // If we've got a new event, queue it.
+ if (fNewEvent)
+ {
+ this->QueueUnmanagedEvent(pUnmanagedThread, pEvent);
+ }
+
+ if (fNewEvent && this->m_initialized && fDeferInbandEvent)
+ {
+ STRESS_LOG4(LF_CORDB, LL_INFO1000, "W32ET::W32EL: Needed to defer dispatching event: %d %d %d %d\n",
+ fHasUserUncontinuedNativeEvents,
+ GetSynchronized(),
+ GetSyncCompleteRecv(),
+ m_specialDeferment);
+
+ // this continues the IB debug event into the hijack
+ // the process is now running again
+ pW32EventThread->DoDbgContinue(this, pUnmanagedThread->IBEvent());
+
+ // Since we've hijacked this event, we don't need to do any further processing.
+ goto LDone;
+ }
+ else
+ {
+ // No need to defer the dispatch, do it now
+ this->DispatchUnmanagedInBandEvent();
+
+ goto LDone;
+ }
+ UNREACHABLE();
+ }
+
+ case Reaction::cFirstChanceHijackStarted:
+ {
+ // determine the logical event we are handling, if any
+ CordbUnmanagedEvent* pUnmanagedEvent = NULL;
+ if(pUnmanagedThread->HasIBEvent())
+ {
+ pUnmanagedEvent = pUnmanagedThread->IBEvent();
+ }
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: IB hijack starting, ue=0x%p\n", pUnmanagedEvent));
+
+ // fetch the LS memory set up for this hijack
+ REMOTE_PTR pDebuggerWord = NULL;
+ DebuggerIPCFirstChanceData fcd;
+ pUnmanagedThread->GetEEDebuggerWord(&pDebuggerWord);
+ SafeReadStruct(PTR_TO_CORDB_ADDRESS(pDebuggerWord), &fcd);
+
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: old fcd DebugCounter=0x%x\n", fcd.debugCounter));
+
+ // determine what action the LS should take
+ if(pUnmanagedThread->IsBlockingForSync())
+ {
+ // there should be an event we hijacked in this case
+ _ASSERTE(pUnmanagedEvent != NULL);
+
+ // block that event
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: blocking\n"));
+ fcd.action = HIJACK_ACTION_WAIT;
+ fcd.debugCounter = 0x2;
+ SafeWriteStruct(PTR_TO_CORDB_ADDRESS(pDebuggerWord), &fcd);
+ }
+ else
+ {
+ // we don't need to block. We want the vectored handler to just exit
+ // as if it wasn't there
+ _ASSERTE(fcd.action == HIJACK_ACTION_EXIT_UNHANDLED);
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: not blocking\n"));
+ }
+
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: continuing from flare\n"));
+ pW32EventThread->ForceDbgContinue(this, pUnmanagedThread, DBG_CONTINUE, false);
+ goto LDone;
+ }
+
+ case Reaction::cInbandHijackComplete:
+ {
+ // We now execute the hijack worker even when not actually hijacked
+ // so can't assert this
+ //_ASSERTE(pUnmanagedThread->IsFirstChanceHijacked());
+
+ // we should not be stepping at the end of hijacks
+ _ASSERTE(!pUnmanagedThread->IsSSFlagHidden());
+ _ASSERTE(!pUnmanagedThread->IsSSFlagNeeded());
+
+ // if we were hijacked then clean up
+ if(pUnmanagedThread->IsFirstChanceHijacked())
+ {
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: hijack complete will restore context...\n"));
+ DT_CONTEXT tempContext = { 0 };
+ tempContext.ContextFlags = DT_CONTEXT_FULL;
+ HRESULT hr = pUnmanagedThread->GetThreadContext(&tempContext);
+ _ASSERTE(SUCCEEDED(hr));
+
+ // The sync hijack returns normally but the m2uHandoff hijack needs to have the IP
+ // deliberately restored
+ if(!pUnmanagedThread->IsBlockingForSync())
+ {
+ // restore the context to the current un-hijacked context
+ BOOL succ = DbiSetThreadContext(pUnmanagedThread->m_handle, &tempContext);
+ _ASSERTE(succ);
+
+ // Because hijacks don't return normally they might have pushed handlers without poping them
+ // back off. To take care of that we explicitly restore the old SEH chain.
+ #ifdef DBG_TARGET_X86
+ hr = pUnmanagedThread->RestoreLeafSeh();
+ _ASSERTE(SUCCEEDED(hr));
+ #endif
+ }
+ else
+ {
+ _ASSERTE(pUnmanagedThread->HasIBEvent());
+ CordbUnmanagedEvent* pUnmanagedEvent = pUnmanagedThread->IBEvent();
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: IB hijack completing, continuing unhijacked ue=0x%p\n", pUnmanagedEvent));
+ _ASSERTE(pUnmanagedEvent->IsEventContinuedHijacked());
+ _ASSERTE(pUnmanagedEvent->IsDispatched());
+ _ASSERTE(pUnmanagedEvent->IsEventUserContinued());
+ _ASSERTE(!pUnmanagedEvent->IsEventContinuedUnhijacked());
+ pUnmanagedEvent->SetState(CUES_EventContinuedUnhijacked);
+
+ // fetch the LS memory set up for this hijack
+ REMOTE_PTR pDebuggerWord = NULL;
+ DebuggerIPCFirstChanceData fcd;
+ pUnmanagedThread->GetEEDebuggerWord(&pDebuggerWord);
+ SafeReadStruct(PTR_TO_CORDB_ADDRESS(pDebuggerWord), &fcd);
+
+ LOG((LF_CORDB, LL_INFO10000, "W32ET::W32EL: pDebuggerWord is 0x%p\n", pDebuggerWord));
+
+ //set the correct continuation action based upon the user's selection
+ if(pUnmanagedEvent->IsExceptionCleared())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "W32ET::W32EL: exception cleared\n"));
+ fcd.action = HIJACK_ACTION_EXIT_HANDLED;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "W32ET::W32EL: exception not cleared\n"));
+ fcd.action = HIJACK_ACTION_EXIT_UNHANDLED;
+ }
+
+ //
+ // LS context is restored here so that execution continues from next instruction that caused the hijack.
+ // We shouldn't always restore the LS context though.
+ // Consider the following case where this can cause issues:
+ // Debuggee process hits an exception and calls KERNELBASE!RaiseException, debugger gets the notification and
+ // prepares for first-chance hijack. Debugger(DBI) saves the current thread context (see SetupFirstChanceHijackForSync) which is restored
+ // later below (see SafeWriteThreadContext call) when the process is in VEH (CLRVectoredExceptionHandlerShim->FirstChanceSuspendHijackWorker).
+ // The thread context that got saved(by SetupFirstChanceHijackForSync) was for when the thread was executing RaiseException and when
+ // this context gets restored in VEH, the thread resumes after the exception handler with a context that is not same as one with which
+ // it entered. This inconsistency can lead to bad execution code-paths or even a debuggee crash.
+ //
+ // Example case where we should definitely update the LS context:
+ // After a DbgBreakPoint call, IP gets updated to point to the instruction after int 3 and this is the context saved by debugger.
+ // The IP in context passed to VEH still points to int 3 though and if we don't update the LS context in VEH, the breakpoint
+ // instruction will get executed again.
+ //
+ // Here's a list of cases when we update the LS context:
+ // * we know that context was explicitly updated during this hijack, OR
+ // * if single-stepping flag was set on it originally, OR
+ // * if this was a breakpoint event
+ // Note that above list is a heuristic and it is possible that we need to add more such cases in future.
+ //
+ BOOL isBreakPointEvent = (pUnmanagedEvent->m_currentDebugEvent.dwDebugEventCode == EXCEPTION_DEBUG_EVENT &&
+ pUnmanagedEvent->m_currentDebugEvent.u.Exception.ExceptionRecord.ExceptionCode == STATUS_BREAKPOINT);
+ if (pUnmanagedThread->IsContextSet() || IsSSFlagEnabled(&tempContext) || isBreakPointEvent)
+ {
+ _ASSERTE(fcd.pLeftSideContext != NULL);
+ LOG((LF_CORDB, LL_INFO10000, "W32ET::W32EL: updating LS context at 0x%p\n", fcd.pLeftSideContext));
+ // write the new context over the old one on the LS
+ SafeWriteThreadContext(fcd.pLeftSideContext, &tempContext);
+ }
+
+ // Write the new Fcd data to the LS
+ fcd.debugCounter = 0x1;
+ SafeWriteStruct(PTR_TO_CORDB_ADDRESS(pDebuggerWord), &fcd);
+
+ fcd.debugCounter = 0;
+ SafeReadStruct(PTR_TO_CORDB_ADDRESS(pDebuggerWord), &fcd);
+ _ASSERTE(fcd.debugCounter == 1);
+
+ DequeueUnmanagedEvent(pUnmanagedThread);
+ }
+
+ _ASSERTE(m_cFirstChanceHijackedThreads > 0);
+ m_cFirstChanceHijackedThreads--;
+ if(m_cFirstChanceHijackedThreads == 0)
+ {
+ m_state &= ~PS_HIJACKS_IN_PLACE;
+ }
+
+ pUnmanagedThread->ClearState(CUTS_FirstChanceHijacked);
+ pUnmanagedThread->ClearState(CUTS_BlockingForSync);
+
+ // if the user set the context it either was already applied (m2uHandoff hijack)
+ // or is about to be applied when the hijack returns (sync hijack).
+ // There may still a small window where it won't appear accurate that
+ // we just have to live with
+ pUnmanagedThread->ClearState(CUTS_HasContextSet);
+ }
+
+ pW32EventThread->ForceDbgContinue(this, pUnmanagedThread, DBG_CONTINUE, false);
+
+ // We've handled this event. Skip further processing.
+ goto LDone;
+ }
+
+ case Reaction::cBreakpointRequiringHijack:
+ {
+ HRESULT hr = pUnmanagedThread->SetupFirstChanceHijack(EHijackReason::kM2UHandoff, &(pEvent->u.Exception.ExceptionRecord));
+ _ASSERTE(SUCCEEDED(hr));
+ pW32EventThread->ForceDbgContinue(this, pUnmanagedThread, DBG_CONTINUE, false);
+ goto LDone;
+ }
+
+ case Reaction::cInbandExceptionRetrigger:
+ {
+ // this should be unused now
+ _ASSERTE(FALSE);
+ _ASSERTE(pUnmanagedThread->HasIBEvent());
+ CordbUnmanagedEvent* pUnmanagedEvent = pUnmanagedThread->IBEvent();
+ _ASSERTE(pUnmanagedEvent->IsIBEvent());
+ _ASSERTE(pUnmanagedEvent->IsEventContinuedUnhijacked());
+ _ASSERTE(pUnmanagedEvent->IsDispatched());
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: IB event completing, continuing ue=0x%p\n", pUnmanagedEvent));
+
+ DequeueUnmanagedEvent(pUnmanagedThread);
+ // If this event came from RaiseException then flush the context to ensure we won't use it until we re-enter
+ if(pUnmanagedEvent->m_owner->IsRaiseExceptionHijacked())
+ {
+ pUnmanagedEvent->m_owner->RestoreFromRaiseExceptionHijack();
+ pUnmanagedEvent->m_owner->ClearRaiseExceptionEntryContext();
+ }
+ else // otherwise we should have been stepping
+ {
+ pUnmanagedThread->EndStepping();
+ }
+ pW32EventThread->ForceDbgContinue(this, pUnmanagedThread,
+ pUnmanagedEvent->IsExceptionCleared() ? DBG_CONTINUE : DBG_EXCEPTION_NOT_HANDLED, false);
+
+ // We've handled this event. Skip further processing.
+ goto LDone;
+ }
+
+ case Reaction::cOOB:
+ {
+ // Don't care if this thread claimed to be suspended or not. Dispatch event anyways. After all,
+ // OOB events can come at *any* time.
+
+ // This thread may be suspended. We don't care.
+ this->m_DbgSupport.m_TotalOOB++;
+
+ // Not an inband event. This includes ALL non-exception events (including EXIT_THREAD) as
+ // well as any exception that can't be hijacked (ex, an exception on the helper thread).
+
+ // If this is an exit thread or exit process event, then we need to mark the unmanaged thread as
+ // exited for later.
+ if ((pEvent->dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) ||
+ (pEvent->dwDebugEventCode == EXIT_THREAD_DEBUG_EVENT))
+ {
+ pUnmanagedThread->SetState(CUTS_Deleted);
+ }
+
+ // If we get an exit process or exit thread event on the helper thread, then we know we're loosing
+ // the Left Side, so go ahead and remember that the helper thread has died.
+ if (this->IsHelperThreadWorked(pUnmanagedThread->GetOSTid()))
+ {
+ if ((pEvent->dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) ||
+ (pEvent->dwDebugEventCode == EXIT_THREAD_DEBUG_EVENT))
+ {
+ this->m_helperThreadDead = true;
+ }
+ }
+
+ // Queue the current out-of-band event.
+ this->QueueOOBUnmanagedEvent(pUnmanagedThread, pEvent);
+
+ // Go ahead and dispatch the event if its the first one.
+ if (this->m_outOfBandEventQueue == pUnmanagedThread->OOBEvent())
+ {
+ // Set this to true to indicate to Continue() that we're in the unamnaged callback.
+ CordbUnmanagedEvent * pUnmanagedEvent = pUnmanagedThread->OOBEvent();
+
+ this->m_dispatchingOOBEvent = true;
+
+ pUnmanagedEvent->SetState(CUES_Dispatched);
+
+ this->Unlock();
+
+ // Handler should have been registered by now.
+ _ASSERTE(this->m_cordb->m_unmanagedCallback != NULL);
+
+ // Call the callback with fIsOutOfBand = TRUE.
+ {
+ PUBLIC_WIN32_CALLBACK_IN_THIS_SCOPE(this, pEvent, TRUE);
+ this->m_cordb->m_unmanagedCallback->DebugEvent(const_cast<DEBUG_EVENT*> (pEvent), TRUE);
+ }
+
+ this->Lock();
+
+ // If m_dispatchingOOBEvent is false, that means that the user called Continue() from within
+ // the callback. We know that we can go ahead and continue the process now.
+ if (this->m_dispatchingOOBEvent == false)
+ {
+ // Note: this call will dispatch more OOB events if necessary.
+ pW32EventThread->UnmanagedContinue(this, cOobUMContinue);
+ }
+ else
+ {
+ // We're not dispatching anymore, so set this back to false.
+ this->m_dispatchingOOBEvent = false;
+ }
+ }
+
+ // We've handled this event. Skip further processing.
+ goto LDone;
+ }
+ } // end Switch on Reaction
+
+ UNREACHABLE();
+
+LDone:
+ // Process Lock implicitly released by holder.
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "W32ET::W32EL: done processing event.\n");
+
+ return;
+}
+
+//
+// Returns true if the exception is a flare from the left side, false otherwise.
+//
+bool CordbProcess::ExceptionIsFlare(DWORD exceptionCode, const void *exceptionAddress)
+{
+ _ASSERTE(m_runtimeOffsetsInitialized);
+
+ // Can't have a flare if the left side isn't initialized
+ if (m_initialized)
+ {
+ DebuggerIPCRuntimeOffsets *pRO = &m_runtimeOffsets;
+
+ // All flares are breakpoints...
+ if (exceptionCode == STATUS_BREAKPOINT)
+ {
+ // Does the breakpoint address match a flare address?
+ if ((exceptionAddress == pRO->m_signalHijackStartedBPAddr) ||
+ (exceptionAddress == pRO->m_excepForRuntimeHandoffStartBPAddr) ||
+ (exceptionAddress == pRO->m_excepForRuntimeHandoffCompleteBPAddr) ||
+ (exceptionAddress == pRO->m_signalHijackCompleteBPAddr) ||
+ (exceptionAddress == pRO->m_excepNotForRuntimeBPAddr) ||
+ (exceptionAddress == pRO->m_notifyRSOfSyncCompleteBPAddr))
+ return true;
+ }
+ }
+
+ return false;
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+// Allocate a buffer in the target and copy data into it.
+//
+// Arguments:
+// pDomain - an appdomain associated with the allocation request.
+// bufferSize - size of the buffer in bytes
+// bufferFrom - local buffer of data (bufferSize bytes) to copy data from.
+// ppRes - address into target of allocated buffer
+//
+// Returns:
+// S_OK on success, else error.
+HRESULT CordbProcess::GetAndWriteRemoteBuffer(CordbAppDomain *pDomain, unsigned int bufferSize, const void *bufferFrom, void **ppRes)
+{
+ _ASSERTE(ppRes != NULL);
+ *ppRes = NULL;
+
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ TargetBuffer tbTarget = GetRemoteBuffer(bufferSize); // throws
+ SafeWriteBuffer(tbTarget, (const BYTE*) bufferFrom); // throws
+
+ // Succeeded.
+ *ppRes = CORDB_ADDRESS_TO_PTR(tbTarget.pAddress);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+
+//
+// Checks to see if the given second chance exception event actually signifies the death of the process due to a second
+// stack overflow special case.
+//
+// There are strange cases with stack overflow exceptions. If a nieve application catches a stack overflow exception and
+// handles it, without resetting the guard page, then the app will get an AV when it overflows the stack a second time. We
+// will get the first chance AV, but when we continue from it the OS won't run any SEH handlers, so our FCH won't
+// actually work. Instead, we'll get the AV back on second chance right away.
+//
+bool CordbProcess::IsSpecialStackOverflowCase(CordbUnmanagedThread *pUThread, const DEBUG_EVENT *pEvent)
+{
+ _ASSERTE(pEvent->dwDebugEventCode == EXCEPTION_DEBUG_EVENT);
+ _ASSERTE(pEvent->u.Exception.dwFirstChance == 0);
+
+ // If this is not an AV, it can't be our special case.
+ if (pEvent->u.Exception.ExceptionRecord.ExceptionCode != STATUS_ACCESS_VIOLATION)
+ return false;
+
+ // If the thread isn't already first chance hijacked, it can't be our special case.
+ if (!pUThread->IsFirstChanceHijacked())
+ return false;
+
+ // The first chance hijack didn't take, so we're not FCH anymore and we're not waiting for an answer
+ // anymore... Note: by leaving this thread completely unhijacked, we'll report its true context, which is correct.
+ pUThread->ClearState(CUTS_FirstChanceHijacked);
+
+ // The process is techincally dead as a door nail here, so we'll mark that the helper thread is dead so our managed
+ // API bails nicely.
+ m_helperThreadDead = true;
+
+ // Remember we're in our special case.
+ pUThread->SetState(CUTS_HasSpecialStackOverflowCase);
+
+ // Now, remember the second chance AV event in the second IB event slot for this thread and add it to the end of the
+ // IB event queue.
+ QueueUnmanagedEvent(pUThread, pEvent);
+
+ // Note: returning true will ensure that the queued first chance AV for this thread is dispatched.
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Longhorn broke ContinueDebugEvent.
+// In previous OS releases, DBG_CONTINUE would continue a non-continuable exception.
+// In longhorn, we need to pass the DBG_FORCE_CONTINUE flag to do that.
+// Note that all CLR exceptions are non-continuable.
+// Now instead of DBG_CONTINUE, we need to pass DBG_FORCE_CONTINUE.
+//-----------------------------------------------------------------------------
+
+// Currently we don't have headers for the longhorn winnt.h. So we need to privately declare
+// this here. We have a check such that if we do get headers, the value won't change underneath us.
+#define MY_DBG_FORCE_CONTINUE ((DWORD )0x00010003L)
+#ifndef DBG_FORCE_CONTINUE
+#define DBG_FORCE_CONTINUE MY_DBG_FORCE_CONTINUE
+#else
+static_assert_no_msg(DBG_FORCE_CONTINUE == MY_DBG_FORCE_CONTINUE);
+#endif
+
+DWORD GetDbgContinueFlag()
+{
+ // Currently, default to not using the new DBG_FORCE_CONTINUE flag.
+ static ConfigDWORD fNoFlagKey;
+ bool fNoFlag = fNoFlagKey.val(CLRConfig::UNSUPPORTED_DbgNoForceContinue) != 0;
+
+
+ if (!fNoFlag)
+ {
+ return DBG_FORCE_CONTINUE;
+ }
+ else
+ {
+ return DBG_CONTINUE;
+ }
+}
+
+
+// Some Interop bugs involve threads that land at a crazy IP. Since we're interop-debugging, we can't
+// attach a debugger to the LS. So we have some debug mode where we enable the SS flag and thus
+// produce a trace of where a thread is going.
+#ifdef _DEBUG
+void EnableDebugTrace(CordbUnmanagedThread *ut)
+{
+ // To enable, attach w/ a debugger and either set fTrace==true, or setip.
+ static bool fTrace = false;
+ if (!fTrace)
+ return;
+
+ // Give us a nop so that we can setip in the optimized case.
+#ifdef _TARGET_X86_
+ __asm {
+ nop
+ }
+#endif
+
+ fTrace = true;
+ CordbProcess *pProcess = ut->GetProcess();
+
+ // Get the context
+ HRESULT hr = S_OK;
+ DT_CONTEXT context;
+ context.ContextFlags = DT_CONTEXT_FULL;
+
+
+ hr = pProcess->GetThreadContext((DWORD) ut->m_id, sizeof(context), (BYTE*)&context);
+ if (FAILED(hr))
+ return;
+
+ // If the flag is already set, then don't set it again - that will just get confusing.
+ if (IsSSFlagEnabled(&context))
+ {
+ return;
+ }
+ _ASSERTE(CORDbgGetIP(&context) != 0);
+ SetSSFlag(&context);
+
+ // If SS flag not set, enable it. And remeber that it's us so we know how to handle
+ // it when we get the debug event.
+ hr = pProcess->SetThreadContext((DWORD)ut->m_id, sizeof(context), (BYTE*)&context);
+ ut->SetState(CUTS_DEBUG_SingleStep);
+}
+#endif // _DEBUG
+
+//-----------------------------------------------------------------------------
+// DoDbgContinue
+//
+// Continues from a specific Win32 DEBUG_EVENT.
+//
+// Arguments:
+// pProcess - The process to continue.
+// pUnmanagedEvent - The event to continue.
+//
+//-----------------------------------------------------------------------------
+void CordbWin32EventThread::DoDbgContinue(CordbProcess *pProcess,
+ CordbUnmanagedEvent *pUnmanagedEvent)
+{
+ _ASSERTE(pProcess->ThreadHoldsProcessLock());
+ _ASSERTE(IsWin32EventThread());
+ _ASSERTE(pUnmanagedEvent != NULL);
+ _ASSERTE(!pUnmanagedEvent->IsEventContinuedUnhijacked());
+
+ STRESS_LOG3(LF_CORDB, LL_INFO1000,
+ "W32ET::DDC: continue with ue=0x%p, thread=0x%p, tid=0x%x\n",
+ pUnmanagedEvent,
+ pUnmanagedEvent->m_owner,
+ pUnmanagedEvent->m_owner->m_id);
+
+#ifdef _DEBUG
+ EnableDebugTrace(pUnmanagedEvent->m_owner);
+#endif
+
+
+ if (pUnmanagedEvent->IsEventContinuedHijacked())
+ {
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::DDC: Skiping DoDbgContinue because event was already"
+ " continued hijacked, ue=0x%p\n", pUnmanagedEvent));
+ return;
+ }
+
+ BOOL threadIsHijacked = (pUnmanagedEvent->m_owner->IsFirstChanceHijacked() ||
+ pUnmanagedEvent->m_owner->IsGenericHijacked());
+
+ BOOL eventIsIB = (pUnmanagedEvent->m_owner->HasIBEvent() &&
+ pUnmanagedEvent->m_owner->IBEvent() == pUnmanagedEvent);
+
+ _ASSERTE((DWORD) pProcess->m_id == pUnmanagedEvent->m_currentDebugEvent.dwProcessId);
+ _ASSERTE(pProcess->m_state & CordbProcess::PS_WIN32_STOPPED);
+
+ DWORD dwContType;
+ if(eventIsIB)
+ {
+ // 3 cases here...
+ // event was already hijacked
+ if(threadIsHijacked)
+ {
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::DDC: Continuing IB, already hijacked, ue=0x%p\n", pUnmanagedEvent));
+ pUnmanagedEvent->SetState(CUES_EventContinuedHijacked);
+ dwContType = !pUnmanagedEvent->m_owner->IsBlockingForSync() ? GetDbgContinueFlag() : DBG_EXCEPTION_NOT_HANDLED;
+ }
+ // event was not hijacked but has been dispatched
+ else if(!threadIsHijacked && pUnmanagedEvent->IsDispatched())
+ {
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::DDC: Continuing IB, not hijacked, ue=0x%p\n", pUnmanagedEvent));
+ _ASSERTE(pUnmanagedEvent->IsDispatched());
+ _ASSERTE(pUnmanagedEvent->IsEventUserContinued());
+ _ASSERTE(!pUnmanagedEvent->IsEventContinuedUnhijacked());
+ pUnmanagedEvent->SetState(CUES_EventContinuedUnhijacked);
+ dwContType = pUnmanagedEvent->IsExceptionCleared() ? GetDbgContinueFlag() : DBG_EXCEPTION_NOT_HANDLED;
+
+ // The event was never hijacked and so will never need to retrigger, get rid
+ // of it right now. If it had been hijacked then we would dequeue it either after the
+ // hijack complete flare or one instruction after that when it has had a chance to retrigger
+ pProcess->DequeueUnmanagedEvent(pUnmanagedEvent->m_owner);
+ }
+ // event was not hijacked nor dispatched
+ else // if(!threadIsHijacked && !pUnmanagedEvent->IsDispatched())
+ {
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::DDC: Continuing IB, now hijacked, ue=0x%p\n", pUnmanagedEvent));
+ HRESULT hr = pProcess->HijackIBEvent(pUnmanagedEvent);
+ _ASSERTE(SUCCEEDED(hr));
+ pUnmanagedEvent->SetState(CUES_EventContinuedHijacked);
+ dwContType = !pUnmanagedEvent->m_owner->IsBlockingForSync() ? GetDbgContinueFlag() : DBG_EXCEPTION_NOT_HANDLED;
+ }
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::DDC: Continuing OB, ue=0x%p\n", pUnmanagedEvent));
+ // we might actually be hijacked here, but if we are it should be for a previous IB event
+ // we just mark all OB events as continued unhijacked
+ pUnmanagedEvent->SetState(CUES_EventContinuedUnhijacked);
+ dwContType = pUnmanagedEvent->IsExceptionCleared() ? GetDbgContinueFlag() : DBG_EXCEPTION_NOT_HANDLED;
+ }
+
+ // If the exception is marked as unclearable, then make sure the continue type is correct and force the process
+ // to terminate.
+ if (pUnmanagedEvent->IsExceptionUnclearable())
+ {
+ TerminateProcess(pProcess->UnsafeGetProcessHandle(), pUnmanagedEvent->m_currentDebugEvent.u.Exception.ExceptionRecord.ExceptionCode);
+ dwContType = DBG_EXCEPTION_NOT_HANDLED;
+ }
+
+ // If we're continuing from the loader-bp, then send the managed attach here.
+ // (Note this will only be set if the runtime was loaded when we first tried to attach).
+ // We assume that the loader-bp is the 1st BP exception. This is naive,
+ // since it's not 100% accurate (someone could CreateThread w/ a threadproc of DebugBreak).
+ // But it's the best we can do.
+ // Note that it's critical we do this BEFORE continuing the process. If this is mixed-mode, we've already
+ // told VS about this breakpoint, and so it's set the attach-complete event. As soon as we continue this debug
+ // event the process can start moving again, so the CLR needs to know to wait for a managed attach.
+ DWORD dwEventCode = pUnmanagedEvent->m_currentDebugEvent.dwDebugEventCode;
+ if (dwEventCode == EXCEPTION_DEBUG_EVENT)
+ {
+ EXCEPTION_DEBUG_INFO * pDebugInfo = &pUnmanagedEvent->m_currentDebugEvent.u.Exception;
+ if (pDebugInfo->dwFirstChance && pDebugInfo->ExceptionRecord.ExceptionCode == STATUS_BREAKPOINT)
+ {
+ HRESULT hrIgnore = S_OK;
+ EX_TRY
+ {
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::DDC: Continuing from LdrBp, doing managed attach.\n"));
+ pProcess->QueueManagedAttachIfNeededWorker();
+ }
+ EX_CATCH_HRESULT(hrIgnore);
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hrIgnore));
+ }
+ }
+
+ STRESS_LOG4(LF_CORDB, LL_INFO1000,
+ "W32ET::DDC: calling ContinueDebugEvent(0x%x, 0x%x, 0x%x), process state=0x%x\n",
+ pProcess->m_id, pUnmanagedEvent->m_owner->m_id, dwContType, pProcess->m_state);
+
+ // Actually continue the debug event
+ pProcess->m_state &= ~CordbProcess::PS_WIN32_STOPPED;
+ BOOL fSuccess = m_pNativePipeline->ContinueDebugEvent((DWORD)pProcess->m_id, (DWORD)pUnmanagedEvent->m_owner->m_id, dwContType);
+
+ // ContinueDebugEvent may 'fail' if we force kill the debuggee while stopped at the exit-process event.
+ if (!fSuccess && (dwEventCode != EXIT_PROCESS_DEBUG_EVENT))
+ {
+ _ASSERTE(!"ContinueDebugEvent failed!");
+ CORDBSetUnrecoverableError(pProcess, HRESULT_FROM_GetLastError(), 0);
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "W32ET::DDC: Last error after ContinueDebugEvent is %d\n", GetLastError());
+ }
+
+ // If this thread is marked for deletion (exit thread or exit process event on it), then we need to delete the
+ // unmanaged thread object.
+ if ((dwEventCode == EXIT_PROCESS_DEBUG_EVENT) || (dwEventCode == EXIT_THREAD_DEBUG_EVENT))
+ {
+ CordbUnmanagedThread * pUnmanagedThread = pUnmanagedEvent->m_owner;
+ _ASSERTE(pUnmanagedThread->IsDeleted());
+
+
+ // Thread may have a hijacked inband event on it. Thus it's actually running free from the OS perspective,
+ // and fair game to be terminated. In that case, we need to auto-dequeue the event.
+ // This will just prevent the RS from making the underlying call to ContinueDebugEvent on this thread
+ // for the inband event. Since we've already lost the thread, that's actually exactly what we want.
+ if (pUnmanagedThread->HasIBEvent())
+ {
+ pProcess->DequeueUnmanagedEvent(pUnmanagedThread);
+ }
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "Removing thread 0x%x (%d) from process list\n", pUnmanagedThread->m_id);
+ pProcess->m_unmanagedThreads.RemoveBase((ULONG_PTR)pUnmanagedThread->m_id);
+ }
+
+
+ // If we just continued from an exit process event, then its time to do the exit processing.
+ if (dwEventCode == EXIT_PROCESS_DEBUG_EVENT)
+ {
+ pProcess->Unlock();
+ ExitProcess(false); // not detach case
+ pProcess->Lock();
+ }
+
+}
+
+//---------------------------------------------------------------------------------------
+//
+// ForceDbgContinue continues from the last Win32 DEBUG_EVENT on the given thread, no matter what it was.
+//
+// Arguments:
+// pProcess - process object to continue
+// pUnmanagedThread - unmanaged thread object (maybe null if we're doing a raw cotninue)
+// contType - continuation status (DBG_CONTINUE or DBG_EXCEPTION_NOT_HANDLED)
+// fContinueProcess - do we resume hijacks?
+//
+void CordbWin32EventThread::ForceDbgContinue(CordbProcess *pProcess, CordbUnmanagedThread *pUnmanagedThread, DWORD contType,
+ bool fContinueProcess)
+{
+ _ASSERTE(pProcess->ThreadHoldsProcessLock());
+ _ASSERTE(pUnmanagedThread != NULL);
+ STRESS_LOG4(LF_CORDB, LL_INFO1000,
+ "W32ET::FDC: force continue with 0x%x (%s), contProcess=%d, tid=0x%x\n",
+ contType,
+ (contType == DBG_CONTINUE) ? "DBG_CONTINUE" : "DBG_EXCEPTION_NOT_HANDLED",
+ fContinueProcess,
+ pUnmanagedThread->m_id);
+
+ if (fContinueProcess)
+ {
+ pProcess->ResumeHijackedThreads();
+ }
+
+ if (contType == DBG_CONTINUE)
+ {
+ contType = GetDbgContinueFlag();
+ }
+
+ _ASSERTE(pProcess->m_state & CordbProcess::PS_WIN32_STOPPED);
+
+ // Remove the Win32 stopped flag so long as the OOB event queue is empty. We're forcing a continue here, so by
+ // definition this should be the case...
+ _ASSERTE(pProcess->m_outOfBandEventQueue == NULL);
+
+ pProcess->m_state &= ~CordbProcess::PS_WIN32_STOPPED;
+
+ STRESS_LOG4(LF_CORDB, LL_INFO1000, "W32ET::FDC: calling ContinueDebugEvent(0x%x, 0x%x, 0x%x), process state=0x%x\n",
+ pProcess->m_id, pUnmanagedThread->m_id, contType, pProcess->m_state);
+
+
+ #ifdef _DEBUG
+ EnableDebugTrace(pUnmanagedThread);
+ #endif
+ BOOL ret = m_pNativePipeline->ContinueDebugEvent((DWORD)pProcess->m_id, (DWORD)pUnmanagedThread->m_id, contType);
+
+ if (!ret)
+ {
+ // This could in theory fail from Process exit, but that really would only be on the DoDbgContinue path.
+ _ASSERTE(!"ContinueDebugEvent failed #2!");
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "W32ET::DDC: Last error after ContinueDebugEvent is %d\n", GetLastError());
+ }
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+//
+// This is the thread's real thread proc. It simply calls to the
+// thread proc on the given object.
+//
+/*static*/ DWORD WINAPI CordbWin32EventThread::ThreadProc(LPVOID parameter)
+{
+ CordbWin32EventThread* t = (CordbWin32EventThread*) parameter;
+ INTERNAL_THREAD_ENTRY(t);
+ t->ThreadProc();
+ return 0;
+}
+
+
+//
+// Send a CreateProcess event to the Win32 thread to have it create us
+// a new process.
+//
+HRESULT CordbWin32EventThread::SendCreateProcessEvent(
+ MachineInfo machineInfo,
+ LPCWSTR programName,
+ __in_z LPWSTR programArgs,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ PVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation,
+ CorDebugCreateProcessFlags corDebugFlags)
+{
+ HRESULT hr = S_OK;
+
+ LockSendToWin32EventThreadMutex();
+ LOG((LF_CORDB, LL_EVERYTHING, "CordbWin32EventThread::SCPE Called\n"));
+ m_actionData.createData.machineInfo = machineInfo;
+ m_actionData.createData.programName = programName;
+ m_actionData.createData.programArgs = programArgs;
+ m_actionData.createData.lpProcessAttributes = lpProcessAttributes;
+ m_actionData.createData.lpThreadAttributes = lpThreadAttributes;
+ m_actionData.createData.bInheritHandles = bInheritHandles;
+ m_actionData.createData.dwCreationFlags = dwCreationFlags;
+ m_actionData.createData.lpEnvironment = lpEnvironment;
+ m_actionData.createData.lpCurrentDirectory = lpCurrentDirectory;
+ m_actionData.createData.lpStartupInfo = lpStartupInfo;
+ m_actionData.createData.lpProcessInformation = lpProcessInformation;
+ m_actionData.createData.corDebugFlags = corDebugFlags;
+
+ // m_action is set last so that the win32 event thread can inspect
+ // it and take action without actually having to take any
+ // locks. The lock around this here is simply to prevent multiple
+ // threads from making requests at the same time.
+ m_action = W32ETA_CREATE_PROCESS;
+
+ BOOL succ = SetEvent(m_threadControlEvent);
+
+ if (succ)
+ {
+ DWORD ret = WaitForSingleObject(m_actionTakenEvent, INFINITE);
+
+ LOG((LF_CORDB, LL_EVERYTHING, "Process Handle is: %x, m_threadControlEvent is %x\n",
+ (UINT_PTR)m_actionData.createData.lpProcessInformation->hProcess, (UINT_PTR)m_threadControlEvent));
+
+ if (ret == WAIT_OBJECT_0)
+ hr = m_actionResult;
+ else
+ hr = HRESULT_FROM_GetLastError();
+ }
+ else
+ hr = HRESULT_FROM_GetLastError();
+
+ UnlockSendToWin32EventThreadMutex();
+
+ return hr;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Create a process
+//
+// Assumptions:
+// This occurs on the win32 event thread. It is invokved via
+// a message sent from code:CordbWin32EventThread::SendCreateProcessEvent
+//
+// Notes:
+// Create a new process. This is called in the context of the Win32
+// event thread to ensure that if we're Win32 debugging the process
+// that the same thread that waits for debugging events will be the
+// thread that creates the process.
+//
+//---------------------------------------------------------------------------------------
+void CordbWin32EventThread::CreateProcess()
+{
+ m_action = W32ETA_NONE;
+ HRESULT hr = S_OK;
+
+ DWORD dwCreationFlags = m_actionData.createData.dwCreationFlags;
+
+ // If the creation flags has DEBUG_PROCESS in them, then we're
+ // Win32 debugging this process. Otherwise, we have to create
+ // suspended to give us time to setup up our side of the IPC
+ // channel.
+ BOOL fInteropDebugging =
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ (dwCreationFlags & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS));
+#else
+ false; // Interop not supported.
+#endif
+
+ // Have Win32 create the process...
+ hr = m_pNativePipeline->CreateProcessUnderDebugger(
+ m_actionData.createData.machineInfo,
+ m_actionData.createData.programName,
+ m_actionData.createData.programArgs,
+ m_actionData.createData.lpProcessAttributes,
+ m_actionData.createData.lpThreadAttributes,
+ m_actionData.createData.bInheritHandles,
+ dwCreationFlags,
+ m_actionData.createData.lpEnvironment,
+ m_actionData.createData.lpCurrentDirectory,
+ m_actionData.createData.lpStartupInfo,
+ m_actionData.createData.lpProcessInformation);
+
+ if (SUCCEEDED(hr))
+ {
+ // Process ID is filled in after process is succesfully created.
+ DWORD dwProcessId = m_actionData.createData.lpProcessInformation->dwProcessId;
+
+ RSUnsafeExternalSmartPtr<CordbProcess> pProcess;
+ hr = m_pShim->InitializeDataTarget(dwProcessId);
+
+ if (SUCCEEDED(hr))
+ {
+ // To emulate V2 semantics, we pass 0 for the clrInstanceID into
+ // OpenVirtualProcess. This will then connect to the first CLR
+ // loaded.
+ const ULONG64 cFirstClrLoaded = 0;
+ hr = CordbProcess::OpenVirtualProcess(cFirstClrLoaded, m_pShim->GetDataTarget(), NULL, m_cordb, dwProcessId, m_pShim, &pProcess);
+ }
+
+ // Shouldn't happen on a create, only an attach
+ _ASSERTE(hr != CORDBG_E_DEBUGGER_ALREADY_ATTACHED);
+
+ // Remember the process in the global list of processes.
+ if (SUCCEEDED(hr))
+ {
+ EX_TRY
+ {
+ // Mark if we're interop-debugging
+ if (fInteropDebugging)
+ {
+ pProcess->EnableInteropDebugging();
+ }
+
+ m_cordb->AddProcess(pProcess); // will take ref if it succeeds
+ }
+ EX_CATCH_HRESULT(hr);
+ }
+
+ // If we're Win32 attached to this process, then increment the
+ // proper count, otherwise add this process to the wait set
+ // and resume the process's main thread.
+ if (SUCCEEDED(hr))
+ {
+ _ASSERTE(m_pProcess == NULL);
+ m_pProcess.Assign(pProcess);
+ }
+ }
+
+
+ //
+ // Signal the hr to the caller.
+ //
+ m_actionResult = hr;
+ SetEvent(m_actionTakenEvent);
+}
+
+
+//
+// Send a DebugActiveProcess event to the Win32 thread to have it attach to
+// a new process.
+//
+HRESULT CordbWin32EventThread::SendDebugActiveProcessEvent(
+ MachineInfo machineInfo,
+ DWORD pid,
+ bool fWin32Attach,
+ CordbProcess *pProcess)
+{
+ HRESULT hr = S_OK;
+
+ LockSendToWin32EventThreadMutex();
+
+ m_actionData.attachData.machineInfo = machineInfo;
+ m_actionData.attachData.processId = pid;
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ m_actionData.attachData.fWin32Attach = fWin32Attach;
+#endif
+ m_actionData.attachData.pProcess = pProcess;
+
+ // m_action is set last so that the win32 event thread can inspect
+ // it and take action without actually having to take any
+ // locks. The lock around this here is simply to prevent multiple
+ // threads from making requests at the same time.
+ m_action = W32ETA_ATTACH_PROCESS;
+
+ BOOL succ = SetEvent(m_threadControlEvent);
+
+ if (succ)
+ {
+ DWORD ret = WaitForSingleObject(m_actionTakenEvent, INFINITE);
+
+ if (ret == WAIT_OBJECT_0)
+ hr = m_actionResult;
+ else
+ hr = HRESULT_FROM_GetLastError();
+ }
+ else
+ hr = HRESULT_FROM_GetLastError();
+
+ UnlockSendToWin32EventThreadMutex();
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Is the given thread id a helper thread (real or worker?)
+//-----------------------------------------------------------------------------
+bool CordbProcess::IsHelperThreadWorked(DWORD tid)
+{
+ // Check against the id gained by sniffing Thread-Create events.
+ if (tid == this->m_helperThreadId)
+ {
+ return true;
+ }
+
+ // Now check for potential datate in the IPC block. If not there,
+ // then we know it can't be the helper.
+ DebuggerIPCControlBlock * pDCB = this->GetDCB();
+
+ if (pDCB == NULL)
+ {
+ return false;
+ }
+
+ // get the latest information from the LS DCB
+ UpdateRightSideDCB();
+ return
+ (tid == pDCB->m_realHelperThreadId) ||
+ (tid == pDCB->m_temporaryHelperThreadId);
+
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Cleans up the Left Side's DCB after a failed attach attempt.
+//
+// Assumptions:
+// Called when the left-site failed initialization
+//
+// Notes:
+// This can be called multiple times.
+//---------------------------------------------------------------------------------------
+void CordbProcess::CleanupHalfBakedLeftSide()
+{
+ if (GetDCB() != NULL)
+ {
+ EX_TRY
+ {
+ GetDCB()->m_rightSideIsWin32Debugger = false;
+ UpdateLeftSideDCBField(&(GetDCB()->m_rightSideIsWin32Debugger), sizeof(GetDCB()->m_rightSideIsWin32Debugger));
+
+ if (m_pEventChannel != NULL)
+ {
+ m_pEventChannel->Delete();
+ m_pEventChannel = NULL;
+ }
+ }
+ EX_CATCH
+ {
+ _ASSERTE(!"Writing process memory failed, perhaps due to an unexpected disconnection from the target.");
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+ }
+
+ // Close and null out the various handles and events, including our process handle m_handle.
+ CloseIPCHandles();
+
+ m_cordb.Clear();
+
+ // This process object is Dead-On-Arrival, so it doesn't really have anything to neuter.
+ // But for safekeeping, we'll mark it as neutered.
+ UnsafeNeuterDeadObject();
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Attach to an existing process.
+//
+//
+// Assumptions:
+// Called on W32Event Thread, in response to event sent by
+// code:CordbWin32EventThread::SendDebugActiveProcessEvent
+//
+// Notes:
+// Attach to a process. This is called in the context of the Win32
+// event thread to ensure that if we're Win32 debugging the process
+// that the same thread that waits for debugging events will be the
+// thread that attaches the process.
+//
+// @dbgtodo shim: this will be part of the shim
+//---------------------------------------------------------------------------------------
+void CordbWin32EventThread::AttachProcess()
+{
+ _ASSERTE(IsWin32EventThread());
+
+ RSUnsafeExternalSmartPtr<CordbProcess> pProcess;
+
+ m_action = W32ETA_NONE;
+
+ HRESULT hr = S_OK;
+
+ DWORD dwProcessId = m_actionData.attachData.processId;
+ bool fNativeAttachSucceeded = false;
+
+
+ // Always do OS attach to the target.
+ // By this point, the pid should be valid (because OpenProcess above), pending some race where the process just exited.
+ // The OS will enforce that only 1 debugger is attached.
+ // Common failure paths here would be: access denied, double-attach
+ {
+ hr = m_pNativePipeline->DebugActiveProcess(m_actionData.attachData.machineInfo,
+ dwProcessId);
+ if (FAILED(hr))
+ {
+ goto LExit;
+ }
+ fNativeAttachSucceeded = true;
+ }
+
+
+ hr = m_pShim->InitializeDataTarget(m_actionData.attachData.processId);
+ if (FAILED(hr))
+ {
+ goto LExit;
+ }
+
+ // To emulate V2 semantics, we pass 0 for the clrInstanceID into
+ // OpenVirtualProcess. This will then connect to the first CLR
+ // loaded.
+ {
+ const ULONG64 cFirstClrLoaded = 0;
+ hr = CordbProcess::OpenVirtualProcess(cFirstClrLoaded, m_pShim->GetDataTarget(), NULL, m_cordb, dwProcessId, m_pShim, &pProcess);
+ if (FAILED(hr))
+ {
+ goto LExit;
+ }
+ }
+
+ // Remember the process in the global list of processes.
+ // The caller back in code:Cordb::DebugActiveProcess will then get this by fetching it from the list.
+
+ EX_TRY
+ {
+ // Mark interop-debugging
+ if (m_actionData.attachData.IsInteropDebugging())
+ {
+ pProcess->EnableInteropDebugging(); // Throwing
+ }
+
+ m_cordb->AddProcess(pProcess); // will take ref if it succeeds
+
+
+ // Queue fake Attach event for CreateProcess
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(pProcess);
+ m_pShim->BeginQueueFakeAttachEvents();
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ goto LExit;
+ }
+
+ _ASSERTE(m_pProcess == NULL);
+ m_pProcess.Assign(pProcess);
+ pProcess.Clear(); // ownership transfered to m_pProcess
+
+ // Should have succeeded if we got to this point.
+ _ASSERTE(SUCCEEDED(hr));
+
+
+LExit:
+ if (FAILED(hr))
+ {
+ // If we succeed to do a native-attach, but then failed elsewhere, try to native-detach.
+ if (fNativeAttachSucceeded)
+ {
+ m_pNativePipeline->DebugActiveProcessStop(dwProcessId);
+ }
+
+ if (pProcess != NULL)
+ {
+ // Safe to call this even if the process wasn't added.
+ m_cordb->RemoveProcess(pProcess);
+ pProcess->CleanupHalfBakedLeftSide();
+ pProcess.Clear();
+ }
+ m_pProcess.Clear();
+ }
+
+ //
+ // Signal the hr to the caller.
+ //
+ m_actionResult = hr;
+ SetEvent(m_actionTakenEvent);
+}
+
+
+// Note that the actual 'DetachProcess' method is really ExitProcess with CW32ET_UNKNOWN_PROCESS_SLOT ==
+// processSlot
+HRESULT CordbWin32EventThread::SendDetachProcessEvent(CordbProcess *pProcess)
+{
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::SDPE\n"));
+ HRESULT hr = S_OK;
+
+ LockSendToWin32EventThreadMutex();
+
+ m_actionData.detachData.pProcess = pProcess;
+
+ // m_action is set last so that the win32 event thread can inspect it and take action without actually
+ // having to take any locks. The lock around this here is simply to prevent multiple threads from making
+ // requests at the same time.
+ m_action = W32ETA_DETACH;
+
+ BOOL succ = SetEvent(m_threadControlEvent);
+
+ if (succ)
+ {
+ DWORD ret = WaitForSingleObject(m_actionTakenEvent, INFINITE);
+
+ if (ret == WAIT_OBJECT_0)
+ hr = m_actionResult;
+ else
+ hr = HRESULT_FROM_GetLastError();
+ }
+ else
+ hr = HRESULT_FROM_GetLastError();
+
+ UnlockSendToWin32EventThreadMutex();
+
+ return hr;
+}
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+//
+// Send a UnmanagedContinue event to the Win32 thread to have it
+// continue from an unmanged debug event.
+//
+HRESULT CordbWin32EventThread::SendUnmanagedContinue(CordbProcess *pProcess,
+ EUMContinueType eContType)
+{
+ HRESULT hr = S_OK;
+
+ // If this were being called on the win32 EventThread, we'd deadlock.
+ _ASSERTE(!IsWin32EventThread());
+
+ // This can't hold the process lock, b/c we're making a cross-thread call,
+ // and our target will need the process lock.
+ _ASSERTE(!pProcess->ThreadHoldsProcessLock());
+
+ LockSendToWin32EventThreadMutex();
+
+ m_actionData.continueData.process = pProcess;
+ m_actionData.continueData.eContType = eContType;
+
+ // m_action is set last so that the win32 event thread can inspect
+ // it and take action without actually having to take any
+ // locks. The lock around this here is simply to prevent multiple
+ // threads from making requests at the same time.
+ m_action = W32ETA_CONTINUE;
+
+ BOOL succ = SetEvent(m_threadControlEvent);
+
+ if (succ)
+ {
+ DWORD ret = WaitForSingleObject(m_actionTakenEvent, INFINITE);
+
+ if (ret == WAIT_OBJECT_0)
+ hr = m_actionResult;
+ else
+ hr = HRESULT_FROM_GetLastError();
+ }
+ else
+ hr = HRESULT_FROM_GetLastError();
+
+ UnlockSendToWin32EventThreadMutex();
+
+ return hr;
+}
+
+
+//
+// Handle unmanaged continue. Continue an unmanaged debug
+// event. Deferes to UnmanagedContinue. This is called in the context
+// of the Win32 event thread to ensure that if we're Win32 debugging
+// the process that the same thread that waits for debugging events
+// will be the thread that continues the process.
+//
+void CordbWin32EventThread::HandleUnmanagedContinue()
+{
+ _ASSERTE(IsWin32EventThread());
+
+ m_action = W32ETA_NONE;
+ HRESULT hr = S_OK;
+
+ // Continue the process
+ CordbProcess *pProcess = m_actionData.continueData.process;
+
+ // If we lost the process object, we must have exited.
+ if (m_pProcess != NULL)
+ {
+ _ASSERTE(m_pProcess != NULL);
+ _ASSERTE(pProcess == m_pProcess);
+
+ _ASSERTE(!pProcess->ThreadHoldsProcessLock());
+
+ RSSmartPtr<CordbProcess> proc(pProcess);
+ RSLockHolder ch(&pProcess->m_processMutex);
+
+ hr = UnmanagedContinue(pProcess, m_actionData.continueData.eContType);
+ }
+
+ // Signal the hr to the caller.
+ m_actionResult = hr;
+ SetEvent(m_actionTakenEvent);
+}
+
+//
+// Continue an unmanaged debug event. This is called in the context of the Win32 Event thread to ensure that the same
+// thread that waits for debug events will be the thread that continues the process.
+//
+HRESULT CordbWin32EventThread::UnmanagedContinue(CordbProcess *pProcess,
+ EUMContinueType eContType)
+{
+ _ASSERTE(pProcess->ThreadHoldsProcessLock());
+ _ASSERTE(IsWin32EventThread());
+ _ASSERTE(m_pShim != NULL);
+
+ HRESULT hr = S_OK;
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "UM Continue. type=%d\n", eContType);
+
+ if (eContType == cOobUMContinue)
+ {
+ _ASSERTE(pProcess->m_outOfBandEventQueue != NULL);
+
+ // Dequeue the OOB event.
+ CordbUnmanagedEvent *ue = pProcess->m_outOfBandEventQueue;
+ CordbUnmanagedThread *ut = ue->m_owner;
+ pProcess->DequeueOOBUnmanagedEvent(ut);
+
+ // Do a little extra work if that was an OOB exception event...
+ hr = ue->m_owner->FixupAfterOOBException(ue);
+ _ASSERTE(SUCCEEDED(hr));
+
+ // Continue from the event.
+ DoDbgContinue(pProcess, ue);
+
+ // If there are more queued OOB events, dispatch them now.
+ if (pProcess->m_outOfBandEventQueue != NULL)
+ pProcess->DispatchUnmanagedOOBEvent();
+
+ // Note: if we previously skipped letting the entire process go on an IB continue due to a blocking OOB event,
+ // and if the OOB event queue is now empty, then go ahead and let the process continue now...
+ if ((pProcess->m_doRealContinueAfterOOBBlock == true) &&
+ (pProcess->m_outOfBandEventQueue == NULL))
+ goto doRealContinue;
+ }
+ else if (eContType == cInternalUMContinue)
+ {
+ // We're trying to get into a synced state which means we need the process running (potentially
+ // with some threads hijacked) in order to have the helper thread do the sync.
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::UC: internal continue.\n"));
+
+ if (!pProcess->GetSynchronized())
+ {
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::UC: internal continue, !sync'd.\n"));
+ pProcess->ResumeUnmanagedThreads();
+
+ // the event we may need to hijack and continue;
+ CordbUnmanagedEvent* pEvent = pProcess->m_lastQueuedUnmanagedEvent;
+
+ // It is possible to be stopped at either an IB or an OOB event here. We only want to
+ // continue from an IB event here though
+ if(pProcess->m_state & CordbProcess::PS_WIN32_STOPPED && pEvent != NULL &&
+ pEvent->IsEventWaitingForContinue())
+ {
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::UC: internal continue, frozen on IB event.\n"));
+
+ // There should be a uncontinued IB event at the head of the queue
+ _ASSERTE(pEvent->IsIBEvent());
+ _ASSERTE(!pEvent->IsEventContinuedUnhijacked());
+ _ASSERTE(!pEvent->IsEventContinuedHijacked());
+
+ // Ensure that the event is hijacked now (it may not have been before) so that the
+ // thread does not slip forward during the sync process. After that we can safely continue
+ // it.
+ pProcess->HijackIBEvent(pEvent);
+ m_pShim->GetWin32EventThread()->DoDbgContinue(pProcess, pEvent);
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::UC: internal continue, done.\n"));
+ }
+ else
+ {
+ // If we're here, then we know 100% for sure that we've successfully gotten the managed continue event to the
+ // Left Side, so we can stop force hijacking left over in-band events now. Note: if we had hijacked any such
+ // events, they'll be dispatched below since they're properly queued.
+ pProcess->m_specialDeferment = false;
+
+ // We don't actually do any work if there is an outstanding out-of-band event. When we do continue from the
+ // out-of-band event, we'll do this work, too.
+ if (pProcess->m_outOfBandEventQueue != NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::UC: ignoring real continue due to block by out-of-band event(s).\n"));
+
+ _ASSERTE(pProcess->m_doRealContinueAfterOOBBlock == false);
+ pProcess->m_doRealContinueAfterOOBBlock = true;
+ }
+ else
+ {
+doRealContinue:
+ // This is either the Frozen -> Running transition or a
+ // Synced -> Running transition
+ _ASSERTE(pProcess->m_outOfBandEventQueue == NULL);
+
+
+ pProcess->m_doRealContinueAfterOOBBlock = false;
+
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::UC: continuing the process.\n"));
+ // Dispatch any more queued in-band events, or if there are none then just continue the process.
+ //
+ // Note: don't dispatch more events if we've already sent up the ExitProcess event... those events are just
+ // lost.
+ if ((pProcess->HasUndispatchedNativeEvents()) && (pProcess->m_exiting == false))
+ {
+ pProcess->DispatchUnmanagedInBandEvent();
+ }
+ else
+ {
+ // If the unmanaged event queue is empty now, and the process is synchronized, and there are queued
+ // managed events, then go ahead and get more managed events dispatched.
+ //
+ // Note: don't dispatch more events if we've already sent up the ExitProcess event... those events are
+ // just lost.
+ if (pProcess->GetSynchronized() && (!m_pShim->GetManagedEventQueue()->IsEmpty()) && (pProcess->m_exiting == false))
+ {
+ if(pProcess->m_state & CordbProcess::PS_WIN32_STOPPED)
+ {
+ DoDbgContinue(pProcess, pProcess->m_lastDispatchedIBEvent);
+
+ // This if should not be necessary, I am just being extra careful because this
+ // fix is going in late - see issue 818301
+ _ASSERTE(pProcess->m_lastDispatchedIBEvent != NULL);
+ if(pProcess->m_lastDispatchedIBEvent != NULL)
+ {
+ pProcess->m_lastDispatchedIBEvent->m_owner->InternalRelease();
+ pProcess->m_lastDispatchedIBEvent = NULL;
+ }
+ }
+
+ // Now, get more managed events dispatched.
+ pProcess->SetSynchronized(false);
+ pProcess->MarkAllThreadsDirty();
+ m_cordb->ProcessStateChanged();
+ }
+ else
+ {
+ // free all the hijacked threads that hit native debug events
+ pProcess->ResumeHijackedThreads();
+
+ // after continuing the here the process should be running completely
+ // free... no hijacks, no suspended threads, and of course not frozen
+ if(pProcess->m_state & CordbProcess::PS_WIN32_STOPPED)
+ {
+ DoDbgContinue(pProcess, pProcess->m_lastDispatchedIBEvent);
+ // This if should not be necessary, I am just being extra careful because this
+ // fix is going in late - see issue 818301
+ _ASSERTE(pProcess->m_lastDispatchedIBEvent != NULL);
+ if(pProcess->m_lastDispatchedIBEvent != NULL)
+ {
+ pProcess->m_lastDispatchedIBEvent->m_owner->InternalRelease();
+ pProcess->m_lastDispatchedIBEvent = NULL;
+ }
+ }
+ }
+ }
+
+ // Implicit Release on UT
+ }
+ }
+
+ return hr;
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+void ExitProcessWorkItem::Do()
+{
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "ExitProcessWorkItem proc=%p\n", GetProcess());
+
+ // This is being called on the RCET.
+ // That's the thread that dispatches managed events. Since it's calling us now, we know
+ // it can't be dispatching a managed event, and so we don't need to both waiting for it
+
+ {
+ // Get the SG lock here to coordinate against any other continues.
+ RSLockHolder ch(GetProcess()->GetStopGoLock());
+ RSLockHolder ch2(&(GetProcess()->m_processMutex));
+
+ LOG((LF_CORDB, LL_INFO1000,"W32ET::EP: ExitProcess callback\n"));
+
+ // We're synchronized now, so mark the process as such.
+ GetProcess()->SetSynchronized(true);
+ GetProcess()->IncStopCount();
+
+ // By the time we release the SG + Process locks here, the process object has been
+ // marked as exiting + terminated (by the w32et which queued us). Future attemps to
+ // continue should fail, and thus we should remain synchronized.
+ }
+
+
+ // Just to be safe, neuter any children before the exit process callback.
+ {
+ RSLockHolder ch(GetProcess()->GetProcessLock());
+
+ // Release the process.
+ GetProcess()->NeuterChildren();
+ }
+
+ RSSmartPtr<Cordb> pCordb(NULL);
+
+ // There is a race condition here where the debuggee process is killed while we are processing a process
+ // detach. We queue the process exit event for the Win32 event thread before queueing the process detach
+ // event. By the time this function is executed, we may have neutered the CordbProcess already as a
+ // result of code:CordbProcess::Detach. Detect that case here under the SG lock.
+ {
+ RSLockHolder ch(GetProcess()->GetStopGoLock());
+ if (!GetProcess()->IsNeutered())
+ {
+ _ASSERTE(GetProcess()->m_cordb != NULL);
+ pCordb.Assign(GetProcess()->m_cordb);
+ }
+ }
+
+ // Move this into Shim?
+
+ // Invoke the ExitProcess callback. This is very important since the a shell
+ // may rely on it for proper shutdown and may hang if they don't get it.
+ // We don't expect Cordbg to continue from this (we're certainly not going to wait for it).
+ if ((pCordb != NULL) && (pCordb->m_managedCallback != NULL))
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(GetProcess());
+ pCordb->m_managedCallback->ExitProcess(GetProcess());
+ }
+
+ // This CordbProcess object now has no reservations against a client calling ICorDebug::Terminate.
+ // That call may race against the CordbProcess::Neuter below, but since we already neutered the children,
+ // that neuter call will not do anything interesting that will conflict with Terminate.
+
+ LOG((LF_CORDB, LL_INFO1000,"W32ET::EP: returned from ExitProcess callback\n"));
+
+ {
+ RSLockHolder ch(GetProcess()->GetStopGoLock());
+
+ // Release the process.
+ GetProcess()->Neuter();
+ }
+
+ // Our dtor will release the Process object.
+ // This may be the final release on the process.
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Handles process exiting and detach cases
+//
+// Arguments:
+// fDetach - true if detaching, false if process is exiting.
+//
+// Return Value:
+// The type of the next argument in the signature,
+// normalized.
+//
+// Assumptions:
+// On exit, the process has already exited and we detected this by either an EXIT_PROCESS
+// native debug event, or by waiting on the process handle.
+// On detach, the process is stil live.
+//
+// Notes:
+// ExitProcess is called when a process exits or detaches.
+// This does our final cleanup and removes the process from our wait sets.
+// We're either here because we're detaching (fDetach == TRUE), or because the process has really exited,
+// and we're doing shutdown logic.
+//
+//---------------------------------------------------------------------------------------
+void CordbWin32EventThread::ExitProcess(bool fDetach)
+{
+ INTERNAL_API_ENTRY(this);
+
+ // Consider the following when you're modifying this function:
+ // - The OS can kill the debuggee at any time.
+ // - ExitProcess can race with detach.
+
+ LOG((LF_CORDB, LL_INFO1000,"W32ET::EP: begin ExitProcess, detach=%d\n", fDetach));
+
+
+ // For the Mac remote debugging transport, DebugActiveProcessStop() is a nop. The transport will be
+ // shut down later when we neuter the CordbProcess.
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ // @dbgtodo shim: this is a primitive workaround for interop-detach
+ // Eventually, the Debugger owns the detach pipeline, so this won't be necessary.
+ if (fDetach && (m_pProcess != NULL))
+ {
+ HRESULT hr = m_pNativePipeline->DebugActiveProcessStop(m_pProcess->GetPid());
+
+ // We don't expect detach to fail (we check earlier for common conditions that
+ // may cause it to fail)
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+ if( FAILED(hr) )
+ {
+ m_actionResult = hr;
+ SetEvent(m_actionTakenEvent);
+ return;
+ }
+ }
+#endif // !FEATURE_DBGIPC_TRANSPORT_DI
+
+
+ // We don't really care if we're on the Win32 thread or not here. We just want to be sure that
+ // the LS Exit case and the Detach case both occur on the same thread. This makes it much easier
+ // to assert that if we exit while detaching, EP is only called once.
+ // If we ever decide to make the RCET listen on the LS process handle for EP(exit), then we should also
+ // make the EP(detach) handled on the RCET (via DoFavor() ).
+ _ASSERTE(IsWin32EventThread());
+
+ // So either the Exit case or Detach case must happen first.
+ // 1) If Detach first, then LS process is removed from wait set and so EP(Exit) will never happen
+ // because we check wait set after returning from EP(Detach).
+ // 2) If Exit is first, m_pProcess gets set=NULL. EP(detach) will still get called, so explicitly check that.
+ if (fDetach && ((m_pProcess == NULL) || m_pProcess->m_terminated))
+ {
+ // m_terminated is only set after the LS exits.
+ // So the only way (fDetach && m_terminated) is true is if the LS exited while detaching. In that case
+ // we already called EP(exit) and we don't want to call it again for EP(detach). So return here.
+ LOG((LF_CORDB, LL_INFO1000,"W32ET::EP: In EP(detach), but EP(exit) already called. Early failure\n"));
+
+ m_actionResult = CORDBG_E_PROCESS_TERMINATED;
+ SetEvent(m_actionTakenEvent);
+
+ return;
+ }
+
+ // We null m_pProcess at the end here, so
+ // Only way we could get here w/ null process is if we're called twice. We can only be called
+ // by detach or exit. Can't detach twice, can't exit twice, so must have been one of each.
+ // If exit is first, we got removed from the wait set, so 2nd call must be detach and we'd catch
+ // that above. If detach is first, we'd get removed from the wait set and so exit would never happen.
+ _ASSERTE(m_pProcess != NULL);
+ _ASSERTE(!m_pProcess->ThreadHoldsProcessLock());
+
+
+
+ // Mark the process teminated. After this, the RCET will never call FlushQueuedEvents. It will
+ // ignore all events it receives (including a SyncComplete) and the RCET also does not listen
+ // to terminated processes (so ProcessStateChange() won't cause a FQE either).
+ m_pProcess->Terminating(fDetach);
+
+ // Take care of the race where the process exits right after the user calls Continue() from the last
+ // managed event but before the handler has actually returned.
+ //
+ // Also, To get through this lock means that either:
+ // 1. FlushQueuedEvents is not currently executing and no one will call FQE.
+ // 2. FQE is exiting but is in the middle of a callback (so AreDispatchingEvent = true)
+ //
+ m_pProcess->Lock();
+
+ m_pProcess->m_exiting = true;
+
+ if (fDetach)
+ {
+ m_pProcess->SetSynchronized(false);
+ }
+
+ // If we are exiting, we *must* dispatch the ExitProcess callback, but we will delete all the events
+ // in the queue and not bother dispatching anything else. If (and only if) we are currently dispatching
+ // an event, then we will wait while that event is finished before invoking ExitProcess.
+ // (Note that a dispatched event has already been removed from the queue)
+
+ // Remove the process from the global list of processes.
+ m_cordb->RemoveProcess(m_pProcess);
+
+ if (fDetach)
+ {
+ // Signal the hr to the caller.
+ LOG((LF_CORDB, LL_INFO1000,"W32ET::EP: Detach: send result back!\n"));
+
+ m_actionResult = S_OK;
+ SetEvent(m_actionTakenEvent);
+ }
+
+ m_pProcess->Unlock();
+
+ // Delete all queued events
+ m_pProcess->DeleteQueuedEvents();
+
+
+ // If we're detaching, then the Detach already neutered everybody, so nothing here.
+ // If we're exiting, then we still need to neuter things, but we can't do that on this thread,
+ // so we queue it. We also need to dispatch an exit process callback. We'll queue that onto the RCET
+ // and dispatch it inband w/the other callbacks.
+ if (!fDetach)
+ {
+#ifdef FEATURE_PAL
+ // Cleanup the transport pipe and semaphore files that might be left by the target (LS) process.
+ m_pNativePipeline->CleanupTargetProcess();
+#endif
+ ExitProcessWorkItem * pItem = new (nothrow) ExitProcessWorkItem(m_pProcess);
+ if (pItem != NULL)
+ {
+ m_cordb->m_rcEventThread->QueueAsyncWorkItem(pItem);
+ }
+ }
+
+ // This will remove the process from our wait lists (so that we don't send multiple ExitProcess events).
+ m_pProcess.Clear();
+}
+
+
+//
+// Start actually creates and starts the thread.
+//
+HRESULT CordbWin32EventThread::Start()
+{
+ HRESULT hr = S_OK;
+ if (m_threadControlEvent == NULL)
+ return E_INVALIDARG;
+
+ // Create the thread suspended to make sure that m_threadId is set
+ // before CordbWin32EventThread::ThreadProc runs
+ // Stack size = 0x80000 = 512KB
+ m_thread = CreateThread(NULL, 0x80000, &CordbWin32EventThread::ThreadProc,
+ (LPVOID) this, CREATE_SUSPENDED | STACK_SIZE_PARAM_IS_A_RESERVATION, &m_threadId);
+
+ if (m_thread == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ DWORD succ = ResumeThread(m_thread);
+ if (succ == (DWORD)-1)
+ return HRESULT_FROM_GetLastError();
+ return hr;
+}
+
+
+//
+// Stop causes the thread to stop receiving events and exit. It
+// waits for it to exit before returning.
+//
+HRESULT CordbWin32EventThread::Stop()
+{
+ HRESULT hr = S_OK;
+
+ // m_pProcess may be NULL from CordbWin32EventThread::ExitProcess
+
+ // Can't block on W32ET while holding the process-lock since the W32ET may need that to exit.
+ // But since m_pProcess may be null, we can't enforce that.
+
+ if (m_thread != NULL)
+ {
+ LockSendToWin32EventThreadMutex();
+ m_action = W32ETA_NONE;
+ m_run = FALSE;
+
+ SetEvent(m_threadControlEvent);
+ UnlockSendToWin32EventThreadMutex();
+
+ DWORD ret = WaitForSingleObject(m_thread, INFINITE);
+
+ if (ret != WAIT_OBJECT_0)
+ hr = HRESULT_FROM_GetLastError();
+ }
+
+ m_pProcess.Clear();
+ m_cordb.Clear();
+
+ return hr;
+}
+
+
+
+
+
+
+
+
+// Allocate a buffer of cbBuffer bytes in the target.
+//
+// Arguments:
+// cbBuffer - count of bytes for the buffer.
+//
+// Returns:
+// a TargetBuffer describing the new memory region in the target.
+// Throws on error.
+TargetBuffer CordbProcess::GetRemoteBuffer(ULONG cbBuffer)
+{
+ INTERNAL_SYNC_API_ENTRY(this); //
+
+ // Create and initialize the event as synchronous
+ DebuggerIPCEvent event;
+ InitIPCEvent(&event,
+ DB_IPCE_GET_BUFFER,
+ true,
+ VMPTR_AppDomain::NullPtr());
+
+ // Indicate the buffer size wanted
+ event.GetBuffer.bufSize = cbBuffer;
+
+ // Make the request, which is synchronous
+ HRESULT hr = SendIPCEvent(&event, sizeof(event));
+ IfFailThrow(hr);
+ _ASSERTE(event.type == DB_IPCE_GET_BUFFER_RESULT);
+
+ IfFailThrow(event.GetBufferResult.hr);
+
+ // The request succeeded. Return the newly allocated range.
+ return TargetBuffer(event.GetBufferResult.pBuffer, cbBuffer);
+}
+
+/*
+ * This will release a previously allocated left side buffer.
+ */
+HRESULT CordbProcess::ReleaseRemoteBuffer(void **ppBuffer)
+{
+ INTERNAL_SYNC_API_ENTRY(this); //
+
+ _ASSERTE(m_pShim != NULL);
+
+ // Create and initialize the event as synchronous
+ DebuggerIPCEvent event;
+ InitIPCEvent(&event,
+ DB_IPCE_RELEASE_BUFFER,
+ true,
+ VMPTR_AppDomain::NullPtr());
+
+ // Indicate the buffer to release
+ event.ReleaseBuffer.pBuffer = (*ppBuffer);
+
+ // Make the request, which is synchronous
+ HRESULT hr = SendIPCEvent(&event, sizeof(event));
+ TESTANDRETURNHR(hr);
+
+ (*ppBuffer) = NULL;
+
+ // Indicate success
+ return event.ReleaseBufferResult.hr;
+}
+
+HRESULT CordbProcess::SetDesiredNGENCompilerFlags(DWORD dwFlags)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+#if defined(FEATURE_PREJIT)
+ if ((dwFlags != CORDEBUG_JIT_DEFAULT) && (dwFlags != CORDEBUG_JIT_DISABLE_OPTIMIZATION))
+ {
+ return E_INVALIDARG;
+ }
+
+ CordbProcess *pProcess = GetProcess();
+ ATT_REQUIRE_STOPPED_MAY_FAIL(pProcess);
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Left-side checks that this is a valid time to set the Ngen flags.
+ hr = pProcess->GetDAC()->SetNGENCompilerFlags(dwFlags);
+ if (!SUCCEEDED(hr) && GetShim() != NULL)
+ {
+ // Emulate V2 error semantics.
+ hr = GetShim()->FilterSetNgenHresult(hr);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+
+#else // !FEATURE_PREJIT
+ return CORDBG_E_NGEN_NOT_SUPPORTED;
+
+#endif // FEATURE_PREJIT
+}
+
+HRESULT CordbProcess::GetDesiredNGENCompilerFlags(DWORD *pdwFlags )
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pdwFlags, DWORD*);
+ *pdwFlags = 0;
+
+ CordbProcess *pProcess = GetProcess();
+ ATT_REQUIRE_STOPPED_MAY_FAIL(pProcess);
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ hr = pProcess->GetDAC()->GetNGENCompilerFlags(pdwFlags);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Get an ICorDebugReference Value for the GC handle.
+// handle - raw bits for the GC handle.
+// pOutHandle
+//-----------------------------------------------------------------------------
+HRESULT CordbProcess::GetReferenceValueFromGCHandle(
+ UINT_PTR gcHandle,
+ ICorDebugReferenceValue **pOutValue)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+ VALIDATE_POINTER_TO_OBJECT(pOutValue, ICorDebugReferenceValue*);
+
+ *pOutValue = NULL;
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ if (gcHandle == NULL)
+ {
+ ThrowHR(CORDBG_E_BAD_REFERENCE_VALUE);
+ }
+
+ IDacDbiInterface* pDAC = GetProcess()->GetDAC();
+ VMPTR_OBJECTHANDLE vmObjHandle = pDAC->GetVmObjectHandle(gcHandle);
+ if(!pDAC->IsVmObjectHandleValid(vmObjHandle))
+ {
+ ThrowHR(CORDBG_E_BAD_REFERENCE_VALUE);
+ }
+ ULONG appDomainId = pDAC->GetAppDomainIdFromVmObjectHandle(vmObjHandle);
+ VMPTR_AppDomain vmAppDomain = pDAC->GetAppDomainFromId(appDomainId);
+
+ RSLockHolder lockHolder(GetProcessLock());
+ CordbAppDomain * pAppDomain = LookupOrCreateAppDomain(vmAppDomain);
+ lockHolder.Release();
+
+ // Now that we finally have the AppDomain, we can go ahead and get a ReferenceValue
+ // from the ObjectHandle.
+ hr = CordbReferenceValue::BuildFromGCHandle(pAppDomain, vmObjHandle, pOutValue);
+ _ASSERTE(SUCCEEDED(hr) == (*pOutValue != NULL));
+ IfFailThrow(hr);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Return count of outstanding GC handles held by CordbHandleValue objects
+LONG CordbProcess::OutstandingHandles()
+{
+ return m_cOutstandingHandles;
+}
+
+//-----------------------------------------------------------------------------
+// Increment the outstanding handle count for code:CordbProces::OutstandingHandles
+// This is the inverse of code:CordbProces::DecrementOutstandingHandles
+void CordbProcess::IncrementOutstandingHandles()
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ m_cOutstandingHandles++;
+}
+
+//-----------------------------------------------------------------------------
+// Decrement the outstanding handle count for code:CordbProces::OutstandingHandles
+// This is the inverse of code:CordbProces::IncrementOutstandingHandles
+void CordbProcess::DecrementOutstandingHandles()
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ m_cOutstandingHandles--;
+}
+
+
+/*
+ * IsReadyForDetach
+ *
+ * This method encapsulates all logic for deciding if it is ok for a debugger to
+ * detach from the process at this time.
+ *
+ * Parameters: None.
+ *
+ * Returns: S_OK if it is ok to detach, else a specific HRESULT describing why it
+ * is not ok to detach.
+ *
+ */
+HRESULT CordbProcess::IsReadyForDetach()
+{
+ INTERNAL_API_ENTRY(this);
+
+ // Always safe to detach in V3 case.
+ if (m_pShim == NULL)
+ {
+ return S_OK;
+ }
+
+ // If not initialized yet, then there are no detach liabilities.
+ if (!m_initialized)
+ {
+ return S_OK;
+ }
+
+ RSLockHolder lockHolder(&this->m_processMutex);
+
+ //
+ // If there are any outstanding func-evals then fail the detach.
+ //
+ if (OutstandingEvalCount() != 0)
+ {
+ return CORDBG_E_DETACH_FAILED_OUTSTANDING_EVALS;
+ }
+
+ // V2 didn't check outstanding handles (code:CordbProcess::OutstandingHandles)
+ // because it could automatically clean those up on detach.
+
+ //
+ // If there are any outstanding steppers then fail the detach.
+ //
+ if (m_steppers.IsInitialized() && (m_steppers.GetCount() > 0))
+ {
+ return CORDBG_E_DETACH_FAILED_OUTSTANDING_STEPPERS;
+ }
+
+ //
+ // If there are any outstanding breakpoints then fail the detach.
+ //
+ HASHFIND foundAppDomain;
+ CordbAppDomain *pAppDomain = m_appDomains.FindFirst(&foundAppDomain);
+
+ while (pAppDomain != NULL)
+ {
+ if (pAppDomain->m_breakpoints.IsInitialized() && (pAppDomain->m_breakpoints.GetCount() > 0))
+ {
+ return CORDBG_E_DETACH_FAILED_OUTSTANDING_BREAKPOINTS;
+ }
+
+ // Check for any outstanding EnC modules.
+ HASHFIND foundModule;
+ CordbModule * pModule = pAppDomain->m_modules.FindFirst(&foundModule);
+ while (pModule != NULL)
+ {
+ if (pModule->m_EnCCount > 0)
+ {
+ return CORDBG_E_DETACH_FAILED_ON_ENC;
+ }
+ pModule = pAppDomain->m_modules.FindNext(&foundModule);
+ }
+
+
+ pAppDomain = m_appDomains.FindNext(&foundAppDomain);
+ }
+
+ // If we're using the shim, give a chance to early-out if the OS doesn't support detach
+ // so that the user can continue to debug in that case.
+ // Ideally we'd just rely on the failure from DebugActiveProcessStop, but by then it's too late
+ // to recover. This function is our only chance to distinguish between graceful detach failures
+ // and hard detach failures (after which the process object is neutered).
+ if (m_pShim != NULL)
+ {
+#if !defined(FEATURE_CORESYSTEM) // CORESYSTEM TODO
+ HModuleHolder hKernel32;
+ hKernel32 = WszLoadLibrary(W("kernel32"));
+ if (hKernel32 == NULL)
+ return HRESULT_FROM_GetLastError();
+ typedef BOOL (*DebugActiveProcessStopSig) (DWORD);
+ DebugActiveProcessStopSig pDebugActiveProcessStop =
+ reinterpret_cast<DebugActiveProcessStopSig>(GetProcAddress(hKernel32, "DebugActiveProcessStop"));
+ if (pDebugActiveProcessStop == NULL)
+ return COR_E_PLATFORMNOTSUPPORTED;
+#endif
+ }
+
+ return S_OK;
+}
+
+
+/*
+ * Look for any thread which was last seen in the specified AppDomain.
+ * The CordbAppDomain object is about to be neutered due to an AD Unload
+ * So the thread must no longer be considered to be in that domain.
+ * Note that this is a workaround due to the existance of the (possibly incorrect)
+ * cached AppDomain value. Ideally we would remove the cached value entirely
+ * and there would be no need for this.
+ *
+ * @dbgtodo: , appdomain: We should remove CordbThread::m_pAppDomain in the V3 architecture.
+ * If we need the thread's current domain, we should get it accurately with DAC.
+ */
+void CordbProcess::UpdateThreadsForAdUnload(CordbAppDomain * pAppDomain)
+{
+ INTERNAL_API_ENTRY(this);
+
+ // If we're doing an AD unload then we should have already seen the ATTACH
+ // notification for the default domain.
+ //_ASSERTE( m_pDefaultAppDomain != NULL );
+ // @dbgtodo appdomain: fix Default domain invariants with DAC-izing Appdomain work.
+
+ RSLockHolder lockHolder(GetProcessLock());
+
+ CordbThread* t;
+ HASHFIND find;
+
+ // We don't need to prepopulate here (to collect LS state) because we're just updating RS state.
+ for (t = m_userThreads.FindFirst(&find);
+ t != NULL;
+ t = m_userThreads.FindNext(&find))
+ {
+ if( t->GetAppDomain() == pAppDomain )
+ {
+ // This thread cannot actually be in this AppDomain anymore (since it's being
+ // unloaded). Reset it to point to the default AppDomain
+ t->m_pAppDomain = m_pDefaultAppDomain;
+ }
+ }
+}
+
+// CordbProcess::LookupClass
+// Looks up a previously constructed CordbClass instance without creating. May return NULL if the
+// CordbClass instance doesn't exist.
+// Argument: (in) vmDomainFile - pointer to the domainfile for the module
+// (in) mdTypeDef - metadata token for the class
+// Return value: pointer to a previously created CordbClass instance or NULL in none exists
+CordbClass * CordbProcess::LookupClass(ICorDebugAppDomain * pAppDomain, VMPTR_DomainFile vmDomainFile, mdTypeDef classToken)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ if (pAppDomain != NULL)
+ {
+ CordbModule * pModule = ((CordbAppDomain *)pAppDomain)->m_modules.GetBase(VmPtrToCookie(vmDomainFile));
+ if (pModule != NULL)
+ {
+ return pModule->LookupClass(classToken);
+ }
+ }
+ return NULL;
+} // CordbProcess::LookupClass
+
+//---------------------------------------------------------------------------------------
+// Look for a specific module in the process.
+//
+// Arguments:
+// vmDomainFile - non-null module to lookup
+//
+// Returns:
+// a CordbModule object for the given cookie. Object may be from the cache, or created
+// lazily.
+// Never returns null. Throws on error.
+//
+// Notes:
+// A VMPTR_DomainFile has appdomain affinity, but is ultimately scoped to a process.
+// So if we get a raw VMPTR_DomainFile (eg, from the stackwalker or from some other
+// lookup function), then we need to do a process wide lookup since we don't know which
+// appdomain it's in. If you know the appdomain, you can use code:CordbAppDomain::LookupOrCreateModule.
+//
+CordbModule * CordbProcess::LookupOrCreateModule(VMPTR_DomainFile vmDomainFile)
+{
+ INTERNAL_API_ENTRY(this);
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ _ASSERTE(!vmDomainFile.IsNull());
+
+ DomainFileInfo data;
+ GetDAC()->GetDomainFileData(vmDomainFile, &data); // throws
+
+ CordbAppDomain * pAppDomain = LookupOrCreateAppDomain(data.vmAppDomain);
+ return pAppDomain->LookupOrCreateModule(vmDomainFile);
+}
+
+//---------------------------------------------------------------------------------------
+// Determine if the process has any in-band queued events which have not been dispatched
+//
+// Returns:
+// TRUE iff there are undispatched IB events
+//
+#ifdef FEATURE_INTEROP_DEBUGGING
+BOOL CordbProcess::HasUndispatchedNativeEvents()
+{
+ INTERNAL_API_ENTRY(this);
+
+ CordbUnmanagedEvent* pEvent = m_unmanagedEventQueue;
+ while(pEvent != NULL && pEvent->IsDispatched())
+ {
+ pEvent = pEvent->m_next;
+ }
+
+ return pEvent != NULL;
+}
+#endif
+
+//---------------------------------------------------------------------------------------
+// Determine if the process has any in-band queued events which have not been user continued
+//
+// Returns:
+// TRUE iff there are user uncontinued IB events
+//
+#ifdef FEATURE_INTEROP_DEBUGGING
+BOOL CordbProcess::HasUserUncontinuedNativeEvents()
+{
+ INTERNAL_API_ENTRY(this);
+
+ CordbUnmanagedEvent* pEvent = m_unmanagedEventQueue;
+ while(pEvent != NULL && pEvent->IsEventUserContinued())
+ {
+ pEvent = pEvent->m_next;
+ }
+
+ return pEvent != NULL;
+}
+#endif
+
+//---------------------------------------------------------------------------------------
+// Hijack the thread which had this event. If the thread is already hijacked this method
+// has no effect.
+//
+// Arguments:
+// pUnmanagedEvent - the debug event which requires us to hijack
+//
+// Returns:
+// S_OK on success, failing HRESULT if the hijack could not be set up
+//
+#ifdef FEATURE_INTEROP_DEBUGGING
+HRESULT CordbProcess::HijackIBEvent(CordbUnmanagedEvent * pUnmanagedEvent)
+{
+ // Can't hijack after the event has already been continued hijacked
+ _ASSERTE(!pUnmanagedEvent->IsEventContinuedHijacked());
+ // Can only hijack IB events
+ _ASSERTE(pUnmanagedEvent->IsIBEvent());
+
+ // If we already hijacked the event then there is nothing left to do
+ if(pUnmanagedEvent->m_owner->IsFirstChanceHijacked() ||
+ pUnmanagedEvent->m_owner->IsGenericHijacked())
+ {
+ return S_OK;
+ }
+
+ ResetEvent(this->m_leftSideUnmanagedWaitEvent);
+ if (pUnmanagedEvent->m_currentDebugEvent.u.Exception.dwFirstChance)
+ {
+ HRESULT hr = pUnmanagedEvent->m_owner->SetupFirstChanceHijackForSync();
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+ return hr;
+ }
+ else // Second chance exceptions must be generic hijacked.
+ {
+ HRESULT hr = pUnmanagedEvent->m_owner->SetupGenericHijack(pUnmanagedEvent->m_currentDebugEvent.dwDebugEventCode, &pUnmanagedEvent->m_currentDebugEvent.u.Exception.ExceptionRecord);
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+ return hr;
+ }
+}
+#endif
+
+// Sets a bitfield reflecting the managed debugging state at the time of
+// the jit attach.
+HRESULT CordbProcess::GetAttachStateFlags(CLR_DEBUGGING_PROCESS_FLAGS *pFlags)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ if(pFlags == NULL)
+ hr = E_POINTER;
+ else
+ *pFlags = GetDAC()->GetAttachStateFlags();
+ }
+ PUBLIC_API_END(hr);
+
+ return hr;
+}
+
+// Determine if this version of ICorDebug is compatibile with the ICorDebug in the specified major CLR version
+bool CordbProcess::IsCompatibleWith(DWORD clrMajorVersion)
+{
+ // The debugger versioning policy is that debuggers generally need to opt-in to supporting major new
+ // versions of the CLR. Often new versions of the CLR violate some invariant that previous debuggers assume
+ // (eg. hot/cold splitting in Whidbey, multiple CLRs in a process in CLR v4), and neither VS or the CLR
+ // teams generally want the support burden of forward compatibility.
+
+ //
+ // If this assert is firing for you, its probably because the major version
+ // number of the clr.dll has changed. This assert is here to remind you to do a bit of other
+ // work you may not have realized you needed to do so that our versioning works smoothly
+ // for debugging. You probably want to contact the CLR DST team if you are a
+ // non-debugger person hitting this. DON'T JUST DELETE THIS ASSERT!!!
+ //
+ // 1) You should ensure new versions of all ICorDebug users in DevDiv (VS Debugger, MDbg, etc.)
+ // are using a creation path that explicitly specifies that they support this new major
+ // version of the CLR.
+ // 2) You should file an issue to track blocking earlier debuggers from targetting this
+ // version of the CLR (i.e. update requiredVersion to the new CLR major
+ // version). To enable a smooth internal transition, this often isn't done until absolutely
+ // necessary (sometimes as late as Beta2).
+ // 3) You can consider updating the CLR_ID guid used by the shim to recognize a CLR, but only
+ // if it's important to completely hide newer CLRs from the shim. The expectation now
+ // is that we won't need to do this (i.e. we'd like VS to give a nice error message about
+ // needed a newer version of the debugger, rather than just acting as if a process has no CLR).
+ // 4) Update this assert so that it no longer fires for your new CLR version or any of
+ // the previous versions, but don't delete the assert...
+ // the next CLR version after yours will probably need the same reminder
+
+ _ASSERTE_MSG(clrMajorVersion <= 4,
+ "Found major CLR version greater than 4 in mscordbi.dll from CLRv4 - contact CLRDST");
+
+ // This knob lets us enable forward compatibility for internal scenarios, and also simulate new
+ // versions of the runtime for testing the failure user-experience in a version of the debugger
+ // before it is shipped.
+ // We don't want to risk customers getting this, so for RTM builds this must be CHK-only.
+ // To aid in internal transition, we may temporarily enable this in RET builds, but when
+ // doing so must file a bug to track making it CHK only again before RTM.
+ // For example, Dev10 Beta2 shipped with this knob, but it was made CHK-only at the start of RC.
+ // In theory we might have a point release someday where we break debugger compat, but
+ // it seems unlikely and since this knob is unsupported anyway we can always extend it
+ // then (support reading a string value, etc.). So for now we just map the number
+ // to the major CLR version number.
+ DWORD requiredVersion = 0;
+#ifdef _DEBUG
+ requiredVersion = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_Debugging_RequiredVersion);
+#endif
+
+ // If unset (the only supported configuration), then we require a debugger designed for CLRv4
+ // for desktop, where we do not allow forward compat.
+ // For SL, we allow forward compat. Right now, that means SLv2+ debugger requests can be
+ // honored for SLv4.
+ if (requiredVersion <= 0)
+ {
+#if defined(FEATURE_CORECLR)
+ requiredVersion = 2;
+#else
+ requiredVersion = 4;
+#endif
+ }
+
+ // Compare the version we were created for against the minimum required
+ return (clrMajorVersion >= requiredVersion);
+}
+
+bool CordbProcess::IsThreadSuspendedOrHijacked(ICorDebugThread * pICorDebugThread)
+{
+ // An RS lock can be held while this is called. Specifically,
+ // CordbThread::EnumerateChains may be on the stack, and it uses
+ // ATT_REQUIRE_STOPPED_MAY_FAIL, which holds the CordbProcess::m_StopGoLock lock for
+ // its entire duration. As a result, this needs to be considered a reentrant API. See
+ // comments above code:PrivateShimCallbackHolder for more info.
+ PUBLIC_REENTRANT_API_ENTRY_FOR_SHIM(this);
+
+ CordbThread * pCordbThread = static_cast<CordbThread *> (pICorDebugThread);
+ return GetDAC()->IsThreadSuspendedOrHijacked(pCordbThread->m_vmThreadToken);
+}
diff --git a/src/debug/di/publish.cpp b/src/debug/di/publish.cpp
new file mode 100644
index 0000000000..888988a10f
--- /dev/null
+++ b/src/debug/di/publish.cpp
@@ -0,0 +1,1282 @@
+// 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.
+//*****************************************************************************
+// File: publish.cpp
+//
+
+//
+//*****************************************************************************
+
+
+#include "stdafx.h"
+#ifdef FEATURE_DBG_PUBLISH
+
+#include "check.h"
+
+#include <tlhelp32.h>
+#include "wtsapi32.h"
+
+#ifndef SM_REMOTESESSION
+#define SM_REMOTESESSION 0x1000
+#endif
+
+#include "corpriv.h"
+#include "../../dlls/mscorrc/resource.h"
+#include <limits.h>
+
+// Publish shares header files with the rest of ICorDebug.
+// ICorDebug should not call ReadProcessMemory & other APIs directly, it should instead go through
+// the Data-target. ICD headers #define these APIs to help enforce this.
+// Since Publish is separate and doesn't use data-targets, it can access the APIs directly.
+// see code:RSDebuggingInfo#UseDataTarget
+#undef ReadProcessMemory
+
+//****************************************************************************
+//************ App Domain Publishing Service API Implementation **************
+//****************************************************************************
+
+// This function enumerates all the process in the system and returns
+// their PIDs
+BOOL GetAllProcessesInSystem(DWORD *ProcessId,
+ DWORD dwArraySize,
+ DWORD *pdwNumEntries)
+{
+ HandleHolder hSnapshotHolder;
+
+#if !defined(FEATURE_CORESYSTEM)
+ // Load the dll "kernel32.dll".
+ HModuleHolder hDll = WszLoadLibrary(W("kernel32"));
+ _ASSERTE(hDll != NULL);
+
+ if (hDll == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "Unable to load the dll for enumerating processes. "
+ "LoadLibrary (kernel32.dll) failed.\n"));
+ return FALSE;
+ }
+#else
+ // Load the dll "api-ms-win-obsolete-kernel32-l1-1-0.dll".
+ HModuleHolder hDll = WszLoadLibrary(W("api-ms-win-obsolete-kernel32-l1-1-0.dll"));
+ _ASSERTE(hDll != NULL);
+
+ if (hDll == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "Unable to load the dll for enumerating processes. "
+ "LoadLibrary (api-ms-win-obsolete-kernel32-l1-1-0.dll) failed.\n"));
+ return FALSE;
+ }
+#endif
+
+
+ // Create the Process' Snapshot
+ // Get the pointer to the requested function
+ FARPROC pProcAddr = GetProcAddress(hDll, "CreateToolhelp32Snapshot");
+
+ // If the proc address was not found, return error
+ if (pProcAddr == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "Unable to enumerate processes in the system. "
+ "GetProcAddr (CreateToolhelp32Snapshot) failed.\n"));
+ return FALSE;
+ }
+
+
+
+ // Handle from CreateToolHelp32Snapshot must be freed via CloseHandle().
+ typedef HANDLE CREATETOOLHELP32SNAPSHOT(DWORD, DWORD);
+
+ HANDLE hSnapshot =
+ ((CREATETOOLHELP32SNAPSHOT *)pProcAddr)(TH32CS_SNAPPROCESS, NULL);
+
+ if (hSnapshot == INVALID_HANDLE_VALUE)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "Unable to create snapshot of processes in the system. "
+ "CreateToolhelp32Snapshot() failed.\n"));
+ return FALSE;
+ }
+ // HandleHolder doesn't deal with INVALID_HANDLE_VALUE, so we only assign if we have a legal value.
+ hSnapshotHolder.Assign(hSnapshot);
+
+ // Get the first process in the process list
+ // Get the pointer to the requested function
+ pProcAddr = GetProcAddress(hDll, "Process32First");
+
+ // If the proc address was not found, return error
+ if (pProcAddr == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "Unable to enumerate processes in the system. "
+ "GetProcAddr (Process32First) failed.\n"));
+ return FALSE;
+ }
+
+ PROCESSENTRY32 PE32;
+
+ // need to initialize the dwSize field before calling Process32First
+ PE32.dwSize = sizeof (PROCESSENTRY32);
+
+ typedef BOOL PROCESS32FIRST(HANDLE, LPPROCESSENTRY32);
+
+ BOOL succ =
+ ((PROCESS32FIRST *)pProcAddr)(hSnapshot, &PE32);
+
+ if (succ != TRUE)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "Unable to create snapshot of processes in the system. "
+ "Process32First() returned FALSE.\n"));
+ return FALSE;
+ }
+
+
+ // Loop over and get all the remaining processes
+ // Get the pointer to the requested function
+ pProcAddr = GetProcAddress(hDll, "Process32Next");
+
+ // If the proc address was not found, return error
+ if (pProcAddr == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "Unable to enumerate processes in the system. "
+ "GetProcAddr (Process32Next) failed.\n"));
+ return FALSE;
+ }
+
+ typedef BOOL PROCESS32NEXT(HANDLE, LPPROCESSENTRY32);
+
+ int iIndex = 0;
+
+ do
+ {
+ ProcessId [iIndex++] = PE32.th32ProcessID;
+
+ succ = ((PROCESS32NEXT *)pProcAddr)(hSnapshot, &PE32);
+
+ } while ((succ == TRUE) && (iIndex < (int)dwArraySize));
+
+ // I would like to know if we're running more than 512 processes on Win95!!
+ _ASSERTE (iIndex < (int)dwArraySize);
+
+ *pdwNumEntries = iIndex;
+
+ // If we made it this far, we succeeded
+ return TRUE;
+}
+
+
+// We never want to wait infinite on an object that we can't verify.
+// Wait with a timeout.
+const DWORD SAFETY_TIMEOUT = 2000;
+
+// ******************************************
+// CorpubPublish
+// ******************************************
+
+CorpubPublish::CorpubPublish()
+ : CordbCommonBase(0),
+ m_fpGetModuleFileNameEx(NULL)
+{
+ // Try to get psapi!GetModuleFileNameExW once, and then every process object can use it.
+ // If we can't get it, then we'll fallback to getting information from the IPC block.
+#if !defined(FEATURE_CORESYSTEM)
+ m_hPSAPIdll = WszLoadLibrary(W("psapi.dll"));
+#else
+ m_hPSAPIdll = WszLoadLibrary(W("api-ms-win-obsolete-psapi-l1-1-0.dll"));
+#endif
+
+ if (m_hPSAPIdll != NULL)
+ {
+ m_fpGetModuleFileNameEx = (FPGetModuleFileNameEx*) GetProcAddress(m_hPSAPIdll, "GetModuleFileNameExW");
+ }
+
+ CordbCommonBase::InitializeCommon();
+}
+
+CorpubPublish::~CorpubPublish()
+{
+ // m_hPSAPIdll is a module holder, so the dtor will free it automatically for us.
+}
+
+
+COM_METHOD CorpubPublish::QueryInterface(REFIID id, void **ppInterface)
+{
+ if (id == IID_ICorPublish)
+ *ppInterface = (ICorPublish*)this;
+ else if (id == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ICorPublish*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+
+COM_METHOD CorpubPublish::EnumProcesses(COR_PUB_ENUMPROCESS Type,
+ ICorPublishProcessEnum **ppIEnum)
+{
+ HRESULT hr = E_FAIL;
+ CorpubProcess* pProcessList = NULL ;
+ CorpubProcessEnum* pProcEnum = NULL;
+ *ppIEnum = NULL;
+
+ if( Type != COR_PUB_MANAGEDONLY )
+ {
+ hr = E_INVALIDARG;
+ goto exit;
+ }
+
+ // call function to get PIDs for all processes in the system
+#define MAX_PROCESSES 512
+
+ DWORD ProcessId[MAX_PROCESSES];
+ DWORD dwNumProcesses = 0;
+ if( !GetAllProcessesInSystem(ProcessId, MAX_PROCESSES, &dwNumProcesses) )
+ {
+ hr = E_FAIL;
+ goto exit;
+ }
+
+ // iterate over all the processes to fetch all the managed processes
+ for (int i = 0; i < (int)dwNumProcesses; i++)
+ {
+ CorpubProcess *pProcess = NULL;
+ hr = GetProcessInternal( ProcessId[i], &pProcess );
+ if( FAILED(hr) )
+ {
+ _ASSERTE( pProcess == NULL );
+ goto exit; // a serious error has occurred, abort
+ }
+
+ if( hr == S_OK )
+ {
+ // Success, Add the process to the list.
+ _ASSERTE( pProcess != NULL );
+ pProcess->SetNext( pProcessList );
+ pProcessList = pProcess;
+ }
+ else
+ {
+ // Ignore this process (isn't managed, or shut down, etc.)
+ _ASSERTE( pProcess == NULL );
+ }
+ }
+
+ // create and return the ICorPublishProcessEnum
+ pProcEnum = new (nothrow) CorpubProcessEnum(pProcessList);
+ if (pProcEnum == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ goto exit;
+ }
+ pProcEnum->AddRef();
+
+ hr = pProcEnum->QueryInterface(IID_ICorPublishProcessEnum, (void**)ppIEnum);
+ if( FAILED(hr) )
+ {
+ goto exit;
+ }
+
+ hr = S_OK;
+
+exit:
+ // release our handle on the process objects
+ while (pProcessList != NULL)
+ {
+ CorpubProcess *pTmp = pProcessList;
+ pProcessList = pProcessList->GetNextProcess();
+ pTmp->Release();
+ }
+ if( pProcEnum != NULL )
+ {
+ pProcEnum->Release();
+ pProcEnum = NULL;
+ }
+
+ return hr;
+}
+
+
+HRESULT CorpubPublish::GetProcess(unsigned pid,
+ ICorPublishProcess **ppProcess)
+{
+ *ppProcess = NULL;
+
+ // Query for this specific process (even if we've already handed out a
+ // now-stale process object for this pid)
+ CorpubProcess * pProcess = NULL;
+ HRESULT hr = GetProcessInternal( pid, &pProcess );
+ if( hr != S_OK )
+ {
+ // Couldn't get this process (doesn't exist, or isn't managed)
+ _ASSERTE( pProcess == NULL );
+ if( FAILED(hr) )
+ {
+ return hr; // there was a serious error trying to get this process info
+ }
+ return E_INVALIDARG; // this process doesn't exist, isn't managed or is shutting down
+ }
+
+ // QI to ICorPublishProcess and return it
+ _ASSERTE( pProcess != NULL );
+ hr = pProcess->QueryInterface(IID_ICorPublishProcess, (void**)ppProcess);
+ pProcess->Release();
+ return hr;
+}
+
+
+// Attempts to create a CorpubProcess object for a specific managed process
+// On success returns S_OK and sets ppProcess to a new AddRef'd CorpubProcess
+// object. Otherwise, returns S_FALSE if the process isn't managed or if it has
+// terminated (i.e. it should be ignored), or and error code on a serious failure.
+HRESULT CorpubPublish::GetProcessInternal(
+ unsigned pid,
+ CorpubProcess **ppProcess )
+{
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ return E_NOTIMPL;
+
+#else // !FEATURE_DBGIPC_TRANSPORT_DI
+ HRESULT hr = S_OK;
+ *ppProcess = NULL;
+
+ NewHolder<IPCReaderInterface> pIPCReader( new (nothrow) IPCReaderInterface() );
+ if (pIPCReader == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO100, "CP::EP: Failed to allocate memory for IPCReaderInterface.\n"));
+ return E_OUTOFMEMORY;
+ }
+
+ // See if it is a managed process by trying to open the shared
+ // memory block.
+ hr = pIPCReader->OpenLegacyPrivateBlockTempV4OnPid(pid);
+ if (FAILED(hr))
+ {
+ return S_FALSE; // Not a managed process
+ }
+
+ // Get the AppDomainIPCBlock
+ AppDomainEnumerationIPCBlock *pAppDomainCB = pIPCReader->GetAppDomainBlock();
+ if (pAppDomainCB == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::EP: Failed to obtain AppDomainIPCBlock.\n"));
+ return S_FALSE;
+ }
+
+ // Get the process handle.
+ HANDLE hProcess = OpenProcess((PROCESS_VM_READ |
+ PROCESS_QUERY_INFORMATION |
+ PROCESS_DUP_HANDLE |
+ SYNCHRONIZE),
+ FALSE, pid);
+ if (hProcess == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::EP: OpenProcess() returned NULL handle.\n"));
+ return S_FALSE;
+ }
+
+ // If the mutex isn't filled in, the CLR is either starting up or shutting down
+ if (pAppDomainCB->m_hMutex == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::EP: IPC block isn't properly filled in.\n"));
+ return S_FALSE;
+ }
+
+ // Dup the valid mutex handle into this process.
+ HANDLE hMutex;
+ if( !pAppDomainCB->m_hMutex.DuplicateToLocalProcess(hProcess, &hMutex) )
+ {
+ return S_FALSE;
+ }
+
+ // Acquire the mutex, only waiting two seconds.
+ // We can't actually gaurantee that the target put a mutex object in here.
+ DWORD dwRetVal = WaitForSingleObject(hMutex, SAFETY_TIMEOUT);
+
+ if (dwRetVal == WAIT_OBJECT_0)
+ {
+ // Make sure the mutex handle is still valid. If
+ // its not, then we lost a shutdown race.
+ if (pAppDomainCB->m_hMutex == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::EP: lost shutdown race, skipping...\n"));
+
+ ReleaseMutex(hMutex);
+ CloseHandle(hMutex);
+ return S_FALSE;
+ }
+ }
+ else
+ {
+ // Again, landing here is most probably a shutdown race. Its okay, though...
+ LOG((LF_CORDB, LL_INFO1000, "CP::EP: failed to get IPC mutex.\n"));
+
+ if (dwRetVal == WAIT_ABANDONED)
+ {
+ ReleaseMutex(hMutex);
+ }
+ CloseHandle(hMutex);
+ return S_FALSE;
+ }
+ // Beware: if the target pid is not properly honoring the mutex, the data in the
+ // IPC block may still shift underneath us.
+
+ // If we get here, then hMutex is held by this process.
+
+ // Now create the CorpubProcess object for the ProcessID
+ CorpubProcess *pProc = new (nothrow) CorpubProcess(pid,
+ true,
+ hProcess,
+ hMutex,
+ pAppDomainCB,
+ pIPCReader,
+ m_fpGetModuleFileNameEx);
+
+ // Release our lock on the IPC block.
+ ReleaseMutex(hMutex);
+
+ if (pProc == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+ pIPCReader.SuppressRelease();
+
+ // Success, return the Process object
+ pProc->AddRef();
+ *ppProcess = pProc;
+ return S_OK;
+
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+}
+
+
+
+// ******************************************
+// CorpubProcess
+// ******************************************
+
+// Constructor
+CorpubProcess::CorpubProcess(DWORD dwProcessId,
+ bool fManaged,
+ HANDLE hProcess,
+ HANDLE hMutex,
+ AppDomainEnumerationIPCBlock *pAD,
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ IPCReaderInterface *pIPCReader,
+#endif // !FEATURE_DBGIPC_TRANSPORT_DI
+ FPGetModuleFileNameEx * fpGetModuleFileNameEx)
+ : CordbCommonBase(0, enumCorpubProcess),
+ m_dwProcessId(dwProcessId),
+ m_fIsManaged(fManaged),
+ m_hProcess(hProcess),
+ m_hMutex(hMutex),
+ m_AppDomainCB(pAD),
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ m_pIPCReader(pIPCReader),
+#endif // !FEATURE_DBGIPC_TRANSPORT_DI
+ m_pNext(NULL)
+{
+ {
+ // First try to get the process name from the OS. That can't be spoofed by badly formed IPC block.
+ // psapi!GetModuleFileNameExW can get that, but it's not available on all platforms so we
+ // need to load it dynamically.
+ if (fpGetModuleFileNameEx != NULL)
+ {
+ // MSDN is very confused about whether the lenght is in bytes (MSDN 2002) or chars (MSDN 2004).
+ // We err on the safe side by having buffer that's twice as large, and ignoring
+ // the units on the return value.
+ WCHAR szName[MAX_LONGPATH * sizeof(WCHAR)];
+
+ DWORD lenInCharsOrBytes = MAX_LONGPATH*sizeof(WCHAR);
+
+ // Pass NULL module handle to get "Main Module", which will give us the process name.
+ DWORD ret = (*fpGetModuleFileNameEx) (hProcess, NULL, szName, lenInCharsOrBytes);
+ if (ret > 0)
+ {
+ // Recompute string length because we don't know if 'ret' is in bytes or char.
+ SIZE_T len = wcslen(szName) + 1;
+ m_szProcessName = new (nothrow) WCHAR[len];
+ if (m_szProcessName != NULL)
+ {
+ wcscpy_s(m_szProcessName, len, szName);
+ goto exit;
+ }
+ }
+ }
+
+ // This is a security feature on WinXp + above, so make sure it worked there.
+ CONSISTENCY_CHECK_MSGF(FALSE, ("On XP/2k03 OSes + above, we should have been able to get\n"
+ "the module name from psapi!GetModuleFileNameEx. fp=0x%p\n.", fpGetModuleFileNameEx));
+ }
+ // We couldn't get it from the OS, so fallthrough to getting it from the IPC block.
+
+ // Fetch the process name from the AppDomainIPCBlock
+ _ASSERTE (pAD->m_szProcessName != NULL);
+
+ if (pAD->m_szProcessName == NULL)
+ m_szProcessName = NULL;
+ else
+ {
+ SIZE_T nBytesRead;
+
+ _ASSERTE(pAD->m_iProcessNameLengthInBytes > 0);
+
+ // Note: this assumes we're reading the null terminator from
+ // the IPC block.
+ m_szProcessName = (WCHAR*) new (nothrow) char[pAD->m_iProcessNameLengthInBytes];
+
+ if (m_szProcessName == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "CP::CP: Failed to allocate memory for ProcessName.\n"));
+
+ goto exit;
+ }
+
+ BOOL bSucc = ReadProcessMemory(hProcess,
+ pAD->m_szProcessName,
+ m_szProcessName,
+ pAD->m_iProcessNameLengthInBytes,
+ &nBytesRead);
+
+ if ((bSucc == 0) ||
+ (nBytesRead != (SIZE_T)pAD->m_iProcessNameLengthInBytes))
+ {
+ // The EE may have done a rude exit
+ LOG((LF_CORDB, LL_INFO1000,
+ "CP::EAD: ReadProcessMemory (ProcessName) failed.\n"));
+ }
+ }
+
+exit:
+ ;
+}
+
+CorpubProcess::~CorpubProcess()
+{
+ delete [] m_szProcessName;
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ delete m_pIPCReader;
+#endif // !FEATURE_DBGIPC_TRANSPORT_DI
+ CloseHandle(m_hProcess);
+ CloseHandle(m_hMutex);
+}
+
+
+HRESULT CorpubProcess::QueryInterface(REFIID id, void **ppInterface)
+{
+ if (id == IID_ICorPublishProcess)
+ *ppInterface = (ICorPublishProcess*)this;
+ else if (id == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ICorPublishProcess*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+
+// Helper to tell if this process has exited.
+bool CorpubProcess::IsExited()
+{
+ DWORD res = WaitForSingleObject(this->m_hProcess, 0);
+ return (res == WAIT_OBJECT_0);
+}
+
+
+HRESULT CorpubProcess::IsManaged(BOOL *pbManaged)
+{
+ *pbManaged = (m_fIsManaged == true) ? TRUE : FALSE;
+
+ return S_OK;
+}
+
+// Helper.
+// Allocates a local buffer (using 'new') and fills it by copying it from remote memory.
+// Returns:
+// - on success, S_OK, *ppNewLocalBuffer points to a newly allocated buffer containing
+// the full copy from remote memoy. Caller must use 'delete []' to free this.
+// - on failure, a failing HR. No memory is allocated.
+HRESULT AllocateAndReadRemoteBuffer(
+ HANDLE hProcess,
+ void * pRemotePtr,
+ SIZE_T cbSize, // size of buffer to allocate + copy.
+ BYTE * * ppNewLocalBuffer
+)
+{
+ _ASSERTE(ppNewLocalBuffer != NULL);
+ *ppNewLocalBuffer = NULL;
+
+
+ if (pRemotePtr == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ BYTE *pLocalBuffer = new (nothrow) BYTE[cbSize];
+
+ if (pLocalBuffer == NULL)
+ {
+ _ASSERTE(!"Failed to alloc memory. Likely size is bogusly large, perhaps from an attacker.");
+ return E_OUTOFMEMORY;
+ }
+
+ SIZE_T nBytesRead;
+
+ // Need to read in the remote process' memory
+ BOOL bSucc = ReadProcessMemory(hProcess,
+ pRemotePtr,
+ pLocalBuffer, cbSize,
+ &nBytesRead);
+
+ if ((bSucc == 0) || (nBytesRead != cbSize))
+ {
+ // The EE may have done a rude exit
+ delete [] pLocalBuffer;
+ return E_FAIL;
+ }
+
+ *ppNewLocalBuffer = pLocalBuffer;
+ return S_OK;
+}
+
+// Wrapper around AllocateAndReadRemoteBuffer,
+// to ensure that we're reading an remote-null terminated string.
+// Ensures that string is null-terminated.
+HRESULT AllocateAndReadRemoteString(
+ HANDLE hProcess,
+ void * pRemotePtr,
+ SIZE_T cbSize, // size of buffer to allocate + copy.
+ __deref_out_bcount(cbSize) WCHAR * * ppNewLocalBuffer
+ )
+{
+ // Make sure buffer has right geometry.
+ if (cbSize < 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ // If it's not on a WCHAR boundary, then we may have a 1-byte buffer-overflow.
+ SIZE_T ceSize = cbSize / sizeof(WCHAR);
+ if ((ceSize * sizeof(WCHAR)) != cbSize)
+ {
+ return E_INVALIDARG;
+ }
+
+ // It should at least have 1 char for the null terminator.
+ if (ceSize < 1)
+ {
+ return E_INVALIDARG;
+ }
+
+
+ HRESULT hr = AllocateAndReadRemoteBuffer(hProcess, pRemotePtr, cbSize, (BYTE**) ppNewLocalBuffer);
+ if (SUCCEEDED(hr))
+ {
+ // Ensure that the string we just read is actually null terminated.
+ // We can't call wcslen() on it yet, since that may AV on a non-null terminated string.
+ WCHAR * pString = *ppNewLocalBuffer;
+
+ if (pString[ceSize - 1] == W('\0'))
+ {
+ // String is null terminated.
+ return S_OK;
+ }
+ pString[ceSize - 1] = W('\0');
+
+ SIZE_T ceTestLen = wcslen(pString);
+ if (ceTestLen == ceSize - 1)
+ {
+ // String was not previously null-terminated.
+ delete [] ppNewLocalBuffer;
+ return E_INVALIDARG;
+ }
+ }
+ return S_OK;
+}
+
+//
+// Enumerate the list of known application domains in the target process.
+//
+HRESULT CorpubProcess::EnumAppDomains(ICorPublishAppDomainEnum **ppIEnum)
+{
+ VALIDATE_POINTER_TO_OBJECT(ppIEnum, ICorPublishAppDomainEnum **);
+ *ppIEnum = NULL;
+
+ int i;
+
+ HRESULT hr = S_OK;
+ WCHAR *pAppDomainName = NULL;
+ CorpubAppDomain *pAppDomainHead = NULL;
+
+ // Lock the IPC block:
+ // We can't trust any of the data in the IPC block (including our own mutex handle),
+ // because we don't want bugs in the debuggee escalating into bugs in the debugger.
+ DWORD res = WaitForSingleObject(m_hMutex, SAFETY_TIMEOUT);
+
+ if (res == WAIT_TIMEOUT)
+ {
+ // This should only happen if the target process is illbehaved.
+ return CORDBG_E_TIMEOUT;
+ }
+
+ // If the process has gone away, or if it has cleared out its control block, then
+ // we've lost the race to access this process before it is terminated.
+ // Note that if the EE does a rude process exit, it won't have cleared the control block so there
+ // will be a small race window.
+ if (this->IsExited() || this->m_AppDomainCB->m_hMutex == NULL )
+ {
+ // This is the common case. A process holding the mutex shouldn't normally exit,
+ // but once it releases the mutex, it may exit asynchronously.
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ if (res == WAIT_FAILED)
+ {
+ // This should be the next most common failure case
+ return HRESULT_FROM_GetLastError();
+ }
+
+ if (res != WAIT_OBJECT_0)
+ {
+ // Catch all other possible failures
+ return E_FAIL;
+ }
+
+ int iAppDomainCount = 0;
+ AppDomainInfo *pADI = NULL;
+
+ // Make a copy of the IPC block so that we can gaurantee that it's not changing on us.
+ AppDomainEnumerationIPCBlock tempBlock;
+ memcpy(&tempBlock, m_AppDomainCB, sizeof(tempBlock));
+
+ // Allocate memory to read the remote process' memory into
+ const SIZE_T cbADI = tempBlock.m_iSizeInBytes;
+
+ // It's possible the process will not have any appdomains.
+ if ((tempBlock.m_rgListOfAppDomains == NULL) != (tempBlock.m_iSizeInBytes == 0))
+ {
+ _ASSERTE(!"Inconsistent IPC block in publish.");
+ hr = E_FAIL;
+ goto exit;
+ }
+
+ // All the data in the IPC block is signed integers. They should never be negative,
+ // so check that now.
+ if ((tempBlock.m_iTotalSlots < 0) ||
+ (tempBlock.m_iNumOfUsedSlots < 0) ||
+ (tempBlock.m_iLastFreedSlot < 0) ||
+ (tempBlock.m_iSizeInBytes < 0) ||
+ (tempBlock.m_iProcessNameLengthInBytes < 0))
+ {
+ hr = E_FAIL;
+ goto exit;
+ }
+
+ // Check other invariants.
+ if (cbADI != tempBlock.m_iTotalSlots * sizeof(AppDomainInfo))
+ {
+ _ASSERTE(!"Size mismatch");
+ hr = E_FAIL;
+ goto exit;
+ }
+
+ hr = AllocateAndReadRemoteBuffer(m_hProcess, tempBlock.m_rgListOfAppDomains, cbADI, (BYTE**) &pADI);
+ if (FAILED(hr))
+ {
+ goto exit;
+ }
+ _ASSERTE(pADI != NULL);
+
+ // Collect all the AppDomain info info a list of CorpubAppDomains
+ for (i = 0; i < tempBlock.m_iTotalSlots; i++)
+ {
+ if (!pADI[i].IsEmpty())
+ {
+ // Should be positive, and at least have a null-terminator character.
+ if (pADI[i].m_iNameLengthInBytes <= 1)
+ {
+ hr = E_INVALIDARG;
+ goto exit;
+ }
+ hr = AllocateAndReadRemoteString(m_hProcess,
+ (void*) pADI[i].m_szAppDomainName, pADI[i].m_iNameLengthInBytes, // remote string + size in bytes
+ &pAppDomainName);
+ if (FAILED(hr))
+ {
+ goto exit;
+ }
+
+ // create a new AppDomainObject. This will take ownership of pAppDomainName.
+ // We know the string is a well-formed null-terminated string,
+ // but beyond that, we can't verify that the data is actually truthful.
+ CorpubAppDomain *pCurrentAppDomain = new (nothrow) CorpubAppDomain(pAppDomainName,
+ pADI[i].m_id);
+
+ if (pCurrentAppDomain == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "CP::EAD: Failed to allocate memory for CorpubAppDomain.\n"));
+
+ hr = E_OUTOFMEMORY;
+ goto exit;
+ }
+
+ // Since CorpubAppDomain now owns pAppDomain's memory, we don't worry about freeing it.
+ pAppDomainName = NULL;
+
+ // Add the appdomain to the list.
+ pCurrentAppDomain->SetNext(pAppDomainHead);
+ pAppDomainHead = pCurrentAppDomain;
+
+ // Shortcut to opt out of reading the rest of the array if it's empty.
+ if (++iAppDomainCount >= tempBlock.m_iNumOfUsedSlots)
+ break;
+ }
+ }
+
+ {
+ _ASSERTE ((iAppDomainCount >= tempBlock.m_iNumOfUsedSlots)
+ && (i <= tempBlock.m_iTotalSlots));
+
+ // create and return the ICorPublishAppDomainEnum object, handing off the AppDomain list to it
+ CorpubAppDomainEnum *pTemp = new (nothrow) CorpubAppDomainEnum(pAppDomainHead);
+
+ if (pTemp == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ goto exit;
+ }
+
+ pAppDomainHead = NULL; // handed off AppDomain list to enum, don't delete below
+
+ hr = pTemp->QueryInterface(IID_ICorPublishAppDomainEnum,
+ (void **)ppIEnum);
+ }
+
+exit:
+ ReleaseMutex(m_hMutex);
+
+ // If we didn't hand off the AppDomain objects, delete them
+ while( pAppDomainHead != NULL )
+ {
+ CorpubAppDomain *pTemp = pAppDomainHead;
+ pAppDomainHead = pAppDomainHead->GetNextAppDomain();
+ delete pTemp;
+ }
+
+ if (pADI != NULL)
+ delete[] pADI;
+
+ if (pAppDomainName != NULL)
+ delete [] pAppDomainName;
+
+ // Either we succeeded && provided an enumerator; or we failed and didn't provide an enum.
+ _ASSERTE(SUCCEEDED(hr) == (*ppIEnum != NULL));
+ return hr;
+}
+
+/*
+ * Returns the OS ID for the process in question.
+ */
+HRESULT CorpubProcess::GetProcessID(unsigned *pid)
+{
+ *pid = m_dwProcessId;
+
+ return S_OK;
+}
+
+/*
+ * Get the display name for a process.
+ */
+HRESULT CorpubProcess::GetDisplayName(ULONG32 cchName,
+ ULONG32 *pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(szName, WCHAR, cchName, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcchName, ULONG32 *);
+
+ // Reasonable defaults
+ if (szName)
+ *szName = 0;
+
+ if (pcchName)
+ *pcchName = 0;
+
+ const WCHAR *szTempName = m_szProcessName;
+
+ // In case we didn't get the name (most likely out of memory on ctor).
+ if (!szTempName)
+ szTempName = W("<unknown>");
+
+ return CopyOutString(szTempName, cchName, pcchName, szName);
+}
+
+
+// ******************************************
+// CorpubAppDomain
+// ******************************************
+
+CorpubAppDomain::CorpubAppDomain (__in LPWSTR szAppDomainName, ULONG Id)
+ : CordbCommonBase (0, enumCorpubAppDomain),
+ m_pNext (NULL),
+ m_szAppDomainName (szAppDomainName),
+ m_id (Id)
+{
+ _ASSERTE(m_szAppDomainName != NULL);
+}
+
+CorpubAppDomain::~CorpubAppDomain()
+{
+ delete [] m_szAppDomainName;
+}
+
+HRESULT CorpubAppDomain::QueryInterface (REFIID id, void **ppInterface)
+{
+ if (id == IID_ICorPublishAppDomain)
+ *ppInterface = (ICorPublishAppDomain*)this;
+ else if (id == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ICorPublishAppDomain*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+
+/*
+ * Get the name and ID for an application domain.
+ */
+HRESULT CorpubAppDomain::GetID (ULONG32 *pId)
+{
+ VALIDATE_POINTER_TO_OBJECT(pId, ULONG32 *);
+
+ *pId = m_id;
+
+ return S_OK;
+}
+
+/*
+ * Get the name for an application domain.
+ */
+HRESULT CorpubAppDomain::GetName(ULONG32 cchName,
+ ULONG32 *pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(szName, WCHAR, cchName, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcchName, ULONG32 *);
+
+ const WCHAR *szTempName = m_szAppDomainName;
+
+ // In case we didn't get the name (most likely out of memory on ctor).
+ if (!szTempName)
+ szTempName = W("<unknown>");
+
+ return CopyOutString(szTempName, cchName, pcchName, szName);
+}
+
+
+
+// ******************************************
+// CorpubProcessEnum
+// ******************************************
+
+CorpubProcessEnum::CorpubProcessEnum (CorpubProcess *pFirst)
+ : CordbCommonBase (0, enumCorpubProcessEnum),
+ m_pFirst (pFirst),
+ m_pCurrent (pFirst)
+{
+ // Increment the ref count on each process, we own the list
+ CorpubProcess * cur = pFirst;
+ while( cur != NULL )
+ {
+ cur->AddRef();
+ cur = cur->GetNextProcess();
+ }
+}
+
+CorpubProcessEnum::~CorpubProcessEnum()
+{
+ // Release each process in the list (our client may still have a reference
+ // to some of them)
+ while (m_pFirst != NULL)
+ {
+ CorpubProcess *pTmp = m_pFirst;
+ m_pFirst = m_pFirst->GetNextProcess();
+ pTmp->Release();
+ }
+}
+
+HRESULT CorpubProcessEnum::QueryInterface (REFIID id, void **ppInterface)
+{
+ if (id == IID_ICorPublishProcessEnum)
+ *ppInterface = (ICorPublishProcessEnum*)this;
+ else if (id == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ICorPublishProcessEnum*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+
+HRESULT CorpubProcessEnum::Skip(ULONG celt)
+{
+ while ((m_pCurrent != NULL) && (celt-- > 0))
+ {
+ m_pCurrent = m_pCurrent->GetNextProcess();
+ }
+
+ return S_OK;
+}
+
+HRESULT CorpubProcessEnum::Reset()
+{
+ m_pCurrent = m_pFirst;
+
+ return S_OK;
+}
+
+HRESULT CorpubProcessEnum::Clone(ICorPublishEnum **ppEnum)
+{
+ VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorPublishEnum **);
+ return E_NOTIMPL;
+}
+
+HRESULT CorpubProcessEnum::GetCount(ULONG *pcelt)
+{
+ VALIDATE_POINTER_TO_OBJECT(pcelt, ULONG *);
+
+ CorpubProcess *pTemp = m_pFirst;
+
+ *pcelt = 0;
+
+ while (pTemp != NULL)
+ {
+ (*pcelt)++;
+ pTemp = pTemp->GetNextProcess();
+ }
+
+ return S_OK;
+}
+
+HRESULT CorpubProcessEnum::Next(ULONG celt,
+ ICorPublishProcess *objects[],
+ ULONG *pceltFetched)
+{
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(objects, ICorPublishProcess *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched, ULONG *);
+
+ if ((pceltFetched == NULL) && (celt != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ if (celt == 0)
+ {
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = 0;
+ }
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+
+ ULONG count = 0;
+
+ while ((m_pCurrent != NULL) && (count < celt))
+ {
+ hr = m_pCurrent->QueryInterface (IID_ICorPublishProcess,
+ (void**)&objects[count]);
+
+ if (hr != S_OK)
+ {
+ break;
+ }
+
+ count++;
+ m_pCurrent = m_pCurrent->GetNextProcess();
+ }
+
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = count;
+ }
+
+ //
+ // If we reached the end of the enumeration, but not the end
+ // of the number of requested items, we return S_FALSE.
+ //
+ if (count < celt)
+ {
+ return S_FALSE;
+ }
+
+ return hr;
+}
+
+// ******************************************
+// CorpubAppDomainEnum
+// ******************************************
+CorpubAppDomainEnum::CorpubAppDomainEnum (CorpubAppDomain *pFirst)
+ : CordbCommonBase (0, enumCorpubAppDomainEnum),
+ m_pFirst (pFirst),
+ m_pCurrent (pFirst)
+{
+ CorpubAppDomain *pCur = pFirst;
+ while( pCur != NULL )
+ {
+ pCur->AddRef();
+ pCur = pCur->GetNextAppDomain();
+ }
+}
+
+CorpubAppDomainEnum::~CorpubAppDomainEnum()
+{
+ // Delete all the app domains
+ while (m_pFirst != NULL )
+ {
+ CorpubAppDomain *pTemp = m_pFirst;
+ m_pFirst = m_pFirst->GetNextAppDomain();
+ pTemp->Release();
+ }
+}
+
+HRESULT CorpubAppDomainEnum::QueryInterface (REFIID id, void **ppInterface)
+{
+ if (id == IID_ICorPublishAppDomainEnum)
+ *ppInterface = (ICorPublishAppDomainEnum*)this;
+ else if (id == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ICorPublishAppDomainEnum*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+
+HRESULT CorpubAppDomainEnum::Skip(ULONG celt)
+{
+ while ((m_pCurrent != NULL) && (celt-- > 0))
+ {
+ m_pCurrent = m_pCurrent->GetNextAppDomain();
+ }
+
+ return S_OK;
+}
+
+HRESULT CorpubAppDomainEnum::Reset()
+{
+ m_pCurrent = m_pFirst;
+
+ return S_OK;
+}
+
+HRESULT CorpubAppDomainEnum::Clone(ICorPublishEnum **ppEnum)
+{
+ VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorPublishEnum **);
+ return E_NOTIMPL;
+}
+
+HRESULT CorpubAppDomainEnum::GetCount(ULONG *pcelt)
+{
+ VALIDATE_POINTER_TO_OBJECT(pcelt, ULONG *);
+
+ CorpubAppDomain *pTemp = m_pFirst;
+
+ *pcelt = 0;
+
+ while (pTemp != NULL)
+ {
+ (*pcelt)++;
+ pTemp = pTemp->GetNextAppDomain();
+ }
+
+ return S_OK;
+}
+
+HRESULT CorpubAppDomainEnum::Next(ULONG celt,
+ ICorPublishAppDomain *objects[],
+ ULONG *pceltFetched)
+{
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(objects, ICorPublishProcess *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched, ULONG *);
+
+ if ((pceltFetched == NULL) && (celt != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ if (celt == 0)
+ {
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = 0;
+ }
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+
+ ULONG count = 0;
+
+ while ((m_pCurrent != NULL) && (count < celt))
+ {
+ hr = m_pCurrent->QueryInterface (IID_ICorPublishAppDomain,
+ (void **)&objects[count]);
+
+ if (hr != S_OK)
+ {
+ break;
+ }
+
+ count++;
+ m_pCurrent = m_pCurrent->GetNextAppDomain();
+ }
+
+
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = count;
+ }
+
+ //
+ // If we reached the end of the enumeration, but not the end
+ // of the number of requested items, we return S_FALSE.
+ //
+ if (count < celt)
+ {
+ return S_FALSE;
+ }
+
+ return hr;
+}
+
+#endif // defined(FEATURE_DBG_PUBLISH)
diff --git a/src/debug/di/remoteeventchannel.cpp b/src/debug/di/remoteeventchannel.cpp
new file mode 100644
index 0000000000..fc1d57a60f
--- /dev/null
+++ b/src/debug/di/remoteeventchannel.cpp
@@ -0,0 +1,342 @@
+// 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.
+//*****************************************************************************
+// File: RemoteEventChannel.cpp
+//
+
+//
+// Implements the old-style event channel between two remote processes.
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "eventchannel.h"
+
+#include "dbgtransportsession.h"
+#include "dbgtransportmanager.h"
+
+
+//---------------------------------------------------------------------------------------
+// Class serves as a connector to win32 native-debugging API.
+class RemoteEventChannel : public IEventChannel
+{
+public:
+ RemoteEventChannel(DebuggerIPCControlBlock * pDCBBuffer,
+ DbgTransportTarget * pProxy,
+ DbgTransportSession * pTransport);
+
+ virtual ~RemoteEventChannel() {}
+
+ // Inititalize the event channel.
+ virtual HRESULT Init(HANDLE hTargetProc);
+
+ // Called when the debugger is detaching.
+ virtual void Detach();
+
+ // Delete the event channel and clean up all the resources it owns. This function can only be called once.
+ virtual void Delete();
+
+
+
+ // Update a single field with a value stored in the RS copy of the DCB.
+ virtual HRESULT UpdateLeftSideDCBField(void *rsFieldAddr, SIZE_T size);
+
+ // Update the entire RS copy of the debugger control block by reading the LS copy.
+ virtual HRESULT UpdateRightSideDCB();
+
+ // Get the pointer to the RS DCB.
+ virtual DebuggerIPCControlBlock * GetDCB();
+
+
+
+ // Check whether we need to wait for an acknowledgement from the LS after sending an IPC event.
+ virtual BOOL NeedToWaitForAck(DebuggerIPCEvent * pEvent);
+
+ // Get a handle to wait on after sending an IPC event to the LS. The caller should call NeedToWaitForAck()
+ virtual HANDLE GetRightSideEventAckHandle();
+
+ // Clean up the state if the wait for an acknowledgement is unsuccessful.
+ virtual void ClearEventForLeftSide();
+
+
+
+ // Send an IPC event to the LS.
+ virtual HRESULT SendEventToLeftSide(DebuggerIPCEvent * pEvent, SIZE_T eventSize);
+
+ // Get the reply from the LS for a previously sent IPC event.
+ virtual HRESULT GetReplyFromLeftSide(DebuggerIPCEvent * pReplyEvent, SIZE_T eventSize);
+
+
+
+ // Save an IPC event from the LS.
+ // Used for transferring an IPC event from the native pipeline to the IPC event channel.
+ virtual HRESULT SaveEventFromLeftSide(DebuggerIPCEvent * pEventFromLeftSide);
+
+ // Get a saved IPC event from the LS.
+ // Used for transferring an IPC event from the native pipeline to the IPC event channel.
+ virtual HRESULT GetEventFromLeftSide(DebuggerIPCEvent * pLocalManagedEvent);
+
+private:
+ DebuggerIPCControlBlock * m_pDCBBuffer; // local buffer for the DCB on the RS
+ DbgTransportTarget * m_pProxy; // connection to the debugger proxy
+ DbgTransportSession * m_pTransport; // connection to the debuggee process
+
+ // The next two fields are used for storing an IPC event from the native pipeline
+ // for the IPC event channel.
+ BYTE m_rgbLeftSideEventBuffer[CorDBIPC_BUFFER_SIZE];
+ BOOL m_fLeftSideEventAvailable;
+};
+
+// Allocate and return an old-style event channel object for this target platform.
+HRESULT NewEventChannelForThisPlatform(CORDB_ADDRESS pLeftSideDCB,
+ ICorDebugMutableDataTarget * pMutableDataTarget,
+ DWORD dwProcessId,
+ MachineInfo machineInfo,
+ IEventChannel ** ppEventChannel)
+{
+ // @dbgtodo Mac - Consider moving all of the transport logic to one place.
+ // Perhaps add a new function on DbgTransportManager.
+ HandleHolder hDummy;
+ HRESULT hr = E_FAIL;
+
+ RemoteEventChannel * pEventChannel = NULL;
+ DebuggerIPCControlBlock * pDCBBuffer = NULL;
+
+ DbgTransportTarget * pProxy = g_pDbgTransportTarget;
+ DbgTransportSession * pTransport = NULL;
+
+ hr = pProxy->GetTransportForProcess(dwProcessId, &pTransport, &hDummy);
+ if (FAILED(hr))
+ {
+ goto Label_Exit;
+ }
+
+ if (!pTransport->WaitForSessionToOpen(10000))
+ {
+ hr = CORDBG_E_TIMEOUT;
+ goto Label_Exit;
+ }
+
+ pDCBBuffer = new (nothrow) DebuggerIPCControlBlock;
+ if (pDCBBuffer == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ goto Label_Exit;
+ }
+
+ pEventChannel = new (nothrow) RemoteEventChannel(pDCBBuffer, pProxy, pTransport);
+ if (pEventChannel == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ goto Label_Exit;
+ }
+
+ _ASSERTE(SUCCEEDED(hr));
+ *ppEventChannel = pEventChannel;
+
+Label_Exit:
+ if (FAILED(hr))
+ {
+ if (pEventChannel != NULL)
+ {
+ // The IEventChannel has ownership of the proxy and the transport,
+ // so we don't need to clean them up here.
+ delete pEventChannel;
+ }
+ else
+ {
+ if (pTransport != NULL)
+ {
+ pProxy->ReleaseTransport(pTransport);
+ }
+ if (pDCBBuffer != NULL)
+ {
+ delete pDCBBuffer;
+ }
+ }
+ }
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+//
+// This is the constructor.
+//
+// Arguments:
+// pLeftSideDCB - target address of the DCB on the LS
+// pDCBBuffer - local buffer for storing the DCB on the RS; the memory is owned by this class
+// pMutableDataTarget - data target for reading from and writing to the target process's address space
+//
+
+RemoteEventChannel::RemoteEventChannel(DebuggerIPCControlBlock * pDCBBuffer,
+ DbgTransportTarget * pProxy,
+ DbgTransportSession * pTransport)
+{
+ m_pDCBBuffer = pDCBBuffer;
+ m_pProxy = pProxy;
+ m_pTransport = pTransport;
+ m_fLeftSideEventAvailable = FALSE;
+}
+
+// Inititalize the event channel.
+//
+// virtual
+HRESULT RemoteEventChannel::Init(HANDLE hTargetProc)
+{
+ return S_OK;
+}
+
+// Called when the debugger is detaching.
+//
+// virtual
+void RemoteEventChannel::Detach()
+{
+ // This is a nop for Mac debugging because we don't use RSEA/RSER.
+ return;
+}
+
+// Delete the event channel and clean up all the resources it owns. This function can only be called once.
+//
+// virtual
+void RemoteEventChannel::Delete()
+{
+ if (m_pDCBBuffer != NULL)
+ {
+ delete m_pDCBBuffer;
+ m_pDCBBuffer = NULL;
+ }
+
+ if (m_pTransport != NULL)
+ {
+ m_pProxy->ReleaseTransport(m_pTransport);
+ }
+
+ delete this;
+}
+
+// Update a single field with a value stored in the RS copy of the DCB.
+//
+// virtual
+HRESULT RemoteEventChannel::UpdateLeftSideDCBField(void * rsFieldAddr, SIZE_T size)
+{
+ _ASSERTE(m_pDCBBuffer != NULL);
+
+ // Ask the transport to update the LS DCB.
+ return m_pTransport->SetDCB(m_pDCBBuffer);
+}
+
+// Update the entire RS copy of the debugger control block by reading the LS copy.
+//
+// virtual
+HRESULT RemoteEventChannel::UpdateRightSideDCB()
+{
+ _ASSERTE(m_pDCBBuffer != NULL);
+
+ // Ask the transport to read the DCB from the Ls.
+ return m_pTransport->GetDCB(m_pDCBBuffer);
+}
+
+// Get the pointer to the RS DCB.
+//
+// virtual
+DebuggerIPCControlBlock * RemoteEventChannel::GetDCB()
+{
+ return m_pDCBBuffer;
+}
+
+// Check whether we need to wait for an acknowledgement from the LS after sending an IPC event.
+//
+// virtual
+BOOL RemoteEventChannel::NeedToWaitForAck(DebuggerIPCEvent * pEvent)
+{
+ // There are three cases to consider when sending an event over the transport:
+ //
+ // 1) asynchronous
+ // - the LS can just send the event and continue
+ //
+ // 2) synchronous, but no reply
+ // - This is different than Windows. We don't wait for an acknowledgement.
+ // Needless to say this is a semantical difference, but none of our code actually expects
+ // this type of IPC events to be synchronized.
+ //
+ // 3) synchronous, reply required:
+ // - This is the only case we need to wait for an acknowledgement in the Mac debugging case.
+ return (!pEvent->asyncSend && pEvent->replyRequired);
+}
+
+// Get a handle to wait on after sending an IPC event to the LS. The caller should call NeedToWaitForAck()
+//
+// virtual
+HANDLE RemoteEventChannel::GetRightSideEventAckHandle()
+{
+ // Delegate to the transport which does the real work.
+ return m_pTransport->GetIPCEventReadyEvent();
+}
+
+// Clean up the state if the wait for an acknowledgement is unsuccessful.
+//
+// virtual
+void RemoteEventChannel::ClearEventForLeftSide()
+{
+ // This is a nop for Mac debugging because we don't use RSEA/RSER.
+ return;
+}
+
+// Send an IPC event to the LS.
+//
+// virtual
+HRESULT RemoteEventChannel::SendEventToLeftSide(DebuggerIPCEvent * pEvent, SIZE_T eventSize)
+{
+ _ASSERTE(eventSize <= CorDBIPC_BUFFER_SIZE);
+
+ // Delegate to the transport. The event size is ignored.
+ return m_pTransport->SendEvent(pEvent);
+}
+
+// Get the reply from the LS for a previously sent IPC event.
+//
+// virtual
+HRESULT RemoteEventChannel::GetReplyFromLeftSide(DebuggerIPCEvent * pReplyEvent, SIZE_T eventSize)
+{
+ // Delegate to the transport.
+ m_pTransport->GetNextEvent(pReplyEvent, (DWORD)eventSize);
+ return S_OK;
+}
+
+// Save an IPC event from the LS.
+// Used for transferring an IPC event from the native pipeline to the IPC event channel.
+//
+// virtual
+HRESULT RemoteEventChannel::SaveEventFromLeftSide(DebuggerIPCEvent * pEventFromLeftSide)
+{
+ if (m_fLeftSideEventAvailable)
+ {
+ // We should only be saving one event at a time.
+ return E_FAIL;
+ }
+ else
+ {
+ memcpy(m_rgbLeftSideEventBuffer, reinterpret_cast<BYTE *>(pEventFromLeftSide), CorDBIPC_BUFFER_SIZE);
+ m_fLeftSideEventAvailable = TRUE;
+ return S_OK;
+ }
+}
+
+// Get a saved IPC event from the LS.
+// Used for transferring an IPC event from the native pipeline to the IPC event channel.
+//
+// virtual
+HRESULT RemoteEventChannel::GetEventFromLeftSide(DebuggerIPCEvent * pLocalManagedEvent)
+{
+ if (m_fLeftSideEventAvailable)
+ {
+ memcpy(reinterpret_cast<BYTE *>(pLocalManagedEvent), m_rgbLeftSideEventBuffer, CorDBIPC_BUFFER_SIZE);
+ m_fLeftSideEventAvailable = FALSE;
+ return S_OK;
+ }
+ else
+ {
+ // We have not saved any event.
+ return E_FAIL;
+ }
+}
diff --git a/src/debug/di/rsappdomain.cpp b/src/debug/di/rsappdomain.cpp
new file mode 100644
index 0000000000..fdfe657c57
--- /dev/null
+++ b/src/debug/di/rsappdomain.cpp
@@ -0,0 +1,1235 @@
+// 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.
+//*****************************************************************************
+// File: RsAppDomain.cpp
+//
+
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "primitives.h"
+#include "safewrap.h"
+
+#include "check.h"
+
+#include <tlhelp32.h>
+#include "wtsapi32.h"
+
+#ifndef SM_REMOTESESSION
+#define SM_REMOTESESSION 0x1000
+#endif
+
+#include "corpriv.h"
+#include "../../dlls/mscorrc/resource.h"
+#include <limits.h>
+
+
+/* ------------------------------------------------------------------------- *
+ * AppDomain class methods
+ * ------------------------------------------------------------------------- */
+
+//
+// Create a CordbAppDomain object based on a pointer to the AppDomain instance
+// in the CLR. Pre-populates some cached information about the AppDomain
+// from the CLR using DAC.
+//
+// Arguments:
+// pProcess - the CordbProcess object that this AppDomain is part of
+// vmAppDomain - the address in the CLR of the AppDomain object this corresponds to.
+// This will be used to read any additional information about the AppDomain.
+//
+// Assumptions:
+// The IMetaSig object should have been allocated by
+// IMDInternal on a valid metadata blob
+//
+//
+CordbAppDomain::CordbAppDomain(CordbProcess * pProcess, VMPTR_AppDomain vmAppDomain)
+ : CordbBase(pProcess, LsPtrToCookie(vmAppDomain.ToLsPtr()), enumCordbAppDomain),
+ m_AppDomainId(0),
+ m_breakpoints(17),
+ m_sharedtypes(3),
+ m_modules(17),
+ m_assemblies(9),
+ m_vmAppDomain(vmAppDomain)
+{
+ // This may throw out of the Ctor on error.
+
+ // @dbgtodo reliability: we should probably tolerate failures here and keep track
+ // of whether our ADID is valid or not, and requery if necessary.
+ m_AppDomainId = m_pProcess->GetDAC()->GetAppDomainId(m_vmAppDomain);
+
+ LOG((LF_CORDB,LL_INFO10000, "CAD::CAD: this:0x%x (void*)this:0x%x<%d>\n", this, (void *)this, m_AppDomainId));
+
+#ifdef _DEBUG
+ m_assemblies.DebugSetRSLock(pProcess->GetProcessLock());
+ m_modules.DebugSetRSLock(pProcess->GetProcessLock());
+ m_breakpoints.DebugSetRSLock(pProcess->GetProcessLock());
+ m_sharedtypes.DebugSetRSLock(pProcess->GetProcessLock());
+#endif
+
+}
+
+/*
+ A list of which resources owened by this object are accounted for.
+
+ RESOLVED:
+ // AddRef() in CordbHashTable::GetBase for a special InProc case
+ // AddRef() on the DB_IPCE_CREATE_APP_DOMAIN event from the LS
+ // Release()ed in Neuter
+ CordbProcess *m_pProcess;
+
+ WCHAR *m_szAppDomainName; // Deleted in ~CordbAppDomain
+
+ // Cleaned up in Neuter
+ CordbHashTable m_assemblies;
+ CordbHashTable m_sharedtypes;
+ CordbHashTable m_modules;
+ CordbHashTable m_breakpoints; // Disconnect()ed in ~CordbAppDomain
+
+ private:
+*/
+
+CordbAppDomain::~CordbAppDomain()
+{
+
+ // We expect to be Neutered before being released. Neutering will release our process ref
+ _ASSERTE(IsNeutered());
+}
+
+
+// Neutered by process. Once we're neutered, we lose our backpointer to the CordbProcess object, and
+// thus can't do things like call GetProcess() or Continue().
+void CordbAppDomain::Neuter()
+{
+ // This check prevents us from calling this twice and underflowing the internal ref count!
+ if (IsNeutered())
+ {
+ return;
+ }
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ //
+ // Disconnect any active breakpoints
+ //
+ {
+ CordbBreakpoint* entry;
+ HASHFIND find;
+
+ for (entry = m_breakpoints.FindFirst(&find);
+ entry != NULL;
+ entry = m_breakpoints.FindNext(&find))
+ {
+ entry->Disconnect();
+ }
+ }
+
+ // Mark as neutered so that our children can tell the appdomain has now
+ // exited.
+ CordbBase::Neuter();
+
+ //
+ // Purge neuter lists.
+ //
+ m_TypeNeuterList.NeuterAndClear(GetProcess());
+ m_SweepableNeuterList.NeuterAndClear(GetProcess());
+
+
+ m_assemblies.NeuterAndClear(GetProcess()->GetProcessLock());
+ m_modules.NeuterAndClear(GetProcess()->GetProcessLock());
+ m_sharedtypes.NeuterAndClear(GetProcess()->GetProcessLock());
+ m_breakpoints.NeuterAndClear(GetProcess()->GetProcessLock());
+
+}
+
+
+HRESULT CordbAppDomain::QueryInterface(REFIID id, void **ppInterface)
+{
+ if (id == IID_ICorDebugAppDomain)
+ {
+ *ppInterface = (ICorDebugAppDomain*)this;
+ }
+ else if (id == IID_ICorDebugAppDomain2)
+ {
+ *ppInterface = (ICorDebugAppDomain2*)this;
+ }
+ else if (id == IID_ICorDebugAppDomain3)
+ {
+ *ppInterface = (ICorDebugAppDomain3*)this;
+ }
+ else if (id == IID_ICorDebugAppDomain4)
+ {
+ *ppInterface = (ICorDebugAppDomain4*)this;
+ }
+ else if (id == IID_ICorDebugController)
+ *ppInterface = (ICorDebugController*)(ICorDebugAppDomain*)this;
+ else if (id == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ICorDebugAppDomain*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Ensure the AppDomain friendly name has been set.
+//
+// Return value:
+// S_OK on success, or a failure code if we couldn't read the name for some reason.
+// There shouldn't be any reason in practice for this to fail other than a corrupt
+// process image.
+//
+// Assumptions:
+// The AppDomain object has already been initialized to know about
+// it's corresponding VM appdomain.
+// InvalidateName is called whenever the name may have changed to prompt us to re-fetch.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbAppDomain::RefreshName()
+{
+ if (m_strAppDomainName.IsSet())
+ {
+ // If we already have a valid name, we're done.
+ return S_OK;
+ }
+
+ // Use DAC to get the name.
+
+ _ASSERTE(!m_vmAppDomain.IsNull());
+ IDacDbiInterface * pDac = NULL;
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ pDac = m_pProcess->GetDAC();
+
+ #ifdef _DEBUG
+ // For debug, double-check the cached value against getting the AD via an AppDomainId.
+ VMPTR_AppDomain pAppDomain = pDac->GetAppDomainFromId(m_AppDomainId);
+ _ASSERTE(m_vmAppDomain == pAppDomain);
+ #endif
+
+ // Get the actual string contents.
+ pDac->GetAppDomainFullName(m_vmAppDomain, &m_strAppDomainName);
+
+ // Now that m_strAppDomainName is set, don't fail without clearing it.
+ }
+ EX_CATCH_HRESULT(hr);
+
+ _ASSERTE(SUCCEEDED(hr) == m_strAppDomainName.IsSet());
+
+ return hr;
+}
+
+
+HRESULT CordbAppDomain::Stop(DWORD dwTimeout)
+{
+ FAIL_IF_NEUTERED(this);
+ PUBLIC_API_ENTRY(this);
+ return (m_pProcess->StopInternal(dwTimeout, this->GetADToken()));
+}
+
+HRESULT CordbAppDomain::Continue(BOOL fIsOutOfBand)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ return m_pProcess->ContinueInternal(fIsOutOfBand);
+}
+
+HRESULT CordbAppDomain::IsRunning(BOOL *pbRunning)
+{
+ PUBLIC_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pbRunning, BOOL *);
+ FAIL_IF_NEUTERED(this);
+
+ *pbRunning = !m_pProcess->GetSynchronized();
+
+ return S_OK;
+}
+
+HRESULT CordbAppDomain::HasQueuedCallbacks(ICorDebugThread *pThread, BOOL *pbQueued)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pThread,ICorDebugThread *);
+ VALIDATE_POINTER_TO_OBJECT(pbQueued,BOOL *);
+
+ return m_pProcess->HasQueuedCallbacks (pThread, pbQueued);
+}
+
+HRESULT CordbAppDomain::EnumerateThreads(ICorDebugThreadEnum **ppThreads)
+{
+ // @TODO E_NOIMPL this
+ //
+ // (use Process::EnumerateThreads and let users filter their own data)
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ ValidateOrThrow(ppThreads);
+
+ RSInitHolder<CordbEnumFilter> pThreadEnum(
+ new CordbEnumFilter(GetProcess(), GetProcess()->GetContinueNeuterList()));
+
+ GetProcess()->PrepopulateThreadsOrThrow();
+
+ RSInitHolder<CordbHashTableEnum> pEnum;
+ GetProcess()->BuildThreadEnum(this, NULL, pEnum.GetAddr());
+
+ // This builds up auxillary list. don't need pEnum after this.
+ hr = pThreadEnum->Init(pEnum, this);
+ IfFailThrow(hr);
+
+ pThreadEnum.TransferOwnershipExternal(ppThreads);
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+
+HRESULT CordbAppDomain::SetAllThreadsDebugState(CorDebugThreadState state,
+ ICorDebugThread *pExceptThisThread)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return m_pProcess->SetAllThreadsDebugState(state, pExceptThisThread);
+}
+
+HRESULT CordbAppDomain::Detach()
+{
+ PUBLIC_REENTRANT_API_ENTRY(this); // may be called from IMDA::Detach
+ FAIL_IF_NEUTERED(this);
+
+ return E_NOTIMPL;
+}
+
+HRESULT CordbAppDomain::Terminate(unsigned int exitCode)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ return E_NOTIMPL;
+}
+
+void CordbAppDomain::AddToTypeList(CordbBase *pObject)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(pObject != NULL);
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ this->m_TypeNeuterList.Add(GetProcess(), pObject);
+}
+
+
+HRESULT CordbAppDomain::CanCommitChanges(
+ ULONG cSnapshots,
+ ICorDebugEditAndContinueSnapshot *pSnapshots[],
+ ICorDebugErrorInfoEnum **pError)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbAppDomain::CommitChanges(
+ ULONG cSnapshots,
+ ICorDebugEditAndContinueSnapshot *pSnapshots[],
+ ICorDebugErrorInfoEnum **pError)
+{
+ return E_NOTIMPL;
+}
+
+
+/*
+ * GetProcess returns the process containing the app domain
+ */
+HRESULT CordbAppDomain::GetProcess(ICorDebugProcess **ppProcess)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ VALIDATE_POINTER_TO_OBJECT(ppProcess,ICorDebugProcess **);
+
+ _ASSERTE (m_pProcess != NULL);
+
+ *ppProcess = static_cast<ICorDebugProcess *> (m_pProcess);
+ m_pProcess->ExternalAddRef();
+
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Callback for assembly enumeration.
+//
+// Arguments:
+// vmDomainAssembly - new assembly to add
+// pThis - user data for CordbAppDomain to add assembly too
+//
+//
+// Assumptions:
+// Invoked as callback from code:CordbAppDomain::PrepopulateAssemblies
+//
+// Notes:
+//
+
+// static
+void CordbAppDomain::AssemblyEnumerationCallback(VMPTR_DomainAssembly vmDomainAssembly, void * pThis)
+{
+ CordbAppDomain * pAppDomain = static_cast<CordbAppDomain *> (pThis);
+ INTERNAL_DAC_CALLBACK(pAppDomain->GetProcess());
+
+ // This lookup will cause the cache to be populated if we haven't seen this assembly before.
+ pAppDomain->LookupOrCreateAssembly(vmDomainAssembly);
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Cache a new assembly
+//
+// Arguments:
+// vmDomainAssembly - new assembly to add to cache
+//
+// Return Value:
+// Pointer to Assembly in cache.
+// NULL on failure, and sets unrecoverable error.
+//
+// Assumptions:
+// Caller gaurantees assembly is not already added.
+// Called under the stop-go lock.
+//
+// Notes:
+//
+CordbAssembly * CordbAppDomain::CacheAssembly(VMPTR_DomainAssembly vmDomainAssembly)
+{
+ INTERNAL_API_ENTRY(GetProcess());
+
+ VMPTR_Assembly vmAssembly;
+ GetProcess()->GetDAC()->GetAssemblyFromDomainAssembly(vmDomainAssembly, &vmAssembly);
+
+ RSInitHolder<CordbAssembly> pAssembly(new CordbAssembly(this, vmAssembly, vmDomainAssembly));
+
+ return pAssembly.TransferOwnershipToHash(&m_assemblies);
+}
+
+CordbAssembly * CordbAppDomain::CacheAssembly(VMPTR_Assembly vmAssembly)
+{
+ INTERNAL_API_ENTRY(GetProcess());
+
+ RSInitHolder<CordbAssembly> pAssembly(new CordbAssembly(this, vmAssembly, VMPTR_DomainAssembly()));
+
+ return pAssembly.TransferOwnershipToHash(&m_assemblies);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Build up cache of assmeblies
+//
+// Arguments:
+//
+// Return Value:
+// Throws on error.
+//
+// Assumptions:
+// This is an non-invasive inspection operation called when the debuggee is stopped.
+//
+// Notes:
+// This can safely be called multiple times.
+//
+
+void CordbAppDomain::PrepopulateAssembliesOrThrow()
+{
+ INTERNAL_API_ENTRY(GetProcess());
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ if (!GetProcess()->IsDacInitialized())
+ {
+ return;
+ }
+
+ // DD-primitive that invokes a callback.
+ GetProcess()->GetDAC()->EnumerateAssembliesInAppDomain(
+ this->m_vmAppDomain,
+ CordbAppDomain::AssemblyEnumerationCallback,
+ this); // user data
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Public API tp EnumerateAssemblies enumerates all assemblies in the app domain
+//
+// Arguments:
+// ppAssemblies - OUT: get enumerator
+//
+// Return Value:
+// S_OK on success.
+//
+//
+// Notes:
+// This will prepopulate the list of assemblies (useful for non-invasive case
+// where we don't get debug event).
+//
+
+HRESULT CordbAppDomain::EnumerateAssemblies(ICorDebugAssemblyEnum **ppAssemblies)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ ValidateOrThrow(ppAssemblies);
+ *ppAssemblies = NULL;
+
+ PrepopulateAssembliesOrThrow();
+
+ RSInitHolder<CordbHashTableEnum> pEnum;
+ CordbHashTableEnum::BuildOrThrow(
+ this,
+ GetProcess()->GetContinueNeuterList(), // ownership
+ &m_assemblies,
+ IID_ICorDebugAssemblyEnum,
+ pEnum.GetAddr());
+ pEnum.TransferOwnershipExternal(ppAssemblies);
+
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+// Implement public interface
+HRESULT CordbAppDomain::GetModuleFromMetaDataInterface(
+ IUnknown *pIMetaData,
+ ICorDebugModule **ppModule)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pIMetaData, IUnknown *);
+ VALIDATE_POINTER_TO_OBJECT(ppModule, ICorDebugModule **);
+
+
+
+ HRESULT hr = S_OK;
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ *ppModule = NULL;
+
+ EX_TRY
+ {
+ CordbModule * pModule = GetModuleFromMetaDataInterface(pIMetaData);
+ _ASSERTE(pModule != NULL); // thrown on error
+
+ *ppModule = static_cast<ICorDebugModule*> (pModule);
+ pModule->ExternalAddRef();
+ }
+ EX_CATCH_HRESULT(hr);
+
+
+ return hr;
+}
+
+// Gets a CordbModule that has the given metadata interface
+//
+// Arguments:
+// pIMetaData - metadata interface
+//
+// Returns:
+// CordbModule whose associated metadata matches the metadata interface provided here
+// Throws on error. Returns non-null
+//
+CordbModule * CordbAppDomain::GetModuleFromMetaDataInterface(IUnknown *pIMetaData)
+{
+ HRESULT hr = S_OK;
+
+ RSExtSmartPtr<IMetaDataImport> pImport;
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock()); // need for module enumeration
+
+ // Grab the interface we need...
+ hr = pIMetaData->QueryInterface(IID_IMetaDataImport, (void**)&pImport);
+ if (FAILED(hr))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ // Get the mvid of the given module.
+ GUID matchMVID;
+ hr = pImport->GetScopeProps(NULL, 0, 0, &matchMVID);
+ IfFailThrow(hr);
+
+ CordbModule* pModule;
+ HASHFIND findmodule;
+
+ PrepopulateModules();
+
+ for (pModule = m_modules.FindFirst(&findmodule);
+ pModule != NULL;
+ pModule = m_modules.FindNext(&findmodule))
+ {
+ IMetaDataImport * pImportCurrent = pModule->GetMetaDataImporter(); // throws
+ _ASSERTE(pImportCurrent != NULL);
+
+ // Get the mvid of this module
+ GUID MVID;
+ hr = pImportCurrent->GetScopeProps(NULL, 0, 0, &MVID);
+ IfFailThrow(hr);
+
+ if (MVID == matchMVID)
+ {
+ return pModule;
+ }
+ }
+
+ ThrowHR(E_INVALIDARG);
+}
+
+HRESULT CordbAppDomain::EnumerateBreakpoints(ICorDebugBreakpointEnum **ppBreakpoints)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ VALIDATE_POINTER_TO_OBJECT(ppBreakpoints, ICorDebugBreakpointEnum **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ RSInitHolder<CordbHashTableEnum> pEnum;
+ CordbHashTableEnum::BuildOrThrow(
+ this,
+ GetProcess()->GetContinueNeuterList(), // ownership
+ &m_breakpoints,
+ IID_ICorDebugBreakpointEnum,
+ pEnum.GetAddr());
+
+ pEnum.TransferOwnershipExternal(ppBreakpoints);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT CordbAppDomain::EnumerateSteppers(ICorDebugStepperEnum **ppSteppers)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ VALIDATE_POINTER_TO_OBJECT(ppSteppers,ICorDebugStepperEnum **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ //
+ // !!! m_steppers may be modified while user is enumerating,
+ // if steppers complete (if process is running)
+ //
+
+ RSInitHolder<CordbHashTableEnum> pEnum;
+ CordbHashTableEnum::BuildOrThrow(
+ GetProcess(),
+ GetProcess()->GetContinueNeuterList(), // ownership
+ &(m_pProcess->m_steppers),
+ IID_ICorDebugStepperEnum,
+ pEnum.GetAddr());
+
+ pEnum.TransferOwnershipExternal(ppSteppers);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// CordbAppDomain::IsAttached - always returns true
+//
+// Arguments:
+// pfAttached - out parameter, will be set to TRUE
+//
+// Return Value:
+// CORDB_E_OBJECT_NEUTERED if the AppDomain has been neutered
+// E_INVALIDARG if pbAttached is null
+// Otherwise always returns S_OK.
+//
+// Notes:
+// Prior to V3, we used to keep track of a per-appdomain attached status.
+// Debuggers were required to explicitly attach to every AppDomain, so this
+// did not provide any actual functionality. In V3, there is no longer any
+// concept of per-AppDomain attach/detach. This API is provided for compatibility.
+//
+
+HRESULT CordbAppDomain::IsAttached(BOOL *pfAttached)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pfAttached, BOOL *);
+
+ *pfAttached = TRUE;
+
+ return S_OK;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// CordbAppDomain::Attach - does nothing
+//
+// Arguments:
+//
+// Return Value:
+// CORDB_E_OBJECT_NEUTERED if the AppDomain has been neutered
+// Otherwise always returns S_OK.
+//
+// Notes:
+// Prior to V3, we used to keep track of a per-appdomain attached status.
+// Debuggers were required to explicitly attach to every AppDomain, so this
+// did not provide any actual functionality. In V3, there is no longer any
+// concept of per-AppDomain attach/detach. This API is provided for compatibility.
+//
+
+HRESULT CordbAppDomain::Attach()
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(m_pProcess);
+
+ return S_OK;
+}
+
+/*
+ * GetName returns the name of the app domain.
+ */
+HRESULT CordbAppDomain::GetName(ULONG32 cchName,
+ ULONG32 *pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this)
+ {
+ // Some reasonable defaults
+ if (szName)
+ *szName = 0;
+
+ if (pcchName)
+ *pcchName = 0;
+
+
+ // Lazily refresh.
+ IfFailThrow(RefreshName());
+
+ const WCHAR * pName = m_strAppDomainName;
+ _ASSERTE(pName != NULL);
+
+ hr = CopyOutString(pName, cchName, pcchName, szName);
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+/*
+ * GetObject returns the runtime app domain object.
+ * Note: this is lazily initialized and may be NULL
+ */
+HRESULT CordbAppDomain::GetObject(ICorDebugValue **ppObject)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppObject,ICorDebugObjectValue **);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ _ASSERTE(!m_vmAppDomain.IsNull());
+ IDacDbiInterface * pDac = NULL;
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ pDac = m_pProcess->GetDAC();
+ VMPTR_OBJECTHANDLE vmObjHandle = pDac->GetAppDomainObject(m_vmAppDomain);
+ if (!vmObjHandle.IsNull())
+ {
+ ICorDebugReferenceValue * pRefValue = NULL;
+ hr = CordbReferenceValue::BuildFromGCHandle(this, vmObjHandle, &pRefValue);
+ *ppObject = pRefValue;
+ }
+ else
+ {
+ *ppObject = NULL;
+ hr = S_FALSE;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+/*
+ * Get the ID of the app domain.
+ */
+HRESULT CordbAppDomain::GetID (ULONG32 *pId)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ OK_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pId, ULONG32 *);
+
+ *pId = m_AppDomainId;
+
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+// Remove an assembly from the ICorDebug cache.
+//
+// Arguments:
+// vmDomainAssembly - token to remove.
+//
+// Notes:
+// This is the opposite of code:CordbAppDomain::LookupOrCreateAssembly.
+// This only need to be called at assembly unload events.
+void CordbAppDomain::RemoveAssemblyFromCache(VMPTR_DomainAssembly vmDomainAssembly)
+{
+ // This will handle if the assembly is not in the hash.
+ // This could happen if we attach right before an assembly-unload event.
+ m_assemblies.RemoveBase(VmPtrToCookie(vmDomainAssembly));
+}
+
+//---------------------------------------------------------------------------------------
+// Lookup (or create) the CordbAssembly for the given VMPTR_DomainAssembly
+//
+// Arguments:
+// vmDomainAssembly - CLR token for the Assembly.
+//
+// Returns:
+// a CordbAssembly object for the given CLR assembly. This may be from the cache,
+// or newly created if not yet in the cache.
+// Never returns NULL. Throws on error (eg, oom).
+//
+CordbAssembly * CordbAppDomain::LookupOrCreateAssembly(VMPTR_DomainAssembly vmDomainAssembly)
+{
+ CordbAssembly * pAssembly = m_assemblies.GetBase(VmPtrToCookie(vmDomainAssembly));
+ if (pAssembly != NULL)
+ {
+ return pAssembly;
+ }
+ return CacheAssembly(vmDomainAssembly);
+}
+
+
+//
+CordbAssembly * CordbAppDomain::LookupOrCreateAssembly(VMPTR_Assembly vmAssembly)
+{
+ CordbAssembly * pAssembly = m_assemblies.GetBase(VmPtrToCookie(vmAssembly));
+ if (pAssembly != NULL)
+ {
+ return pAssembly;
+ }
+ return CacheAssembly(vmAssembly);
+}
+
+
+//---------------------------------------------------------------------------------------
+// Lookup or create a module within the appdomain
+//
+// Arguments:
+// vmDomainFile - non-null module to lookup
+//
+// Returns:
+// a CordbModule object for the given cookie. Object may be from the cache, or created
+// lazily.
+// Never returns null. Throws on error.
+//
+// Notes:
+// If you don't know which appdomain the module is in, use code:CordbProcess::LookupOrCreateModule.
+//
+CordbModule* CordbAppDomain::LookupOrCreateModule(VMPTR_Module vmModule, VMPTR_DomainFile vmDomainFile)
+{
+ INTERNAL_API_ENTRY(this);
+ CordbModule * pModule;
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock()); // @dbgtodo locking: push this up.
+
+ _ASSERTE(!vmDomainFile.IsNull() || !vmModule.IsNull());
+
+ // check to see if the module is present in this app domain
+ pModule = m_modules.GetBase(vmDomainFile.IsNull() ? VmPtrToCookie(vmModule) : VmPtrToCookie(vmDomainFile));
+ if (pModule != NULL)
+ {
+ return pModule;
+ }
+
+ if (vmModule.IsNull())
+ GetProcess()->GetDAC()->GetModuleForDomainFile(vmDomainFile, &vmModule);
+
+ RSInitHolder<CordbModule> pModuleInit(new CordbModule(GetProcess(), vmModule, vmDomainFile));
+ pModule = pModuleInit.TransferOwnershipToHash(&m_modules);
+
+ // The appdomains should match.
+ GetProcess()->TargetConsistencyCheck(pModule->GetAppDomain() == this);
+
+ return pModule;
+}
+
+
+CordbModule* CordbAppDomain::LookupOrCreateModule(VMPTR_DomainFile vmDomainFile)
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(!vmDomainFile.IsNull());
+ return LookupOrCreateModule(VMPTR_Module::NullPtr(), vmDomainFile);
+}
+
+
+
+//---------------------------------------------------------------------------------------
+// Callback invoked by DAC for each module in an assembly. Used to populate RS module cache.
+//
+// Arguments:
+// vmModule - module from enumeration
+// pUserData - user data, a 'this' pointer to the CordbAssembly to add to.
+//
+// Notes:
+// This is called from code:CordbAppDomain::PrepopulateModules invoking DAC, which
+// invokes this callback.
+
+// static
+void CordbAppDomain::ModuleEnumerationCallback(VMPTR_DomainFile vmModule, void * pUserData)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ CordbAppDomain * pAppDomain = static_cast<CordbAppDomain *> (pUserData);
+ INTERNAL_DAC_CALLBACK(pAppDomain->GetProcess());
+
+ pAppDomain->LookupOrCreateModule(vmModule);
+}
+
+
+//
+// Use DAC to preopulate the list of modules for this assembly
+//
+// Notes:
+// This may pick up modules for which a load notification has not yet been dispatched.
+void CordbAppDomain::PrepopulateModules()
+{
+ INTERNAL_API_ENTRY(GetProcess());
+
+ if (!GetProcess()->IsDacInitialized())
+ {
+ return;
+ }
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ // Want to make sure we don't double-add modules.
+ // Modules for all assemblies are stored in 1 giant hash in the AppDomain, so
+ // we don't have a good way of querying if this specific assembly needs to be prepopulated.
+ // We'll check before adding each module that it's unique.
+
+ PrepopulateAssembliesOrThrow();
+
+ HASHFIND hashfind;
+
+ for (CordbAssembly * pAssembly = m_assemblies.FindFirst(&hashfind);
+ pAssembly != NULL;
+ pAssembly = m_assemblies.FindNext(&hashfind))
+ {
+
+ // DD-primitive that invokes a callback.
+ GetProcess()->GetDAC()->EnumerateModulesInAssembly(
+ pAssembly->GetDomainAssemblyPtr(),
+ CordbAppDomain::ModuleEnumerationCallback,
+ this); // user data
+
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// Get a type that represents an array or pointer of the given type.
+//
+// Arguments:
+// elementType - determines if this will be an array or a pointer
+// nRank - Rank of the array to make
+// pTypeArg - The type of array element or pointer.
+// ppResultType - OUT: out parameter to hold outgoing CordbType.
+//
+// Returns:
+// S_OK on success. Else failure.
+//
+HRESULT CordbAppDomain::GetArrayOrPointerType(CorElementType elementType,
+ ULONG32 nRank,
+ ICorDebugType * pTypeArg,
+ ICorDebugType ** ppResultType)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppResultType, ICorDebugType **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ CordbType * pResultType = NULL;
+
+ if (!(elementType == ELEMENT_TYPE_PTR && nRank == 0) &&
+ !(elementType == ELEMENT_TYPE_BYREF && nRank == 0) &&
+ !(elementType == ELEMENT_TYPE_SZARRAY && nRank == 1) &&
+ !(elementType == ELEMENT_TYPE_ARRAY))
+ {
+ return E_INVALIDARG;
+ }
+
+ HRESULT hr = CordbType::MkType(
+ this,
+ elementType,
+ (ULONG) nRank,
+ static_cast<CordbType *>(pTypeArg),
+ &pResultType);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ _ASSERTE(pResultType != NULL);
+
+ pResultType->ExternalAddRef();
+
+ *ppResultType = pResultType;
+ return hr;
+
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Get a type that represents a function pointer with signature of the types given.
+//
+// Arguments:
+// cTypeArgs - count of the number of entries in rgpTypeArgs
+// rgpTypeArgs - Array of types
+// ppResultType - OUT: out parameter to hold outgoing CordbType.
+//
+// Returns:
+// S_OK on success. Else failure.
+//
+HRESULT CordbAppDomain::GetFunctionPointerType(ULONG32 cTypeArgs,
+ ICorDebugType * rgpTypeArgs[],
+ ICorDebugType ** ppResultType)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppResultType, ICorDebugType **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // Prefast overflow check:
+ S_UINT32 allocSize = S_UINT32(cTypeArgs) * S_UINT32(sizeof(CordbType *));
+
+ if (allocSize.IsOverflow())
+ {
+ return E_INVALIDARG;
+ }
+
+ CordbType ** ppTypeInstantiations = reinterpret_cast<CordbType **>(_alloca(allocSize.Value()));
+
+ for (unsigned int i = 0; i < cTypeArgs; i++)
+ {
+ ppTypeInstantiations[i] = (CordbType *) rgpTypeArgs[i];
+ }
+
+
+ Instantiation typeInstantiation(cTypeArgs, ppTypeInstantiations);
+
+ CordbType * pType;
+
+ HRESULT hr = CordbType::MkType(this, ELEMENT_TYPE_FNPTR, &typeInstantiation, &pType);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ _ASSERTE(pType != NULL);
+
+ pType->ExternalAddRef();
+
+ *ppResultType = static_cast<ICorDebugType *>(pType);
+
+ return hr;
+
+}
+
+//
+// ICorDebugAppDomain3
+//
+
+HRESULT CordbAppDomain::GetCachedWinRTTypesForIIDs(
+ ULONG32 cGuids,
+ GUID * iids,
+ ICorDebugTypeEnum * * ppTypesEnum)
+{
+#if !defined(FEATURE_COMINTEROP)
+
+ return E_NOTIMPL;
+
+#else
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ _ASSERTE(!m_vmAppDomain.IsNull());
+
+
+ EX_TRY
+ {
+ *ppTypesEnum = NULL;
+
+ DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> dacTypes;
+ DacDbiArrayList<GUID> dacGuids;
+
+ IDacDbiInterface* pDAC = GetProcess()->GetDAC();
+
+ dacGuids.Init(iids, cGuids);
+
+ // retrieve type info from LS
+ pDAC->GetCachedWinRTTypesForIIDs(m_vmAppDomain, dacGuids, &dacTypes);
+
+ // synthesize CordbType instances
+ int cItfs = dacTypes.Count();
+ NewArrayHolder<CordbType*> pTypes(NULL);
+
+ if (cItfs > 0)
+ {
+ pTypes = new CordbType*[cItfs];
+ for (int n = 0; n < cItfs; ++n)
+ {
+ hr = CordbType::TypeDataToType(this,
+ &(dacTypes[n]),
+ &pTypes[n]);
+ }
+ }
+
+ // build a type enumerator
+ CordbTypeEnum* pTypeEnum = CordbTypeEnum::Build(this, GetProcess()->GetContinueNeuterList(), cItfs, pTypes);
+ if ( pTypeEnum == NULL )
+ {
+ IfFailThrow(E_OUTOFMEMORY);
+ }
+
+ (*ppTypesEnum) = static_cast<ICorDebugTypeEnum*> (pTypeEnum);
+ pTypeEnum->ExternalAddRef();
+
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+
+#endif // !defined(FEATURE_COMINTEROP)
+}
+
+HRESULT CordbAppDomain::GetCachedWinRTTypes(
+ ICorDebugGuidToTypeEnum * * ppTypesEnum)
+{
+#if !defined(FEATURE_COMINTEROP)
+
+ return E_NOTIMPL;
+
+#else
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ EX_TRY
+ {
+ *ppTypesEnum = NULL;
+
+ DacDbiArrayList<GUID> guids;
+ DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> types;
+
+ IDacDbiInterface* pDAC = GetProcess()->GetDAC();
+
+ // retrieve type info from LS
+ pDAC->GetCachedWinRTTypes(m_vmAppDomain, &guids, &types);
+
+ _ASSERTE(guids.Count() == types.Count());
+ if (guids.Count() != types.Count())
+ IfFailThrow(E_FAIL);
+
+ int cnt = types.Count();
+
+ RsGuidToTypeMapping* pMap = new RsGuidToTypeMapping[cnt];
+
+ for (int i = 0; i < cnt; ++i)
+ {
+ pMap[i].iid = guids[i];
+ CordbType* pType;
+ hr = CordbType::TypeDataToType(this, &(types[i]), &pType);
+ if (SUCCEEDED(hr))
+ {
+ pMap[i].spType.Assign(pType);
+ }
+ else
+ {
+ // spType stays NULL
+ }
+ }
+
+ CordbGuidToTypeEnumerator * enumerator = new CordbGuidToTypeEnumerator(GetProcess(), &pMap, cnt);
+ _ASSERTE(pMap == NULL);
+ GetProcess()->GetContinueNeuterList()->Add(GetProcess(), enumerator);
+
+ hr = enumerator->QueryInterface(IID_ICorDebugGuidToTypeEnum, reinterpret_cast<void**>(ppTypesEnum));
+ _ASSERTE(SUCCEEDED(hr));
+ IfFailThrow(hr);
+
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+
+#endif // !defined(FEATURE_COMINTEROP)
+}
+
+//-----------------------------------------------------------
+// ICorDebugAppDomain4
+//-----------------------------------------------------------
+
+HRESULT CordbAppDomain::GetObjectForCCW(CORDB_ADDRESS ccwPointer, ICorDebugValue **ppManagedObject)
+{
+#if defined(FEATURE_COMINTEROP)
+
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ VALIDATE_POINTER_TO_OBJECT(ppManagedObject, ICorDebugValue **);
+ HRESULT hr = S_OK;
+
+ *ppManagedObject = NULL;
+
+ EX_TRY
+ {
+ VMPTR_OBJECTHANDLE vmObjHandle = GetProcess()->GetDAC()->GetObjectForCCW(ccwPointer);
+ if (vmObjHandle.IsNull())
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ ICorDebugReferenceValue *pRefValue = NULL;
+ hr = CordbReferenceValue::BuildFromGCHandle(this, vmObjHandle, &pRefValue);
+ *ppManagedObject = pRefValue;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+
+#else
+
+ return E_NOTIMPL;
+
+#endif // defined(FEATURE_COMINTEROP)
+}
diff --git a/src/debug/di/rsassembly.cpp b/src/debug/di/rsassembly.cpp
new file mode 100644
index 0000000000..e390f77bc4
--- /dev/null
+++ b/src/debug/di/rsassembly.cpp
@@ -0,0 +1,320 @@
+// 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.
+//*****************************************************************************
+// File: RsAssembly.cpp
+//
+
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "primitives.h"
+#include "safewrap.h"
+
+#include "check.h"
+
+#include <tlhelp32.h>
+#include "wtsapi32.h"
+
+#ifndef SM_REMOTESESSION
+#define SM_REMOTESESSION 0x1000
+#endif
+
+#include "corpriv.h"
+#include "../../dlls/mscorrc/resource.h"
+#include <limits.h>
+
+
+/* ------------------------------------------------------------------------- *
+ * Assembly class
+ * ------------------------------------------------------------------------- */
+CordbAssembly::CordbAssembly(CordbAppDomain * pAppDomain,
+ VMPTR_Assembly vmAssembly,
+ VMPTR_DomainAssembly vmDomainAssembly)
+
+ : CordbBase(pAppDomain->GetProcess(),
+ vmDomainAssembly.IsNull() ? VmPtrToCookie(vmAssembly) : VmPtrToCookie(vmDomainAssembly),
+ enumCordbAssembly),
+ m_vmAssembly(vmAssembly),
+ m_vmDomainAssembly(vmDomainAssembly),
+ m_pAppDomain(pAppDomain)
+{
+ _ASSERTE(!vmAssembly.IsNull());
+}
+
+/*
+ A list of which resources owned by this object are accounted for.
+
+ public:
+ CordbAppDomain *m_pAppDomain; // Assigned w/o addRef(), Deleted in ~CordbAssembly
+*/
+
+CordbAssembly::~CordbAssembly()
+{
+}
+
+HRESULT CordbAssembly::QueryInterface(REFIID id, void **ppInterface)
+{
+ if (id == IID_ICorDebugAssembly)
+ *ppInterface = static_cast<ICorDebugAssembly*>(this);
+ else if (id == IID_ICorDebugAssembly2)
+ *ppInterface = static_cast<ICorDebugAssembly2*>(this);
+ else if (id == IID_IUnknown)
+ *ppInterface = static_cast<IUnknown*>( static_cast<ICorDebugAssembly*>(this) );
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+// Neutered by AppDomain
+void CordbAssembly::Neuter()
+{
+ m_pAppDomain = NULL;
+ CordbBase::Neuter();
+}
+
+
+#ifdef _DEBUG
+//---------------------------------------------------------------------------------------
+// Callback helper for code:CordbAssembly::DbgAssertAssemblyDeleted
+//
+// Arguments
+// vmDomainAssembly - domain file in the enumeration
+// pUserData - pointer to the CordbAssembly that we just got an exit event for.
+//
+
+// static
+void CordbAssembly::DbgAssertAssemblyDeletedCallback(VMPTR_DomainAssembly vmDomainAssembly, void * pUserData)
+{
+ CordbAssembly * pThis = reinterpret_cast<CordbAssembly * >(pUserData);
+ INTERNAL_DAC_CALLBACK(pThis->GetProcess());
+
+ VMPTR_DomainAssembly vmAssemblyDeleted = pThis->m_vmDomainAssembly;
+
+ CONSISTENCY_CHECK_MSGF((vmAssemblyDeleted != vmDomainAssembly),
+ ("An Assembly Unload event was sent, but the assembly still shows up in the enumeration.\n vmAssemblyDeleted=%p\n",
+ VmPtrToCookie(vmAssemblyDeleted)));
+}
+
+//---------------------------------------------------------------------------------------
+// Assert that a assembly is no longer discoverable via enumeration.
+//
+// Notes:
+// See code:IDacDbiInterface#Enumeration for rules that we're asserting.
+// This is a debug only method. It's conceptually similar to
+// code:CordbProcess::DbgAssertAppDomainDeleted.
+//
+void CordbAssembly::DbgAssertAssemblyDeleted()
+{
+ GetProcess()->GetDAC()->EnumerateAssembliesInAppDomain(
+ GetAppDomain()->GetADToken(),
+ CordbAssembly::DbgAssertAssemblyDeletedCallback,
+ this);
+}
+#endif // _DEBUG
+
+/*
+ * GetProcess returns the process containing the assembly
+ */
+HRESULT CordbAssembly::GetProcess(ICorDebugProcess **ppProcess)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppProcess, ICorDebugProcess **);
+
+ return (m_pAppDomain->GetProcess (ppProcess));
+}
+
+//
+// Returns the AppDomain that this assembly belongs to.
+//
+// Arguments:
+// ppAppDomain - a non-NULL pointer to store the AppDomain in.
+//
+// Return Value:
+// S_OK
+//
+// Notes:
+// On the debugger right-side we currently consider every assembly to belong
+// to a single AppDomain, and create multiple CordbAssembly instances (one
+// per AppDomain) to represent domain-neutral assemblies.
+//
+HRESULT CordbAssembly::GetAppDomain(ICorDebugAppDomain **ppAppDomain)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppAppDomain, ICorDebugAppDomain **);
+
+ _ASSERTE(m_pAppDomain != NULL);
+
+ *ppAppDomain = static_cast<ICorDebugAppDomain *> (m_pAppDomain);
+ m_pAppDomain->ExternalAddRef();
+
+ return S_OK;
+}
+
+
+
+/*
+ * EnumerateModules enumerates all modules in the assembly
+ */
+HRESULT CordbAssembly::EnumerateModules(ICorDebugModuleEnum **ppModules)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ ValidateOrThrow(ppModules);
+ *ppModules = NULL;
+
+ m_pAppDomain->PrepopulateModules();
+
+ RSInitHolder<CordbEnumFilter> pModEnum(
+ new CordbEnumFilter(GetProcess(), GetProcess()->GetContinueNeuterList()));
+
+ RSInitHolder<CordbHashTableEnum> pEnum;
+
+ CordbHashTableEnum::BuildOrThrow(
+ this,
+ NULL, // ownership
+ &m_pAppDomain->m_modules,
+ IID_ICorDebugModuleEnum,
+ pEnum.GetAddr());
+
+ // this will build up an auxillary list. Don't need pEnum after this.
+ hr = pModEnum->Init(pEnum, this);
+ IfFailThrow(hr);
+
+ pModEnum.TransferOwnershipExternal(ppModules);
+
+ }
+ PUBLIC_API_END(hr);
+
+ return hr;
+}
+
+
+/*
+ * GetCodeBase returns the code base used to load the assembly
+ */
+HRESULT CordbAssembly::GetCodeBase(ULONG32 cchName,
+ ULONG32 *pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(szName, WCHAR, cchName, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcchName, ULONG32 *);
+
+ return E_NOTIMPL;
+}
+
+//
+// Gets the filename of the assembly
+//
+// Arguments:
+// cchName - number of characters available in szName, or 0 to query length
+// pcchName - optional pointer to store the real length of the filename
+// szName - buffer in which to copy the filename, or NULL if cchName is 0.
+//
+// Return value:
+// S_OK on success (even if there is no filename).
+// An error code if the filename could not be read for the assembly. This should
+// not happen unless the target is corrupt.
+//
+// Notes:
+// In-memory assemblies do not have a filename. In that case, for compatibility
+// this returns success and the string "<unknown>". We may want to change this
+// behavior in the future.
+//
+HRESULT CordbAssembly::GetName(ULONG32 cchName,
+ ULONG32 *pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(szName, WCHAR, cchName, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcchName, ULONG32 *);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Lazily initialize our cache of the assembly filename.
+ // Note that if this fails, we'll try again next time this is called.
+ // This can be convenient for transient errors and debugging purposes, but could cause a
+ // performance problem if failure was common (it should not be).
+ if (!m_strAssemblyFileName.IsSet())
+ {
+ IDacDbiInterface * pDac = m_pProcess->GetDAC(); // throws
+ BOOL fNonEmpty = pDac->GetAssemblyPath(m_vmAssembly, &m_strAssemblyFileName); // throws
+ _ASSERTE(m_strAssemblyFileName.IsSet());
+
+
+ if (!fNonEmpty)
+ {
+ // File name is empty (eg. for an in-memory assembly)
+ _ASSERTE(m_strAssemblyFileName.IsEmpty());
+
+ // Construct a fake name
+ // This seems unwise - the assembly doesn't have a filename, we should probably just return
+ // an empty string and S_FALSE. This is a common case (in-memory assemblies), I don't see any reason to
+ // fake up a filename to pretend that it has a disk location when it doesn't.
+ // But I don't want to break tests at the moment that expect this.
+ // Note that all assemblies have a simple metadata name - perhaps we should have an additional API for that.
+ m_strAssemblyFileName.AssignCopy(W("<unknown>"));
+ }
+ }
+
+ // We should now have a non-empty string
+ _ASSERTE(m_strAssemblyFileName.IsSet());
+ _ASSERTE(!m_strAssemblyFileName.IsEmpty());
+
+ // Copy it out to our caller
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ return CopyOutString(m_strAssemblyFileName, cchName, pcchName, szName);
+}
+
+HRESULT CordbAssembly::IsFullyTrusted( BOOL *pbFullyTrusted )
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ VALIDATE_POINTER_TO_OBJECT(pbFullyTrusted, BOOL*);
+
+ if (m_vmDomainAssembly.IsNull())
+ return E_UNEXPECTED;
+
+ // Check for cached result
+ if( m_foptIsFullTrust.HasValue() )
+ {
+ *pbFullyTrusted = m_foptIsFullTrust.GetValue();
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+
+ CordbProcess * pProcess = m_pAppDomain->GetProcess();
+ IDacDbiInterface * pDac = pProcess->GetDAC();
+
+ BOOL fIsFullTrust = pDac->IsAssemblyFullyTrusted(m_vmDomainAssembly);
+
+ // Once the trust level of an assembly is known, it cannot change.
+ m_foptIsFullTrust = fIsFullTrust;
+
+ *pbFullyTrusted = fIsFullTrust;
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
diff --git a/src/debug/di/rsclass.cpp b/src/debug/di/rsclass.cpp
new file mode 100644
index 0000000000..f0cdda4a0b
--- /dev/null
+++ b/src/debug/di/rsclass.cpp
@@ -0,0 +1,1194 @@
+// 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.
+//*****************************************************************************
+
+//
+// File: class.cpp
+//
+//*****************************************************************************
+#include "stdafx.h"
+
+// We have an assert in ceemain.cpp that validates this assumption
+#define FIELD_OFFSET_NEW_ENC_DB 0x07FFFFFB
+
+#include "winbase.h"
+#include "corpriv.h"
+
+
+
+//-----------------------------------------------------------------------------
+// class CordbClass
+// Represents a IL-Class in the debuggee, eg: List<T>, System.Console, etc
+//
+// Parameters:
+// m - module that the class is contained in.
+// classMetadataToken - metadata token for the class, scoped to module m.
+//-----------------------------------------------------------------------------
+CordbClass::CordbClass(CordbModule *m, mdTypeDef classMetadataToken)
+ : CordbBase(m->GetProcess(), classMetadataToken, enumCordbClass),
+ m_loadLevel(Constructed),
+ m_fLoadEventSent(FALSE),
+ m_fHasBeenUnloaded(false),
+ m_pModule(m),
+ m_token(classMetadataToken),
+ m_fIsValueClassKnown(false),
+ m_fIsValueClass(false),
+ m_fHasTypeParams(false),
+ m_continueCounterLastSync(0),
+ m_fCustomNotificationsEnabled(false)
+{
+ m_classInfo.Clear();
+}
+
+
+/*
+ A list of which resources owned by this object are accounted for.
+
+ HANDLED:
+ CordbModule* m_module; // Assigned w/o AddRef()
+ FieldData *m_fields; // Deleted in ~CordbClass
+ CordbHangingFieldTable m_hangingFieldsStatic; // by value, ~CHashTableAndData frees
+*/
+
+
+//-----------------------------------------------------------------------------
+// Destructor for CordbClass
+//-----------------------------------------------------------------------------
+CordbClass::~CordbClass()
+{
+ // We should have been explicitly neutered before our internal ref went to 0.
+ _ASSERTE(IsNeutered());
+}
+
+//-----------------------------------------------------------------------------
+// Neutered by CordbModule
+// See CordbBase::Neuter for semantics.
+//-----------------------------------------------------------------------------
+void CordbClass::Neuter()
+{
+ // Reduce the reference count on the type object for this class
+ m_type.Clear();
+ CordbBase::Neuter();
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Standard IUnknown::QI implementation.
+// See IUnknown::QI for standard semantics.
+//-----------------------------------------------------------------------------
+HRESULT CordbClass::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugClass)
+ {
+ *pInterface = static_cast<ICorDebugClass*>(this);
+ }
+ else if (id == IID_ICorDebugClass2)
+ {
+ *pInterface = static_cast<ICorDebugClass2*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugClass*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Get a ICorDebugValue for a static field on this class.
+//
+// Parameters:
+// fieldDef - metadata token for field on this class. Can not be from an
+// inherited class.
+// pFrame - frame used to resolve Thread-static, AppDomain-static, etc.
+// ppValue - OUT: gets value of the field.
+//
+// Returns:
+// S_OK on success.
+// CORDBG_E_STATIC_VAR_NOT_AVAILABLE
+//-----------------------------------------------------------------------------
+HRESULT CordbClass::GetStaticFieldValue(mdFieldDef fieldDef,
+ ICorDebugFrame *pFrame,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ *ppValue = NULL;
+ BOOL fEnCHangingField = FALSE;
+
+
+ IMetaDataImport * pImport = NULL;
+ EX_TRY
+ {
+ pImport = GetModule()->GetMetaDataImporter(); // throws
+
+ // Validate the token.
+ if (!pImport->IsValidToken(fieldDef) || (TypeFromToken(fieldDef) != mdtFieldDef))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ // Make sure we have enough info about the class.
+ Init();
+
+ // Uninstantiated generics (eg, Foo<T>) don't have static data. Must use instantiated (eg Foo<int>)
+ // But all CordbClass instances are uninstantiated. So this should fail for all generic types.
+ // Normally, debuggers should be using ICorDebugType instead.
+ // Though in the forward compat case, they'll hit this.
+ if (HasTypeParams())
+ {
+ ThrowHR(CORDBG_E_STATIC_VAR_NOT_AVAILABLE);
+ }
+
+
+ // Lookup the field given its metadata token.
+ FieldData *pFieldData;
+
+ hr = GetFieldInfo(fieldDef, &pFieldData);
+
+ // This field was added by EnC, need to use EnC specific code path
+ if (hr == CORDBG_E_ENC_HANGING_FIELD)
+ {
+ // Static fields added with EnC hang off the EnCFieldDesc
+ hr = GetEnCHangingField(fieldDef,
+ &pFieldData,
+ NULL);
+
+ if (SUCCEEDED(hr))
+ {
+ fEnCHangingField = TRUE;
+ }
+ // Note: the FieldOffset in pFieldData has been cooked to produce
+ // the correct address of the field in the syncBlock.
+ // @todo: extend Debugger_IPCEFieldData so we don't have to cook the offset here
+ }
+
+ IfFailThrow(hr);
+
+ {
+ Instantiation emptyInst;
+
+ hr = CordbClass::GetStaticFieldValue2(GetModule(),
+ pFieldData,
+ fEnCHangingField,
+ &emptyInst,
+ pFrame,
+ ppValue);
+ // Let hr fall through
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // Translate Failure HRs.
+ if (pImport != NULL)
+ {
+ hr = CordbClass::PostProcessUnavailableHRESULT(hr, pImport, fieldDef);
+ }
+
+ return hr;
+
+}
+
+//-----------------------------------------------------------------------------
+// Common helper for accessing statics from both CordbClass and CordbType.
+//
+// Arguments:
+// pModule - module containing the class
+// pFieldData - field data describing the field (this is correlated to a
+// mdFieldDef, but has more specific data)
+// fEnCHangingField - field storage hangs off the FieldDesc for EnC
+// pInst - generic instantiation.
+// pFrame - frame used for context for Thread-static, AD-static, etc.
+// ppValue - OUT: out parameter to get value.
+//
+// Returns:
+// S_OK on success.
+// CORDBG_E_FIELD_NOT_STATIC - if field isn't static.
+// CORDBG_E_STATIC_VAR_NOT_AVAILABLE - if field storage is not available.
+// Else some other failure.
+/* static */
+HRESULT CordbClass::GetStaticFieldValue2(CordbModule * pModule,
+ FieldData * pFieldData,
+ BOOL fEnCHangingField,
+ const Instantiation * pInst,
+ ICorDebugFrame * pFrame,
+ ICorDebugValue ** ppValue)
+{
+ FAIL_IF_NEUTERED(pModule);
+ INTERNAL_SYNC_API_ENTRY(pModule->GetProcess());
+ _ASSERTE((pModule->GetProcess()->GetShim() == NULL) || pModule->GetProcess()->GetSynchronized());
+ HRESULT hr = S_OK;
+
+ if (!pFieldData->m_fFldIsStatic)
+ {
+ return CORDBG_E_FIELD_NOT_STATIC;
+ }
+
+ CORDB_ADDRESS pRmtStaticValue = NULL;
+ CordbProcess * pProcess = pModule->GetProcess();
+
+ if (pFieldData->m_fFldIsCollectibleStatic)
+ {
+ EX_TRY
+ {
+ pRmtStaticValue = pProcess->GetDAC()->GetCollectibleTypeStaticAddress(pFieldData->m_vmFieldDesc,
+ pModule->GetAppDomain()->GetADToken());
+ }
+ EX_CATCH_HRESULT(hr);
+ if(FAILED(hr))
+ {
+ return hr;
+ }
+ }
+ else if (!pFieldData->m_fFldIsTLS && !pFieldData->m_fFldIsContextStatic)
+ {
+ // Statics never move, so we always address them using their absolute address.
+ _ASSERTE(pFieldData->OkToGetOrSetStaticAddress());
+ pRmtStaticValue = pFieldData->GetStaticAddress();
+ }
+ else
+ {
+ // We've got a thread or context local static
+
+ if( fEnCHangingField )
+ {
+ // fEnCHangingField is set for fields added with EnC which hang off the FieldDesc.
+ // Thread-local and context-local statics cannot be added with EnC, so we shouldn't be here
+ // if this is an EnC field is thread- or context-local.
+ _ASSERTE(!pFieldData->m_fFldIsTLS );
+ _ASSERTE(!pFieldData->m_fFldIsContextStatic );
+ }
+ else
+ {
+ if (pFrame == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ CordbFrame * pRealFrame = CordbFrame::GetCordbFrameFromInterface(pFrame);
+ _ASSERTE(pRealFrame != NULL);
+
+ // Get the thread we are working on
+ CordbThread * pThread = pRealFrame->m_pThread;
+
+ EX_TRY
+ {
+ pRmtStaticValue = pProcess->GetDAC()->GetThreadOrContextStaticAddress(pFieldData->m_vmFieldDesc,
+ pThread->m_vmThreadToken);
+ }
+ EX_CATCH_HRESULT(hr);
+ if(FAILED(hr))
+ {
+ return hr;
+ }
+
+ }
+ }
+
+ if (pRmtStaticValue == NULL)
+ {
+ // type probably wasn't loaded yet.
+ // The debugger may chose to func-eval the creation of an instance of this type and try again.
+ return CORDBG_E_STATIC_VAR_NOT_AVAILABLE;
+ }
+
+ SigParser sigParser;
+ hr = S_OK;
+ EX_TRY
+ {
+ hr = pFieldData->GetFieldSignature(pModule, &sigParser);
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+
+ CordbType * pType;
+ IfFailRet (CordbType::SigToType(pModule, &sigParser, pInst, &pType));
+
+ bool fIsValueClass = false;
+ EX_TRY
+ {
+ fIsValueClass = pType->IsValueType(); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+
+ // Static value classes are stored as handles so that GC can deal with them properly. Thus, we need to follow the
+ // handle like an objectref. Do this by forcing CreateValueByType to think this is an objectref. Note: we don't do
+ // this for value classes that have an RVA, since they're layed out at the RVA with no handle.
+ bool fIsBoxed = (fIsValueClass &&
+ !pFieldData->m_fFldIsRVA &&
+ !pFieldData->m_fFldIsPrimitive &&
+ !pFieldData->m_fFldIsTLS &&
+ !pFieldData->m_fFldIsContextStatic);
+
+ TargetBuffer remoteValue(pRmtStaticValue, CordbValue::GetSizeForType(pType, fIsBoxed ? kBoxed : kUnboxed));
+ ICorDebugValue * pValue;
+
+ EX_TRY
+ {
+ CordbValue::CreateValueByType(pModule->GetAppDomain(),
+ pType,
+ fIsBoxed,
+ remoteValue,
+ MemoryRange(NULL, 0),
+ NULL,
+ &pValue); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (SUCCEEDED(hr))
+ {
+ *ppValue = pValue;
+ }
+
+ return hr;
+}
+
+
+//-----------------------------------------------------------------------------
+// Public method to build a CordbType from a CordbClass.
+// This is used to build up generic types. Eg, build:
+// List<T> + { int } --> List<int>
+//
+// Arguments:
+// elementType - element type. Either ELEMENT_TYPE_CLASS, or ELEMENT_TYPE_VALUETYPE.
+// We could technically figure this out from the metadata (by looking if it derives
+// from System.ValueType).
+// cTypeArgs - number of elements in rgpTypeArgs array
+// rgpTypeArgs - array for type args.
+// ppType - OUT: out parameter to hold resulting type.
+//
+// Returns:
+// S_OK on success. Else false.
+//
+HRESULT CordbClass::GetParameterizedType(CorElementType elementType,
+ ULONG32 cTypeArgs,
+ ICorDebugType * rgpTypeArgs[],
+ ICorDebugType ** ppType)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppType, ICorDebugType **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // Note: Do not call Init() to find out if its a VC or not.
+ // Rather expect the client to tell us. This means the debug client
+ // can describe type instantiations not yet seen in the EE.
+
+ if ((elementType != ELEMENT_TYPE_CLASS) && (elementType != ELEMENT_TYPE_VALUETYPE))
+ {
+ return E_INVALIDARG;
+ }
+
+ // Prefast overflow check:
+ S_UINT32 allocSize = S_UINT32( cTypeArgs ) * S_UINT32( sizeof(CordbType *) );
+
+ if (allocSize.IsOverflow())
+ {
+ return E_INVALIDARG;
+ }
+
+ CordbAppDomain * pClassAppDomain = GetAppDomain();
+
+ // Note: casting from (ICorDebugType **) to (CordbType **) is not valid.
+ // Offsets may differ. Copy and validate the type array.
+ CordbType ** ppArgTypes = reinterpret_cast<CordbType **>(_alloca( allocSize.Value()));
+
+ for (unsigned int i = 0; i < cTypeArgs; i++)
+ {
+ ppArgTypes[i] = static_cast<CordbType *>( rgpTypeArgs[i] );
+
+ CordbAppDomain * pArgAppDomain = ppArgTypes[i]->GetAppDomain();
+
+ if ((pArgAppDomain != NULL) && (pArgAppDomain != pClassAppDomain))
+ {
+ return CORDBG_E_APPDOMAIN_MISMATCH;
+ }
+ }
+
+ {
+ CordbType * pResultType;
+
+ Instantiation typeInstantiation(cTypeArgs, ppArgTypes);
+
+ HRESULT hr = CordbType::MkType(pClassAppDomain, elementType, this, &typeInstantiation, &pResultType);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ *ppType = pResultType;
+ }
+
+ _ASSERTE(*ppType);
+
+ if (*ppType)
+ {
+ (*ppType)->AddRef();
+ }
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Returns true if the field is a static literal.
+// In this case, the debugger should get the value from the metadata.
+//-----------------------------------------------------------------------------
+bool IsFieldStaticLiteral(IMetaDataImport *pImport, mdFieldDef fieldDef)
+{
+ DWORD dwFieldAttr;
+ HRESULT hr2 = pImport->GetFieldProps(
+ fieldDef,
+ NULL,
+ NULL,
+ 0,
+ NULL,
+ &dwFieldAttr,
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ 0);
+
+ if (SUCCEEDED(hr2) && IsFdLiteral(dwFieldAttr))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Filter to determine a more descriptive failing HResult for a field lookup.
+//
+// Parameters:
+// hr - incoming ambiguous HR.
+// pImport - metadata importer for this class.
+// feildDef - field being looked up.
+//
+// Returns:
+// hr - the incoming HR if no further HR can be determined.
+// else another failing HR that it judged to be more specific that the incoming HR.
+//-----------------------------------------------------------------------------
+HRESULT CordbClass::PostProcessUnavailableHRESULT(HRESULT hr,
+ IMetaDataImport *pImport,
+ mdFieldDef fieldDef)
+{
+ CONTRACTL
+ {
+ NOTHROW; // just translates an HR. shouldn't need to throw.
+ }
+ CONTRACTL_END;
+
+ if (hr == CORDBG_E_FIELD_NOT_AVAILABLE)
+ {
+ if (IsFieldStaticLiteral(pImport, fieldDef))
+ {
+ return CORDBG_E_VARIABLE_IS_ACTUALLY_LITERAL;
+ }
+ }
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Public method to get the Module that this class lives in.
+//
+// Parameters:
+// ppModule - OUT: holds module that this class gets in.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbClass::GetModule(ICorDebugModule **ppModule)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppModule, ICorDebugModule **);
+
+ *ppModule = static_cast<ICorDebugModule*> (m_pModule);
+ m_pModule->ExternalAddRef();
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Get the mdTypeDef token that this class corresponds to.
+//
+// Parameters:
+// pTypeDef - OUT: out param to get typedef token.
+//
+// Returns:
+// S_OK - on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbClass::GetToken(mdTypeDef *pTypeDef)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pTypeDef, mdTypeDef *);
+
+ _ASSERTE(TypeFromToken(m_token) == mdtTypeDef);
+
+ *pTypeDef = m_token;
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Set the JMC status on all of our member functions.
+// The current implementation just uses the metadata to enumerate all
+// methods and then calls SetJMCStatus on each method.
+// This isn't great perf, but this should never be needed in a
+// perf-critical situation.
+//
+// Parameters:
+// fIsUserCode - true to set entire class to user code. False to set to
+// non-user code.
+//
+// Returns:
+// S_OK on success. On failure, the user-code status of the methods in the
+// class is random.
+//-----------------------------------------------------------------------------
+HRESULT CordbClass::SetJMCStatus(BOOL fIsUserCode)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // Get the member functions via a meta data interface
+ CordbModule * pModule = GetModule();
+
+ // Ensure that our process is in a sane state.
+ CordbProcess * pProcess = pModule->GetProcess();
+ _ASSERTE(pProcess != NULL);
+
+ IMetaDataImport * pImport = NULL;
+ HCORENUM phEnum = 0;
+
+ HRESULT hr = S_OK;
+
+ mdMethodDef rTokens[100];
+ ULONG i;
+ ULONG count;
+
+ EX_TRY
+ {
+ pImport = pModule->GetMetaDataImporter();
+ do
+ {
+ hr = pImport->EnumMethods(&phEnum, m_token, rTokens, NumItems(rTokens), &count);
+ IfFailThrow(hr);
+
+ for (i = 0; i < count; i++)
+ {
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ // Need the ICorDebugFunction to query for JMC status.
+ CordbFunction * pFunction = pModule->LookupOrCreateFunctionLatestVersion(rTokens[i]);
+
+ lockHolder.Release(); // Must release before sending an IPC event
+ hr = pFunction->SetJMCStatus(fIsUserCode);
+ IfFailThrow(hr);
+ }
+ }
+ while (count > 0);
+
+ _ASSERTE(SUCCEEDED(hr));
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if ((pImport != NULL) && (phEnum != 0))
+ {
+ pImport->CloseEnum(phEnum);
+ }
+
+ return hr;
+
+}
+
+//-----------------------------------------------------------------------------
+// We have to go the the EE to find out if a class is a value
+// class or not. This is because there is no flag for this, but rather
+// it depends on whether the class subclasses System.ValueType (apart
+// from System.Enum...). Replicating all that resoultion logic
+// does not seem like a good plan.
+//
+// We also accept other "evidence" that the class is or isn't a VC, in
+// particular:
+// - It is definitely a VC if it has been used after a
+// E_T_VALUETYPE in a signature.
+// - It is definitely not a VC if it has been used after a
+// E_T_CLASS in a signature.
+// - It is definitely a VC if it has been used in combination with
+// E_T_VALUETYPE in one of COM API operations that take both
+// a ICorDebugClass and a CorElementType (e.g. GetParameterizedType)
+//
+// !!!Note the following!!!!
+// - A class may still be a VC even if it has been
+// used in combination with E_T_CLASS in one of COM API operations that take both
+// a ICorDebugClass and a CorElementType (e.g. GetParameterizedType).
+// We allow the user of the API to specify E_T_CLASS when the VC status
+// is not known or is not important.
+//
+// Return Value:
+// indicates whether this is a value-class
+//
+// Notes:
+// Throws CORDBG_E_CLASS_NOT_LOADED or synchronization errors on failure
+//-----------------------------------------------------------------------------
+bool CordbClass::IsValueClass()
+{
+ INTERNAL_API_ENTRY(this);
+ THROW_IF_NEUTERED(this);
+
+ if (!m_fIsValueClassKnown)
+ {
+ ATT_REQUIRE_STOPPED_MAY_FAIL_OR_THROW(GetProcess(), ThrowHR);
+ Init();
+ }
+ return m_fIsValueClass;
+}
+
+//-----------------------------------------------------------------------------
+// Get a CordbType for the 'this' pointer of a method in a CordbClass.
+// The 'this' pointer is argument #0 in an instance method.
+//
+// For ReferenceTypes (ELEMENT_TYPE_CLASS), the 'this' pointer is just a
+// normal reference, and so GetThisType() behaves like GetParameterizedType().
+// For ValueTypes, the 'this' pointer is a byref.
+//
+// Arguments:
+// pInst - instantiation info (eg, the type parameters) to produce CordbType
+// ppResultType - OUT: out parameter to hold outgoing CordbType.
+//
+// Returns:
+// S_OK on success. Else failure.
+//
+HRESULT CordbClass::GetThisType(const Instantiation * pInst, CordbType ** ppResultType)
+{
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+ // Note: We have to call Init() here to find out if it really a VC or not.
+ bool fIsValueClass = false;
+ EX_TRY
+ {
+ fIsValueClass = IsValueClass();
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ if (fIsValueClass)
+ {
+ CordbType *pType;
+
+ hr = CordbType::MkType(GetAppDomain(), // OK: this E_T_VALUETYPE will be normalized by MkType
+ ELEMENT_TYPE_VALUETYPE,
+ this,
+ pInst,
+ &pType);
+
+ if (!SUCCEEDED(hr))
+ {
+ return hr;
+ }
+
+ hr = CordbType::MkType(GetAppDomain(), ELEMENT_TYPE_BYREF, 0, pType, ppResultType);
+
+ if (!SUCCEEDED(hr))
+ {
+ return hr;
+ }
+ }
+ else
+ {
+ hr = CordbType::MkType(GetAppDomain(), ELEMENT_TYPE_CLASS, this, pInst, ppResultType);
+
+ if (!SUCCEEDED(hr))
+ {
+ return hr;
+ }
+ }
+
+ return hr;
+}
+
+
+//-----------------------------------------------------------------------------
+// Initialize the CordbClass.
+// This will collect all the field information via the DAC, and also determine
+// whether this Type is a ReferenceType or ValueType.
+//
+// Parameters:
+// fForceInit - if true, always reinitialize. If false, may skip
+// initialization if we believe we already have the info.
+//
+// Note:
+// Throws CORDBG_E_CLASS_NOT_LOADED on failure
+//-----------------------------------------------------------------------------
+void CordbClass::Init(ClassLoadLevel desiredLoadLevel)
+{
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess());
+
+ CordbProcess * pProcess = GetProcess();
+ IDacDbiInterface* pDac = pProcess->GetDAC();
+
+ // If we've done a continue since the last time we got hanging static fields,
+ // we should clear out our cache, since everything may have moved.
+ if (m_continueCounterLastSync < GetProcess()->m_continueCounter)
+ {
+ m_hangingFieldsStatic.Clear();
+ m_continueCounterLastSync = GetProcess()->m_continueCounter;
+ }
+
+ if (m_loadLevel < desiredLoadLevel)
+ {
+ // reset everything
+ m_loadLevel = Constructed;
+ m_fIsValueClass = false;
+ m_fIsValueClassKnown = false;
+ m_fHasTypeParams = false;
+ m_classInfo.Clear();
+ // @dbgtodo Microsoft inspection: declare a constant to replace badbad
+ m_classInfo.m_objectSize = 0xbadbad;
+ VMPTR_TypeHandle vmTypeHandle = VMPTR_TypeHandle::NullPtr();
+
+ // basic info load level
+ if(desiredLoadLevel >= BasicInfo)
+ {
+ vmTypeHandle = pDac->GetTypeHandle(m_pModule->GetRuntimeModule(), GetToken());
+ SetIsValueClass(pDac->IsValueType(vmTypeHandle));
+ m_fHasTypeParams = !!pDac->HasTypeParams(vmTypeHandle);
+ m_loadLevel = BasicInfo;
+ }
+
+ // full info load level
+ if(desiredLoadLevel == FullInfo)
+ {
+ VMPTR_AppDomain vmAppDomain = VMPTR_AppDomain::NullPtr();
+ VMPTR_DomainFile vmDomainFile = m_pModule->GetRuntimeDomainFile();
+ if (!vmDomainFile.IsNull())
+ {
+ DomainFileInfo info;
+ pDac->GetDomainFileData(vmDomainFile, &info);
+ vmAppDomain = info.vmAppDomain;
+ }
+ pDac->GetClassInfo(vmAppDomain, vmTypeHandle, &m_classInfo);
+
+ BOOL fGotUnallocatedStatic = GotUnallocatedStatic(&m_classInfo.m_fieldList);
+
+ // if we have an unallocated static don't record that we reached FullInfo stage
+ // this seems pretty ugly but I don't want to bite off cleaning this up just yet
+ // Not saving the FullInfo stage effectively means future calls to Init() will
+ // re-init everything and some parts of DBI may be depending on that re-initialization
+ // with alternate data in order to operate correctly
+ if(!fGotUnallocatedStatic)
+ m_loadLevel = FullInfo;
+ }
+ }
+} // CordbClass::Init
+
+// determine if any fields for a type are unallocated statics
+BOOL CordbClass::GotUnallocatedStatic(DacDbiArrayList<FieldData> * pFieldList)
+{
+ BOOL fGotUnallocatedStatic = FALSE;
+ int count = 0;
+ while ((count < pFieldList->Count()) && !fGotUnallocatedStatic )
+ {
+ if ((*pFieldList)[count].OkToGetOrSetStaticAddress() &&
+ (*pFieldList)[count].GetStaticAddress() == NULL )
+ {
+ // The address for a regular static field isn't available yet
+ // How can this happen? Statics appear to get allocated during domain load.
+ // There may be some lazieness or a race-condition involved.
+ fGotUnallocatedStatic = TRUE;
+ }
+ ++count;
+ }
+ return fGotUnallocatedStatic;
+} // CordbClass::GotUnallocatedStatic
+
+/*
+ * FieldData::GetFieldSignature
+ *
+ * Get the field's full metadata signature. This may be cached, but for dynamic modules we'll always read it from
+ * the metadata.
+ *
+ * Parameters:
+ * pModule - pointer to the module that contains the field
+ *
+ * pSigParser - OUT: the full signature for the field.
+ *
+ * Returns:
+ * HRESULT for success or failure.
+ *
+ */
+HRESULT FieldData::GetFieldSignature(CordbModule *pModule,
+ SigParser *pSigParser)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ INTERNAL_SYNC_API_ENTRY(pModule->GetProcess());
+
+ HRESULT hr = S_OK;
+
+ IMetaDataImport * pImport = pModule->GetMetaDataImporter(); // throws;
+
+ PCCOR_SIGNATURE fieldSignature = NULL;
+ ULONG size = ((ULONG) -1);
+
+ _ASSERTE(pSigParser != NULL);
+
+ // If the module is dynamic, there had better not be a cached field signature.
+ _ASSERTE(!pModule->IsDynamic() || (m_fldSignatureCache == NULL));
+
+ // If the field signature cache is null, or if this is a dynamic module, then go read the signature from the
+ // matadata. We always read from the metadata for dynamic modules because our metadata blob is constantly
+ // getting deleted and re-allocated. If we kept a pointer to the signature, we'd end up pointing to bad data.
+ if (m_fldSignatureCache == NULL)
+ {
+ // Go to the metadata for all fields: previously the left-side tranferred over
+ // single-byte signatures as part of the field info. Since the left-side
+ // goes to the metadata anyway, and we already fetch plenty of other metadata,
+ // I don't believe that fetching it here instead of transferring it over
+ // is going to slow things down at all, and
+ // in any case will not be where the primary optimizations lie...
+
+ IfFailRet(pImport->GetFieldProps(m_fldMetadataToken, NULL, NULL, 0, NULL, NULL,
+ &fieldSignature,
+ &size,
+ NULL, NULL, NULL));
+
+ // Point past the calling convention
+ CorCallingConvention conv;
+
+ // Move pointer,
+ BYTE * pOldPtr = (BYTE*) fieldSignature;
+ conv = (CorCallingConvention) CorSigUncompressData(fieldSignature);
+ _ASSERTE(conv == IMAGE_CEE_CS_CALLCONV_FIELD);
+ size -= (ULONG) (((BYTE*) fieldSignature) - pOldPtr); // since we updated filedSignature, adjust size
+
+ // Although the pointer will keep updating, the size should be the same. So we assert that.
+ _ASSERTE((m_fldSignatureCacheSize == 0) || (m_fldSignatureCacheSize == size));
+
+ // Cache the value for non-dynamic modules, so this is faster later.
+ // Since we're caching in a FieldData, we can't store the actual SigParser object.
+ if (!pModule->IsDynamic())
+ {
+ m_fldSignatureCache = fieldSignature;
+ m_fldSignatureCacheSize = size;
+ }
+ }
+ else
+ {
+ // We have a cached value, so return it. Note: we should never have a cached value for a field in a dynamic
+ // module.
+ CONSISTENCY_CHECK_MSGF((!pModule->IsDynamic()),
+ ("We should never cache a field signature in a dynamic module! Module=%p This=%p",
+ pModule, this));
+
+ fieldSignature = m_fldSignatureCache;
+ size = m_fldSignatureCacheSize;
+ }
+
+ _ASSERTE(fieldSignature != NULL);
+ _ASSERTE(size != ((ULONG) -1));
+ *pSigParser = SigParser(fieldSignature, size);
+ return hr;
+}
+
+// CordbClass::InitEnCFieldInfo
+// Initializes an instance of EnCHangingFieldInfo.
+// Arguments:
+// input: fStatic - flag to indicate whether the EnC field is static
+// pObject - For instance fields, the Object instance containing the the sync-block.
+// For static fields (if this is being called from GetStaticFieldValue) object is NULL.
+// fieldToken - token for the EnC field
+// metadataToken - metadata token for this instance of CordbClass
+// output: pEncField - the fields of this class will be appropriately initialized
+void CordbClass::InitEnCFieldInfo(EnCHangingFieldInfo * pEncField,
+ BOOL fStatic,
+ CordbObjectValue * pObject,
+ mdFieldDef fieldToken,
+ mdTypeDef classToken)
+{
+ IDacDbiInterface * pInterface = GetProcess()->GetDAC();
+
+ if (fStatic)
+ {
+ // the field is static, we don't need any additional data
+ pEncField->Init(VMPTR_Object::NullPtr(), /* vmObject */
+ NULL, /* offsetToVars */
+ fieldToken,
+ ELEMENT_TYPE_MAX,
+ classToken,
+ m_pModule->GetRuntimeDomainFile());
+ }
+ else
+ {
+ // This is an instance field, we need to pass a bunch of type information back
+ _ASSERTE(pObject != NULL);
+
+ pEncField->Init(pInterface->GetObject(pObject->m_id), // VMPTR to the object instance of interest.
+ pObject->GetInfo().objOffsetToVars, // The offset from the beginning of the object
+ // to the beginning of the fields. Fields added
+ // with EnC don't actually reside in the object
+ // (they hang off the sync block instead), so
+ // this is used to compute the returned field
+ // offset (fieldData.m_fldInstanceOffset). This
+ // makes it appear to be an offset from the object.
+ // Ideally we wouldn't do any of this, and just
+ // explicitly deal with absolute addresses (instead
+ // of "offsets") for EnC hanging instance fields.
+ fieldToken, // Field token for the added field.
+ pObject->GetInfo().objTypeData.elementType, // An indication of the type of object to which
+ // we're adding a field (specifically,
+ // whether it's a value type or a class).
+ // This is used only for log messages, and could
+ // be removed.
+ classToken, // metadata token for the class
+ m_pModule->GetRuntimeDomainFile()); // Domain file for the class
+ }
+} // CordbClass::InitFieldData
+
+// CordbClass::GetEnCFieldFromDac
+// Get information via the DAC about a field added with Edit and Continue.
+// Arguments:
+// input: fStatic - flag to indicate whether the EnC field is static
+// pObject - For instance fields, the Object instance containing the the sync-block.
+// For static fields (if this is being called from GetStaticFieldValue) object is NULL.
+// fieldToken - token for the EnC field
+// output: pointer to an initialized instance of FieldData that has been added to the appropriate table
+// in our cache
+FieldData * CordbClass::GetEnCFieldFromDac(BOOL fStatic,
+ CordbObjectValue * pObject,
+ mdFieldDef fieldToken)
+{
+ EnCHangingFieldInfo encField;
+ mdTypeDef metadataToken;
+ FieldData fieldData,
+ * pInfo = NULL;
+ BOOL fDacStatic;
+ CordbProcess * pProcess = GetModule()->GetProcess();
+
+ _ASSERTE(pProcess != NULL);
+ IfFailThrow(GetToken(&metadataToken));
+ InitEnCFieldInfo(&encField, fStatic, pObject, fieldToken, metadataToken);
+
+ // Go get this particular field.
+ pProcess->GetDAC()->GetEnCHangingFieldInfo(&encField, &fieldData, &fDacStatic);
+ _ASSERTE(fStatic == fDacStatic);
+
+ // Save the field results in our cache and get a stable pointer to the data
+ if (fStatic)
+ {
+ pInfo = m_hangingFieldsStatic.AddFieldInfo(&fieldData);
+ }
+ else
+ {
+ pInfo = pObject->GetHangingFieldTable()->AddFieldInfo(&fieldData);
+ }
+
+ // We should have a fresh copy of the data (don't want to return a pointer to data on our stack)
+ _ASSERTE((void *)pInfo != (void *)&fieldData);
+ _ASSERTE(pInfo->m_fFldIsStatic == (fStatic == TRUE));
+ _ASSERTE(pInfo->m_fldMetadataToken == fieldToken);
+
+ // Pass a pointer to the data out.
+ return pInfo;
+} // CordbClass::GetEnCFieldFromDac
+
+//-----------------------------------------------------------------------------
+// Internal helper to get a FieldData for fields added by EnC after the type
+// was loaded. Since object and MethodTable layout has already been fixed,
+// such added fields are "hanging" off some other data structure. For instance
+// fields, they're stored in a syncblock off the object. For static fields
+// they're stored off the EnCFieldDesc.
+//
+// The caller must have already determined this is a hanging field (i.e.
+// GetFieldInfo returned CORDBG_E_ENC_HANGING_FIELDF).
+//
+// Arguments:
+// input: fldToken - field of interest to get.
+// pObject - For instance fields, the Object instance containing the the sync-block.
+// For static fields (if this is being called from GetStaticFieldValue) object is NULL.
+// output: ppFieldData - the FieldData matching the fldToken.
+//
+// Returns:
+// S_OK on success, failure code otherwise.
+//-----------------------------------------------------------------------------
+HRESULT CordbClass::GetEnCHangingField(mdFieldDef fldToken,
+ FieldData **ppFieldData,
+ CordbObjectValue * pObject)
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ HRESULT hr = S_OK;
+ _ASSERTE(pObject == NULL || !pObject->IsNeutered() );
+
+ if (HasTypeParams())
+ {
+ _ASSERTE(!"EnC hanging field not yet implemented on constructed types!");
+ return E_FAIL;
+ }
+
+ // This must be a static field if no object was supplied
+ BOOL fStatic = (pObject == NULL);
+
+ // Look for cached field information
+ FieldData *pInfo = NULL;
+ if (fStatic)
+ {
+ // Static fields should _NOT_ be cleared, since they stick around. Thus
+ // the separate tables.
+ pInfo = m_hangingFieldsStatic.GetFieldInfo(fldToken);
+ }
+ else
+ {
+ // We must get new copies each time we call continue b/c we get the
+ // actual Object ptr from the left side, which can move during a GC.
+ pInfo = pObject->GetHangingFieldTable()->GetFieldInfo(fldToken);
+ }
+
+ // We've found a previously located entry
+ if (pInfo != NULL)
+ {
+ *ppFieldData = pInfo;
+ return S_OK;
+ }
+
+ // Field information not already available - go get it
+ EX_TRY
+ {
+
+ // We're not going to be able to get the instance-specific field
+ // if we can't get the instance.
+ if (!fStatic && pObject->GetInfo().objRefBad)
+ {
+ ThrowHR(CORDBG_E_INVALID_OBJECT);
+ }
+
+ *ppFieldData = GetEnCFieldFromDac(fStatic, pObject, fldToken);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Get a FieldData (which rich information, including details about storage)
+// from a metadata token.
+//
+// Parameters:
+// fldToken - incoming metadata token specifying the field.
+// ppFieldData - OUT: resulting FieldData structure.
+//
+// Returns:
+// S_OK on success. else failure.
+//-----------------------------------------------------------------------------
+HRESULT CordbClass::GetFieldInfo(mdFieldDef fldToken, FieldData **ppFieldData)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ Init();
+ return SearchFieldInfo(GetModule(), &m_classInfo.m_fieldList, m_token, fldToken, ppFieldData);
+}
+
+
+//-----------------------------------------------------------------------------
+// Search an array of FieldData (pFieldList) for a field (fldToken).
+// The FieldData array must match the class supplied by classToken, and live
+// in the supplied module.
+//
+// Internal helper used by CordbType::GetFieldInfo, CordbClass::GetFieldInfo
+//
+// Parameters:
+// module - module containing the class that the FieldData array matches.
+// pFieldList - array of fields to search through and the number of elements in
+// the array.
+// classToken - class that the data array matches. class must live in
+// the supplied moudle.
+// fldToken - metadata token of the field to search for. This field should be
+// on the class supplied by classToken.
+//
+// Returns:
+// CORDBG_E_ENC_HANGING_FIELD for "hanging fields" (fields added via Enc) (common error).
+// Returns S_OK, set ppFieldData = pointer into data array for matching field. (*retval)->m_fldMetadataToken == fldToken)
+// Throws on other errors.
+//-----------------------------------------------------------------------------
+/* static */
+HRESULT CordbClass::SearchFieldInfo(
+ CordbModule * pModule,
+ DacDbiArrayList<FieldData> * pFieldList,
+ mdTypeDef classToken,
+ mdFieldDef fldToken,
+ FieldData **ppFieldData
+)
+{
+ int i;
+
+ IMetaDataImport * pImport = pModule->GetMetaDataImporter(); // throws
+
+ HRESULT hr = S_OK;
+ for (i = 0; i < pFieldList->Count(); i++)
+ {
+ if ((*pFieldList)[i].m_fldMetadataToken == fldToken)
+ {
+ // If the storage for this field isn't yet available (i.e. it is newly added with EnC)
+ if (!(*pFieldList)[i].m_fFldStorageAvailable)
+ {
+ // If we're a static literal, then return special HR to let
+ // debugger know that it should look it up via the metadata.
+ // Check m_fFldIsStatic first b/c that's fast.
+ if ((*pFieldList)[i].m_fFldIsStatic)
+ {
+ if (IsFieldStaticLiteral(pImport, fldToken))
+ {
+ ThrowHR(CORDBG_E_VARIABLE_IS_ACTUALLY_LITERAL);
+ }
+ }
+
+ // This is a field added by EnC, caller needs to get instance-specific info.
+ return CORDBG_E_ENC_HANGING_FIELD;
+ }
+
+ *ppFieldData = &((*pFieldList)[i]);
+ return S_OK;
+ }
+ }
+
+ // Hmmm... we didn't find the field on this class. See if the field really belongs to this class or not.
+ mdTypeDef classTok;
+
+ hr = pImport->GetFieldProps(fldToken, &classTok, NULL, 0, NULL, NULL, NULL, 0, NULL, NULL, NULL);
+ IfFailThrow(hr);
+
+ if (classTok == (mdTypeDef) classToken)
+ {
+ // Well, the field belongs in this class. The assumption is that the Runtime optimized the field away.
+ ThrowHR(CORDBG_E_FIELD_NOT_AVAILABLE);
+ }
+
+ // Well, the field doesn't even belong to this class...
+ ThrowHR(E_INVALIDARG);
+}
+
diff --git a/src/debug/di/rsenumerator.hpp b/src/debug/di/rsenumerator.hpp
new file mode 100644
index 0000000000..eb7a225a5d
--- /dev/null
+++ b/src/debug/di/rsenumerator.hpp
@@ -0,0 +1,361 @@
+// 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.
+//*****************************************************************************
+//
+
+//
+// Implementation of CordbEnumerator, a templated COM enumeration pattern on Rs
+// types
+//*****************************************************************************
+
+#include "rspriv.h"
+
+// This CordbEnumerator is a templated enumerator from which COM enumerators for RS types can quickly be fashioned.
+// It uses a private array to store the items it enumerates over so by default it does not reference any
+// other RS type except the process it is associated with. The internal storage type does not need to match the
+// the item type exposed publically so that you can easily create an enumeration that holds RsSmartPtr<CordbThread>
+// but enumerates ICorDebugThread objects as an example. The enumerator has 4 templated parameters which must be
+// defined:
+// ElemType: this is the item type used for storage internal to the enumerator. For most Rs objects you will want
+// to use an RsSmartPtr<T> type to ensure the enumerator holds references to the objects it is
+// containing. The enumerator does not do any explicit Add/Release, it just copies items.
+// ElemPublicType: this is the item type exposed publically via the Next enumeration method. Typically this is
+// an ICorDebugX interface type but it can be anything.
+// EnumInterfaceType: this is the COM interface that the instantiated template will implement. It is expected that
+// this interface type follows the standard ICorDebug COM enumerator pattern, that the interface inherits
+// ICorDebugEnum and defines a strongly typed Next, enumerating over the ElemPublicType.
+// GetPublicType: this is a function which converts from ElemType -> ElemPublicType. It is used to produce the
+// elements that Next actually enumerates from the internal data the enumerator stores. Two conversion
+// functions are already defined here for convenience: QueryInterfaceConvert and IdentityConvert. If
+// neither of those suits your needs then you can define your own.
+//
+// Note: As of right now (10/13/08) most of the ICorDebug enumerators are not implemented using this base class,
+// however it might be good if we converged on this solution. There seems to be quite a bit of redundant and
+// one-off enumeration code that could be eliminated.
+//
+
+// A conversion function that converts from T to U by performing COM QueryInterface
+template<typename T, typename U>
+U * QueryInterfaceConvert(T obj)
+{
+ U* pPublic;
+ obj->QueryInterface(__uuidof(U), (void**) &pPublic);
+ return pPublic;
+}
+
+// A conversion identity function that just returns its argument
+template<typename T>
+T IdentityConvert(T obj)
+{
+ return obj;
+}
+
+// Constructor for an CordbEnumerator.
+// Arguments:
+// pProcess - the CordbProcess with which to associate this enumerator
+// items - the set of items which should be enumerated
+// countItems - the number of items in the array pointed to by items
+//
+// Note that the items are copied into an internal array, and no reference is kept to the users array.
+// Use RsSmartPtr types instead of Rs types directly to keep accurate ref counting for types which need it
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::CordbEnumerator(CordbProcess *pProcess,
+ ElemType *items,
+ DWORD countItems) :
+CordbBase(pProcess, 0, enumCordbEnumerator),
+m_countItems(countItems),
+m_nextIndex(0)
+{
+ m_items = new ElemType[countItems];
+ for(UINT i = 0; i < countItems; i++)
+ {
+ m_items[i] = items[i];
+ }
+}
+
+// Constructor for an CordbEnumerator.
+// Arguments:
+// pProcess - the CordbProcess with which to associate this enumerator
+// items - the address of an array of items which should be enumerated
+// countItems - the number of items in the array pointed to by items
+//
+// Note that the items array is simply taken over, setting *items to NULL.
+// Use RsSmartPtr types instead of Rs types directly to keep accurate ref counting for types which need it
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::CordbEnumerator(CordbProcess *pProcess,
+ ElemType **items,
+ DWORD countItems) :
+CordbBase(pProcess, 0, enumCordbEnumerator),
+m_nextIndex(0),
+m_countItems(countItems)
+{
+ _ASSERTE(items != NULL);
+ m_items = *items;
+ *items = NULL;
+}
+
+// Destructor
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::~CordbEnumerator()
+{
+ // for now at least all of these enumerators should be in neuter lists and get neutered prior to destruction
+ _ASSERTE(IsNeutered());
+}
+
+// COM IUnknown::QueryInterface - provides ICorDebugEnum, IUnknown, and templated EnumInterfaceType
+//
+// Arguments:
+// riid - IID of the interface to query for
+// ppInterface - on output set to a pointer to the desired interface
+//
+// Return:
+// S_OK for the supported interfaces and E_NOINTERFACE otherwise
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+HRESULT CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::QueryInterface(REFIID riid, VOID** ppInterface)
+{
+ if(riid == __uuidof(ICorDebugEnum))
+ {
+ *ppInterface = static_cast<ICorDebugEnum*>(this);
+ AddRef();
+ return S_OK;
+ }
+ else if(riid == __uuidof(IUnknown))
+ {
+ *ppInterface = static_cast<IUnknown*>(static_cast<CordbBase*>(this));
+ AddRef();
+ return S_OK;
+ }
+ else if(riid == __uuidof(EnumInterfaceType))
+ {
+ *ppInterface = static_cast<EnumInterfaceType*>(this);
+ AddRef();
+ return S_OK;
+ }
+ else
+ {
+ return E_NOINTERFACE;
+ }
+}
+
+// COM IUnknown::AddRef()
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+ULONG CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::AddRef()
+{
+ return BaseAddRef();
+}
+
+// COM IUnknown::Release()
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+ULONG CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::Release()
+{
+ return BaseRelease();
+}
+
+// ICorDebugEnum::Clone
+// Makes a duplicate of the enumeration. The internal items are copied by value and there is no explicit reference
+// between the new and old enumerations.
+//
+// Arguments:
+// ppEnum - on output filled with a duplicate enumeration
+//
+// Return:
+// S_OK if the clone was created succesfully, otherwise some appropriate failing HRESULT
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+HRESULT CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::Clone(ICorDebugEnum **ppEnum)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorDebugEnum **);
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ CordbEnumerator<ElemType, ElemPublicType, EnumInterfaceType, GetPublicType>* clone =
+ new CordbEnumerator<ElemType, ElemPublicType, EnumInterfaceType,GetPublicType>(
+ GetProcess(), m_items, m_countItems);
+ clone->QueryInterface(__uuidof(ICorDebugEnum), (void**)ppEnum);
+ }
+ EX_CATCH_HRESULT(hr)
+ {
+ }
+ return hr;
+}
+
+// ICorDebugEnum::GetCount
+// Gets the number of items in the the list that is being enumerated
+//
+// Arguments:
+// pcelt - on return the number of items being enumerated
+//
+// Return:
+// S_OK or failing HRESULTS for other error conditions
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+HRESULT CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::GetCount(ULONG *pcelt)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pcelt, ULONG *);
+
+ *pcelt = m_countItems;
+ return S_OK;
+}
+
+// ICorDebugEnum::Reset
+// Restarts the enumeration at the beginning of the list
+//
+// Return:
+// S_OK or failing HRESULTS for other error conditions
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+HRESULT CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::Reset()
+{
+ FAIL_IF_NEUTERED(this);
+
+ m_nextIndex = 0;
+ return S_OK;
+}
+
+// ICorDebugEnum::Skip
+// Skips over celt items in the enumeration, if celt is greater than the number of remaining items then all
+// remaining items are skipped.
+//
+// Arguments:
+// celt - number of items to be skipped
+//
+// Return:
+// S_OK or failing HRESULTS for other error conditions
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+HRESULT CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::Skip(ULONG celt)
+{
+ FAIL_IF_NEUTERED(this);
+
+ m_nextIndex += celt;
+ if(m_nextIndex > m_countItems)
+ {
+ m_nextIndex = m_countItems;
+ }
+ return S_OK;
+}
+
+// EnumInterfaceType::Next
+// Attempts to enumerate the next celt items by copying them in the items array. If fewer than celt
+// items remain all remaining items are enumerated. In either case pceltFetched indicates the number
+// of items actually fetched.
+//
+// Arguments:
+// celt - the number of enumerated items requested
+// items - an array of size celt where the enumerated items will be copied
+// pceltFetched - on return, the actual number of items enumerated
+//
+// Return:
+// S_OK if all items could be enumerated, S_FALSE if not all the requested items were enumerated,
+// failing HRESULTS for other error conditions
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+HRESULT CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::Next(ULONG celt,
+ ElemPublicType items[],
+ ULONG *pceltFetched)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(items, ElemInterfaceType *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched, ULONG *);
+
+ if ((pceltFetched == NULL) && (celt != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ ULONG countFetched;
+ for(countFetched = 0; countFetched < celt && m_nextIndex < m_countItems; countFetched++, m_nextIndex++)
+ {
+ items[countFetched] = GetPublicType(m_items[m_nextIndex]);
+ }
+
+ if(pceltFetched != NULL)
+ {
+ *pceltFetched = countFetched;
+ }
+
+ return countFetched == celt ? S_OK : S_FALSE;
+}
+
+// Neuter
+// neuters the enumerator and deletes the contents (the contents are not explicitly neutered though)
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+VOID CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::Neuter()
+{
+ delete [] m_items;
+ m_items = NULL;
+ m_countItems = 0;
+ m_nextIndex = 0;
+ CordbBase::Neuter();
+}
diff --git a/src/debug/di/rsfunction.cpp b/src/debug/di/rsfunction.cpp
new file mode 100644
index 0000000000..8621edcedc
--- /dev/null
+++ b/src/debug/di/rsfunction.cpp
@@ -0,0 +1,1191 @@
+// 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.
+//*****************************************************************************
+// File: rsfunction.cpp
+//
+
+//
+//*****************************************************************************
+#include "stdafx.h"
+
+// We have an assert in ceemain.cpp that validates this assumption
+#define FIELD_OFFSET_NEW_ENC_DB 0x07FFFFFB
+
+#include "winbase.h"
+#include "corpriv.h"
+
+/* ------------------------------------------------------------------------- *
+ * Function class
+ * ------------------------------------------------------------------------- */
+
+//-----------------------------------------------------------------------------
+// Constructor for CordbFunction class.
+// This represents an IL Function in the debuggee.
+// CordbFunction is 1:1 with IL method bodies.
+//
+// Parameters:
+// m - module containing this function. All functions live in a single module.
+// funcMetadataToken - the metadata token for this function (scoped to the module).
+// enCVersion - Enc Version number of this function (in sync with the module's
+// EnC version). Each edit to a function means a whole new IL method body,
+// and since CordbFunction is 1:1 with IL, that means a new CordbFunction instance.
+//-----------------------------------------------------------------------------
+CordbFunction::CordbFunction(CordbModule * m,
+ mdMethodDef funcMetadataToken,
+ SIZE_T enCVersion)
+ : CordbBase(m->GetProcess(), funcMetadataToken, enumCordbFunction), m_pModule(m), m_pClass(NULL),
+ m_pILCode(NULL),
+ m_nativeCode(NULL),
+ m_MDToken(funcMetadataToken),
+ m_dwEnCVersionNumber(enCVersion),
+ m_pPrevVersion(NULL),
+ m_fIsNativeImpl(kUnknownImpl),
+ m_fCachedMethodValuesValid(FALSE),
+ m_argCountCached(0),
+ m_fIsStaticCached(FALSE),
+ m_reJitILCodes(1)
+{
+ m_methodSigParserCached = SigParser(NULL, 0);
+
+ _ASSERTE(enCVersion >= CorDB_DEFAULT_ENC_FUNCTION_VERSION);
+}
+
+
+
+/*
+ A list of which resources owned by this object are accounted for.
+
+ UNKNOWN:
+ ICorDebugInfo::NativeVarInfo *m_nativeInfo;
+
+ HANDLED:
+ CordbModule *m_module; // Assigned w/o AddRef()
+ CordbClass *m_class; // Assigned w/o AddRef()
+*/
+
+//-----------------------------------------------------------------------------
+// CordbFunction destructor
+// All external resources, including references counts, should have been
+// released in Neuter(), so this should literally just delete memory or
+// or check that the object is already dead.
+//-----------------------------------------------------------------------------
+CordbFunction::~CordbFunction()
+{
+ // We should have been explicitly neutered before our internal ref went to 0.
+ _ASSERTE(IsNeutered());
+
+ // Since we've been neutered, we shouldn't have any References to release and
+ // our hash of JitInfos should be empty.
+ _ASSERTE(m_pILCode == NULL);
+ _ASSERTE(m_pPrevVersion == NULL);
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::Neuter
+// Neuter releases all of the resources this object holds. CordbFunction
+// lives in a CordbModule, so Module neuter will neuter this.
+// See CordbBase::Neuter for further semantics.
+//
+//-----------------------------------------------------------------------------
+void CordbFunction::Neuter()
+{
+ // Neuter any/all CordbNativeCode & CordbILCode objects
+ if (m_pILCode != NULL)
+ {
+ m_pILCode->Neuter();
+ m_pILCode.Clear(); // this will internal release.
+ }
+
+ // Neuter & Release the Prev-Function list.
+ if (m_pPrevVersion != NULL)
+ {
+ m_pPrevVersion->Neuter();
+ m_pPrevVersion.Clear(); // this will internal release.
+ }
+
+ m_pModule = NULL;
+ m_pClass = NULL;
+
+ m_nativeCode.Clear();
+ m_reJitILCodes.NeuterAndClear(GetProcess()->GetProcessLock());
+
+ CordbBase::Neuter();
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::QueryInterface
+// Public method to implement IUnknown::QueryInterface.
+// Has standard QI semantics.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugFunction)
+ {
+ *pInterface = static_cast<ICorDebugFunction*>(this);
+ }
+ else if (id == IID_ICorDebugFunction2)
+ {
+ *pInterface = static_cast<ICorDebugFunction2*>(this);
+ }
+ else if (id == IID_ICorDebugFunction3)
+ {
+ *pInterface = static_cast<ICorDebugFunction3*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugFunction*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetModule
+// Public method (implements ICorDebugFunction::GetModule).
+// Get the ICorDebugModule (external representation of a module) that this
+// Function is contained in. All functions live in exactly 1 module.
+// This is related to the 'CordbModule* GetModule()' method which returns the
+// internal module representation for the containing module.
+//
+// Parameters:
+// ppModule - out parameter to hold module.
+//
+// Return values:
+// S_OK iff *ppModule is set.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::GetModule(ICorDebugModule **ppModule)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppModule, ICorDebugModule **);
+
+ HRESULT hr = S_OK;
+
+ // Module is set on creation, so just return it.
+ *ppModule = static_cast<ICorDebugModule*> (m_pModule);
+ m_pModule->ExternalAddRef();
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetClass
+// Public function to get ICorDebugClass that this function is in.
+//
+// Parameters:
+// ppClass - out parameter holding which class this function lives in.
+//
+// Return value:
+// S_OK iff *ppClass is set.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::GetClass(ICorDebugClass **ppClass)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppClass, ICorDebugClass **);
+ ATT_ALLOW_LIVE_DO_STOPGO(GetProcess());
+ *ppClass = NULL;
+
+ HRESULT hr = S_OK;
+
+ if (m_pClass == NULL)
+ {
+ // We're not looking for any particular version, just
+ // the class info. This seems like the best version to request
+ hr = InitParentClassOfFunction();
+
+ if (FAILED(hr))
+ goto LExit;
+ }
+
+ *ppClass = static_cast<ICorDebugClass*> (m_pClass);
+
+LExit:
+ if (FAILED(hr))
+ return hr;
+
+ if (*ppClass)
+ {
+ m_pClass->ExternalAddRef();
+ return S_OK;
+ }
+ else
+ return S_FALSE;
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetToken
+// Public function to get the metadata token for this function.
+// This is a MethodDef, which is scoped to a module.
+//
+// Parameters:
+// pMemberDef - out parameter to hold token.
+//
+// Return values:
+// S_OK if pMemberDef is set.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::GetToken(mdMethodDef *pMemberDef)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pMemberDef, mdMethodDef *);
+
+
+ // Token is set on creation, so no updating needed.
+ CONSISTENCY_CHECK_MSGF((TypeFromToken(m_MDToken) == mdtMethodDef),
+ ("CordbFunction token (%08x) is not a mdtMethodDef. This=%p", m_MDToken, this));
+
+ *pMemberDef = m_MDToken;
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetILCode
+// Public function to get an ICorDebugCode object for the IL code in
+// this function.
+// If we EnC, we get a new ICorDebugFunction, so the IL code & function
+// should be 1:1.
+//
+// Parameters:
+// ppCode - out parameter to hold the code object.
+//
+// Return value:
+// S_OK iff *ppCode != NULL. Else error.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::GetILCode(ICorDebugCode ** ppCode)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppCode, ICorDebugCode **);
+ ATT_ALLOW_LIVE_DO_STOPGO(GetProcess());
+
+ *ppCode = NULL;
+ HRESULT hr = S_OK;
+
+ // Get the code object.
+ CordbILCode * pCode = NULL;
+ hr = GetILCode(&pCode);
+ _ASSERTE((pCode == NULL) == FAILED(hr));
+
+ if (FAILED(hr))
+ return hr;
+
+ *ppCode = (ICorDebugCode *)pCode;
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetNativeCode
+// Public API (ICorDebugFunction::GetNativeCode) to get the native code for
+// this function.
+// Note that this gets a pretty much random version of the native code when the
+// function is a generic method that gets JITted more than once, e.g. for generics.
+// Use EnumerateNativeCode instead in that case.
+//
+// Parameters:
+// ppCode - out parameter yeilding the native code object.
+//
+// Returns:
+// S_OK iff *ppCode is set.
+// CORDBG_E_CODE_NOT_AVAILABLE if there is no native code. This is common
+// if the function is not yet jitted.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::GetNativeCode(ICorDebugCode **ppCode)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppCode, ICorDebugCode **);
+ ATT_ALLOW_LIVE_DO_STOPGO(GetProcess());
+
+ HRESULT hr = S_OK;
+
+ // Make sure native code is updated before we go searching it.
+ hr = InitNativeCodeInfo();
+ if (FAILED(hr))
+ return hr;
+
+ // Generic methods may be jitted multiple times for different native instantiations,
+ // and so have 1:n relationship between IL:Native. CordbFunction is 1:1 with IL,
+ // CordbNativeCode is 1:1 with native.
+ // The interface here only lets us return 1 CordbNativeCode object, so we are
+ // returning an arbitrary one
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ _ASSERTE(m_nativeCode == NULL || m_nativeCode->GetVersion() == m_dwEnCVersionNumber);
+
+ if (m_nativeCode == NULL)
+ {
+ hr = CORDBG_E_CODE_NOT_AVAILABLE; // This is the case for an unjitted function,
+ // and so it will be very common.
+ }
+ else
+ {
+ m_nativeCode->ExternalAddRef();
+ *ppCode = m_nativeCode;
+ hr = S_OK;
+ }
+
+ return hr;
+}
+
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetCode
+// Internal method to get the IL code for this function. Each CordbFunction is
+// 1:1 with IL, so there is a unique IL Code object to hand out.
+//
+// Parameters:
+// ppCode - out parameter, the IL code object for this function. This should
+// be set to NULL on entry.
+// Return value:
+// S_OK iff *ppCode is set. Else error.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::GetILCode(CordbILCode ** ppCode)
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+ VALIDATE_POINTER_TO_OBJECT(ppCode, ICorDebugCode **);
+
+ _ASSERTE(*ppCode == NULL && "Common source of errors is getting addref'd copy here and never Release()ing it");
+ *ppCode = NULL;
+
+ // Its okay to do this if the process is not sync'd.
+ CORDBRequireProcessStateOK(GetProcess());
+
+ // Fetch all information about this function.
+ HRESULT hr = S_OK;
+ CordbILCode * pCode = NULL;
+
+ hr = GetILCodeAndSigToken();
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ // It's possible that m_ILCode will still be NULL.
+ pCode = m_pILCode;
+
+ if (pCode != NULL)
+ {
+ pCode->ExternalAddRef();
+ *ppCode = pCode;
+
+ return hr;
+ }
+ else
+ {
+ return CORDBG_E_CODE_NOT_AVAILABLE;
+ }
+} // CordbFunction::GetCode
+
+//-----------------------------------------------------------------------------
+// CordbFunction::CreateBreakpoint
+// Implements ICorDebugFunction::CreateBreakpoint
+// Creates a breakpoint at IL offset 0 (which is after the prolog) of the function.
+// The function does not need to be jitted yet.
+//
+// Parameters:
+// ppBreakpoint - out parameter for newly created breakpoint object.
+//
+// Return:
+// S_OK - on success. Else error.
+//----------------------------------------------------------------------------
+HRESULT CordbFunction::CreateBreakpoint(ICorDebugFunctionBreakpoint **ppBreakpoint)
+{
+ HRESULT hr = S_OK;
+
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppBreakpoint, ICorDebugFunctionBreakpoint **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ RSExtSmartPtr<ICorDebugCode> pCode;
+
+ // Use the IL code so that we stop after the prolog
+ hr = GetILCode(&pCode);
+
+ if (SUCCEEDED(hr))
+ {
+ hr = pCode->CreateBreakpoint(0, ppBreakpoint);
+ }
+
+ return hr;
+}
+
+#ifdef EnC_SUPPORTED
+//-----------------------------------------------------------------------------
+// CordbFunction::MakeOld
+// Internal method to do any cleanup necessary when a Function is no longer
+// the most current.
+//-----------------------------------------------------------------------------
+void CordbFunction::MakeOld()
+{
+ if (m_pILCode != NULL)
+ {
+ m_pILCode->MakeOld();
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetLocalVarSigToken
+// Public function (implements ICorDebugFunction::GetLocalVarSigToken) to
+// get signature token.
+//
+// Parameters:
+// pmdSig - out parameter to hold signature token, which is scoped to the
+// function's module.
+//
+// Return value:
+// S_OK if pmdSig is set.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::GetLocalVarSigToken(mdSignature *pmdSig)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pmdSig, mdSignature *);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // This will initialize the token.
+ HRESULT hr = GetILCodeAndSigToken();
+ if (FAILED(hr))
+ return hr;
+
+ *pmdSig = GetILCode()->GetLocalVarSigToken();
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetCurrentVersionNumber
+// Public method for ICorDebugFunction::GetCurrentVersionNumber.
+// Gets the most recent (highest) EnC version number of this Function.
+// See CordbModule for EnC version number semantics.
+//
+// Parameters
+// pnCurrentVersion - out parameter to hold the version number.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::GetCurrentVersionNumber(ULONG32 *pnCurrentVersion)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pnCurrentVersion, ULONG32 *);
+
+ HRESULT hr = S_OK;
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ // the most current version will always be the one found.
+ CordbFunction* curFunc = m_pModule->LookupFunctionLatestVersion(m_MDToken);
+
+ // will always find at least ourself
+ PREFIX_ASSUME(curFunc != NULL);
+
+ *pnCurrentVersion = (ULONG32)(curFunc->m_dwEnCVersionNumber);
+
+#ifdef EnC_SUPPORTED
+ _ASSERTE( *pnCurrentVersion >= this->m_dwEnCVersionNumber );
+#else
+ _ASSERTE(*pnCurrentVersion == CorDB_DEFAULT_ENC_FUNCTION_VERSION);
+#endif
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetVersionNumber
+// Public method for ICorDebugFunction2::GetVersionNumber.
+// Gets the EnC version number of this specific Function instance.
+// See CordbModule for EnC version number semantics.
+//
+// Parameters
+// pnVersion - out parameter to hold the version number.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::GetVersionNumber(ULONG32 *pnVersion)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pnVersion, ULONG32 *);
+
+ // This API existed in V1.0 but wasn't implemented. It needs V2 support to work.
+ if (! this->GetProcess()->SupportsVersion(ver_ICorDebugFunction2))
+ {
+ return E_NOTIMPL;
+ }
+
+ *pnVersion = (ULONG32)m_dwEnCVersionNumber;
+
+#ifdef EnC_SUPPORTED
+ _ASSERTE(*pnVersion >= CorDB_DEFAULT_ENC_FUNCTION_VERSION);
+#else
+ _ASSERTE(*pnVersion == CorDB_DEFAULT_ENC_FUNCTION_VERSION);
+#endif
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetVersionNumber
+// Public method for ICorDebugFunction2::GetVersionNumber.
+// Gets the EnC version number of this specific Function instance.
+// See CordbModule for EnC version number semantics.
+//
+// Parameters
+// pnVersion - out parameter to hold the version number.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::GetActiveReJitRequestILCode(ICorDebugILCode **ppReJitedILCode)
+{
+ HRESULT hr = S_OK;
+ VALIDATE_POINTER_TO_OBJECT(ppReJitedILCode, ICorDebugILCode **);
+ PUBLIC_API_BEGIN(this);
+ {
+ *ppReJitedILCode = NULL;
+
+ VMPTR_ReJitInfo vmReJitInfo = VMPTR_ReJitInfo::NullPtr();
+ GetProcess()->GetDAC()->GetReJitInfo(GetModule()->m_vmModule, m_MDToken, &vmReJitInfo);
+ if (!vmReJitInfo.IsNull())
+ {
+ VMPTR_SharedReJitInfo vmSharedReJitInfo = VMPTR_SharedReJitInfo::NullPtr();
+ GetProcess()->GetDAC()->GetSharedReJitInfo(vmReJitInfo, &vmSharedReJitInfo);
+ RSSmartPtr<CordbReJitILCode> pILCode;
+ IfFailThrow(LookupOrCreateReJitILCode(vmSharedReJitInfo, &pILCode));
+ IfFailThrow(pILCode->QueryInterface(IID_ICorDebugILCode, (void**)ppReJitedILCode));
+ }
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+// determine whether we have a native-only implementation
+// Arguments:
+// Input: none (we use information in various data members of this instance of CordbFunction: m_isNativeImpl,
+// m_pIMImport, m_EnCCount)
+// Output none, although we will set m_isNativeImpl to true iff the function has a native-only implementation
+
+void CordbFunction::InitNativeImpl()
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ // Bail now if we've already discovered that this function is implemented natively as part of the Runtime.
+ if (m_fIsNativeImpl != kUnknownImpl)
+ {
+ return;
+ }
+
+ // If we don't have a methodToken then we can't figure out what kind of function this is. This includes functions
+ // such as LCG and ILStubs. In the past we created codepaths that avoided ever calling in here in the common case
+ // and there would have been asserts and exceptions in the uncommon cases. Now I have just officially let the
+ // function handle staying as an 'unknown' impl. In such a state it provides no IL, no sigtoken, no native code, and
+ // no parent class.
+ if (m_MDToken == mdMethodDefNil)
+ {
+ return;
+ }
+
+ // Figure out if this function is implemented as a native part of the Runtime. If it is, then this ICorDebugFunction
+ // is just a container for certain Right Side bits of info, i.e., module, class, token, etc.
+ DWORD attrs;
+ DWORD implAttrs;
+ ULONG ulRVA;
+ BOOL isDynamic;
+
+ IfFailThrow(GetModule()->GetMetaDataImporter()->GetMethodProps(m_MDToken, NULL, NULL, 0, NULL,
+ &attrs, NULL, NULL, &ulRVA, &implAttrs));
+ isDynamic = GetModule()->IsDynamic();
+
+ // A method has associated IL if its RVA is non-zero, unless it is a dynamic module
+ // @todo : if RVA is 0 and function has been EnC'd then it isn't native. Remove isEnC
+ // condition when the compilers stop generating 0 for an RVA.
+ BOOL isEnC = (GetModule()->m_EnCCount != 0);
+ if (IsMiNative(implAttrs) || ((isDynamic == FALSE) && (isEnC == FALSE) && (ulRVA == 0)))
+ {
+
+ m_fIsNativeImpl = kNativeOnly;
+ }
+ else
+ {
+ m_fIsNativeImpl = kHasIL;
+ }
+
+} // CordbFunction::GetProcessAndCheckForNativeImpl
+
+// Returns the function's ILCode and SigToken
+// Arguments:
+// Input: none (required info comes from various data members of this instance of CordbFunction
+// Output (required):
+// none explicit, but this will:
+// construct a new instance of CordbILCode and assign it to m_pILCode
+
+HRESULT CordbFunction::GetILCodeAndSigToken()
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ CordbProcess * pProcess = m_pModule->GetProcess();
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+
+ // ensure that we're not trying to get information about a native-only function
+ InitNativeImpl();
+ if (m_fIsNativeImpl == kNativeOnly || m_fIsNativeImpl == kUnknownImpl)
+ {
+ ThrowHR(CORDBG_E_FUNCTION_NOT_IL);
+ }
+
+ if (m_pILCode == NULL)
+ {
+ // we haven't gotten the information previously
+
+ _ASSERTE(pProcess != NULL);
+
+ // This target buffer and mdSignature might never have their values changed from the
+ // initial ones if the dump target is missing memory. TargetBuffer has a default
+ // constructor to zero its data and localVarSigToken is explicitly inited.
+ TargetBuffer codeInfo;
+ mdSignature localVarSigToken = mdSignatureNil;
+ SIZE_T currentEnCVersion;
+
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ // In the dump case we may not have the backing memory for this. In such a case
+ // we construct an empty ILCode object and leave the signatureToken as mdSignatureNil.
+ // It may also be the case that the memory we read from the dump be inconsistent (huge method size)
+ // and we also fallback on creating an empty ILCode object.
+ // See issue DD 273199 for cases where IL and NGEN metadata mismatch (different RVAs).
+ ALLOW_DATATARGET_MISSING_OR_INCONSISTENT_MEMORY(
+ pProcess->GetDAC()->GetILCodeAndSig(m_pModule->GetRuntimeDomainFile(),
+ m_MDToken,
+ &codeInfo,
+ &localVarSigToken);
+ );
+
+ currentEnCVersion = m_pModule->LookupFunctionLatestVersion(m_MDToken)->m_dwEnCVersionNumber;
+ }
+
+ LOG((LF_CORDB,LL_INFO10000,"R:CF::GICAST: looking for IL code, version 0x%x\n", currentEnCVersion));
+
+ if (m_pILCode == NULL)
+ {
+ LOG((LF_CORDB,LL_INFO10000,"R:CF::GICAST: not found, creating...\n"));
+ if(codeInfo.pAddress == 0)
+ {
+ LOG((LF_CORDB,LL_INFO10000,"R:CF::GICAST: memory was missing - empty ILCode being created\n"));
+ }
+
+ // If everything succeeded, we set the IL code object (it's an outparam here).
+ _ASSERTE(m_pILCode == NULL);
+ m_pILCode.Assign(new(nothrow)CordbILCode(this,
+ codeInfo,
+ currentEnCVersion,
+ localVarSigToken));
+
+ if (m_pILCode == NULL)
+ {
+ ThrowHR(E_OUTOFMEMORY);
+ }
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbFunction::GetILCodeAndSigToken
+
+
+// Get the metadata token for the class to which a function belongs.
+// Arguments:
+// Input:
+// funcMetadataToken - the metadata token for the method
+// Output (required):
+// classMetadataToken - the metadata token for the class to which the method belongs
+mdTypeDef CordbFunction::InitParentClassOfFunctionHelper(mdToken funcMetadataToken)
+{
+ // Get the class this method is in.
+ mdToken tkParent = mdTypeDefNil;
+ IfFailThrow(GetModule()->GetInternalMD()->GetParentToken(funcMetadataToken, &tkParent));
+ _ASSERTE(TypeFromToken(tkParent) == mdtTypeDef);
+
+ return tkParent;
+} // CordbFunction::InitParentClassOfFunctionHelper
+
+// Get the class to which a given function belongs
+// Arguments:
+// Input: none (required information comes from data members of this instance of CordbFunction)
+// Output (required): none, but sets m_pClass
+HRESULT CordbFunction::InitParentClassOfFunction()
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ CordbProcess * pProcess = m_pModule->GetProcess();
+ (void)pProcess; //prevent "unused variable" error from GCC
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+
+ // ensure that we're not trying to get information about a native-only function
+ InitNativeImpl();
+ if (m_fIsNativeImpl == kNativeOnly || m_fIsNativeImpl == kUnknownImpl)
+ {
+ ThrowHR(CORDBG_E_FUNCTION_NOT_IL);
+ }
+
+ mdTypeDef classMetadataToken;
+ VMPTR_DomainFile vmDomainFile = m_pModule->GetRuntimeDomainFile();
+
+ classMetadataToken = InitParentClassOfFunctionHelper(m_MDToken);
+
+ if ((m_pClass == NULL) && (classMetadataToken != mdTypeDefNil))
+ {
+ // we haven't gotten the information previously but we have it now
+
+ _ASSERTE(pProcess != NULL);
+
+ CordbAssembly *pAssembly = m_pModule->GetCordbAssembly();
+ PREFIX_ASSUME(pAssembly != NULL);
+
+ CordbModule* pClassModule = pAssembly->GetAppDomain()->LookupOrCreateModule(vmDomainFile);
+ PREFIX_ASSUME(pClassModule != NULL);
+
+ CordbClass *pClass;
+ hr = pClassModule->LookupOrCreateClass(classMetadataToken, &pClass);
+
+ IfFailThrow(hr);
+
+ _ASSERTE(pClass != NULL);
+ m_pClass = pClass;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+
+} // CordbFunction::InitParentClassOfFunction
+
+// Get information about the native code blob for a function and add it to m_nativeCodeTable
+// Arguments:
+// Input: none, but we use some data members of this instance of CordbFunction
+// Output: standard HRESULT value
+// Notes: Apart from the HRESULT, this function will build a new instance of CordbNativeCode and
+// add it to the hash table of CordbNativeCodes for this function, unless we have done that
+// previously
+
+HRESULT CordbFunction::InitNativeCodeInfo()
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ CordbProcess * pProcess = m_pModule->GetProcess();
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+
+ // ensure that we're not trying to get information about a native-only function
+ InitNativeImpl();
+ if (m_fIsNativeImpl == kNativeOnly || m_fIsNativeImpl == kUnknownImpl)
+ {
+ ThrowHR(CORDBG_E_FUNCTION_NOT_IL);
+ }
+
+ _ASSERTE(pProcess != NULL);
+
+ // storage for information retrieved from the DAC. This is cleared in the constructor, so it
+ // won't contain garbage if we don't use the DAC to retrieve information we already got before.
+ NativeCodeFunctionData codeInfo;
+
+ if (m_nativeCode == NULL)
+ {
+ // Get the native code information from the DAC
+ // PERF: this call is potentially more costly than it needs to be
+ // All we actually need is the start address and method desc which are cheap to get relative
+ // to some of the other members. So far this doesn't appear to be a perf hotspot, but if it
+ // shows up in some scenario it wouldn't be too hard to improve it
+ pProcess->GetDAC()->GetNativeCodeInfo(m_pModule->GetRuntimeDomainFile(), m_MDToken, &codeInfo);
+ }
+
+ // populate the m_nativeCode pointer with the code info we found
+ if (codeInfo.IsValid())
+ {
+ m_nativeCode.Assign(m_pModule->LookupOrCreateNativeCode(m_MDToken, codeInfo.vmNativeCodeMethodDescToken,
+ codeInfo.m_rgCodeRegions[kHot].pAddress));
+ }
+
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbFunction::InitNativeCodeInfo
+
+//-----------------------------------------------------------------------------
+// CordbFunction::SetJMCStatus
+// Public method (implements ICorDebugFunction2::SetJMCStatus).
+// Set the JMC (eg, "User code" vs. "Non-user code") status of this function.
+//
+// Parameters:
+// fIsUserCode - true to set this Function to JMC, else False.
+//
+// Returns:
+// S_OK if successfully updated JMC status.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::SetJMCStatus(BOOL fIsUserCode)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ HRESULT hr = S_OK;
+
+ LOG((LF_CORDB,LL_INFO10000,"CordbFunction::SetJMCStatus to %d, (token=0x%08x, module=%p)\n",
+ fIsUserCode, m_MDToken, m_pModule));
+
+ // Make sure the Left-Side is in a good state.
+ FAIL_IF_NEUTERED(this);
+ CordbProcess* pProcess = m_pModule->GetProcess();
+ ATT_REQUIRE_STOPPED_MAY_FAIL(pProcess);
+
+
+
+ // Send an event to the LS to keep it updated.
+
+ // Validation - JMC Steppers don't have defined behavior if
+ // JMC method status gets toggled underneath them. However, we don't have
+ // a good way of verifying which methods are of interest to a JMC stepper.
+ // Having outstanding JMC steppers is dangerous here, but still can be
+ // done safely.
+ // Furthermore, debuggers may want to lazily set JMC status (such as when
+ // code is loaded), which may happen while we have outstanding steppers.
+
+
+ DebuggerIPCEvent event;
+ pProcess->InitIPCEvent(&event, DB_IPCE_SET_METHOD_JMC_STATUS, true, m_pModule->GetAppDomain()->GetADToken());
+ event.SetJMCFunctionStatus.vmDomainFile = m_pModule->GetRuntimeDomainFile();
+ event.SetJMCFunctionStatus.funcMetadataToken = m_MDToken;
+ event.SetJMCFunctionStatus.dwStatus = fIsUserCode;
+
+
+ // Note: two-way event here...
+ hr = pProcess->m_cordb->SendIPCEvent(pProcess, &event, sizeof(DebuggerIPCEvent));
+
+ // Stop now if we can't even send the event.
+ if (!SUCCEEDED(hr))
+ return hr;
+
+ _ASSERTE(event.type == DB_IPCE_SET_METHOD_JMC_STATUS_RESULT);
+
+ return event.hr;
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetJMCStatus
+// Public function (implements ICorDebugFunction2::GetJMCStatus)
+// Get the JMC status of this function.
+//
+// Parameters:
+// pfIsUserCode - out parameter describing whether this method is user code.
+// true iff this function is user code, else false.
+//
+// Return:
+// returns S_OK if *pfIsUserCode is set.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::GetJMCStatus(BOOL * pfIsUserCode)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ VALIDATE_POINTER_TO_OBJECT(pfIsUserCode, BOOL*);
+
+
+ _ASSERTE(pfIsUserCode != NULL);
+ if (pfIsUserCode == NULL)
+ return E_INVALIDARG;
+
+ // <TODO> @perf - If we know that we haven't updated the JMC status on anything
+ // in this module (we could keep a dirty flag), then we can just cache
+ // the jmc status and not send an event to query each time. </TODO>
+
+ // Make sure the process is in a sane state.
+ CordbProcess* pProcess = m_pModule->GetProcess();
+ _ASSERTE(pProcess != NULL);
+
+ // Ask the left-side if a method is user code or not.
+ DebuggerIPCEvent event;
+ pProcess->InitIPCEvent(&event, DB_IPCE_GET_METHOD_JMC_STATUS, true, m_pModule->GetAppDomain()->GetADToken());
+ event.SetJMCFunctionStatus.vmDomainFile = m_pModule->GetRuntimeDomainFile();
+ event.SetJMCFunctionStatus.funcMetadataToken = m_MDToken;
+
+
+ // Note: two-way event here...
+ HRESULT hr = pProcess->m_cordb->SendIPCEvent(pProcess, &event, sizeof(DebuggerIPCEvent));
+
+ // Stop now if we can't even send the event.
+ if (!SUCCEEDED(hr))
+ return hr;
+
+ _ASSERTE(event.type == DB_IPCE_GET_METHOD_JMC_STATUS_RESULT);
+
+ // update our internal copy of the status.
+ BOOL fIsUserCode = event.SetJMCFunctionStatus.dwStatus;
+
+ *pfIsUserCode = fIsUserCode;
+
+ return event.hr;
+}
+
+
+/*
+ * CordbFunction::GetSig
+ *
+ * Get the method's full metadata signature. This may be cached, but for dynamic modules we'll always read it from
+ * the metadata. This function also returns the argument count and whether or not the method is static.
+ *
+ * Parameters:
+ * pMethodSigParser - OUT: the signature parser class to use for all signature parsing.
+ * pFunctionArgCount - OUT: the number of arguments the method takes.
+ * pFunctionIsStatic - OUT: TRUE if the method is static, FALSE if it is not..
+ *
+ * Returns:
+ * HRESULT for success or failure.
+ *
+ */
+HRESULT CordbFunction::GetSig(SigParser *pMethodSigParser,
+ ULONG *pFunctionArgCount,
+ BOOL *pFunctionIsStatic)
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+
+ // If the module is dynamic, there had better not be a cached locals signature.
+ _ASSERTE(!GetModule()->IsDynamic() || !m_fCachedMethodValuesValid);
+
+ // If the method signature cache is null, then go read the signature from the
+ // matadata. For dynamic methods we never cache the parser because the method
+ // may change and the cached value will not match.
+ if (!m_fCachedMethodValuesValid)
+ {
+ PCCOR_SIGNATURE functionSignature;
+ ULONG size;
+ DWORD methodAttr = 0;
+ ULONG argCount;
+
+ EX_TRY // @dbgtotod - push this up
+ {
+ hr = GetModule()->GetMetaDataImporter()->GetMethodProps(m_MDToken, NULL, NULL, 0, NULL,
+ &methodAttr, &functionSignature, &size, NULL, NULL);
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+
+ SigParser sigParser = SigParser(functionSignature, size);
+
+ IfFailRet(sigParser.SkipMethodHeaderSignature(&argCount));
+
+ // If this function is not static, then we've got one extra arg.
+ BOOL isStatic = (methodAttr & mdStatic) != 0;
+
+ if (!isStatic)
+ {
+ argCount++;
+ }
+
+ // Cache the value for non-dynamic modules, so this is faster later.
+ if (!GetModule()->IsDynamic())
+ {
+ m_methodSigParserCached = sigParser;
+ m_argCountCached = argCount;
+ m_fIsStaticCached = isStatic;
+ m_fCachedMethodValuesValid = TRUE;
+ }
+ else
+ {
+ // This is the Dynamic method case, so we can't cache. Just leave fields blank
+ // and set out-parameters based off locals.
+ if (pMethodSigParser != NULL)
+ {
+ *pMethodSigParser = sigParser;
+ }
+
+ if (pFunctionArgCount != NULL)
+ {
+ *pFunctionArgCount = argCount;
+ }
+
+ if (pFunctionIsStatic != NULL)
+ {
+ *pFunctionIsStatic = isStatic;
+ }
+ }
+ }
+
+ if (m_fCachedMethodValuesValid)
+ {
+ //
+ // Retrieve values from cache
+ //
+
+ if (pMethodSigParser != NULL)
+ {
+ //
+ // Give them a new instance of the cached value
+ //
+ *pMethodSigParser = m_methodSigParserCached;
+ }
+
+ if (pFunctionArgCount != NULL)
+ {
+ *pFunctionArgCount = m_argCountCached;
+ }
+
+ if (pFunctionIsStatic != NULL)
+ {
+ *pFunctionIsStatic = m_fIsStaticCached;
+ }
+
+ }
+
+ //
+ // We should never have a cached value for in a dynamic module.
+ //
+ CONSISTENCY_CHECK_MSGF(((GetModule()->IsDynamic() && !m_fCachedMethodValuesValid) ||
+ (!GetModule()->IsDynamic() && m_fCachedMethodValuesValid)),
+ ("No dynamic modules should be cached! Module=%p This=%p", GetModule(), this));
+
+ return hr;
+}
+
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetArgumentType
+// Internal method. Given an 0-based IL argument number, return its type.
+// This can't access hidden parameters.
+//
+// Parameters:
+// dwIndex - 0-based index for IL argument number. For instance types,
+// 'this' argument is #0. For static types, first argument is #0.
+// pInst - instantiation information if this is a generic function. Eg,
+// if function is List<T>, inst describes T.
+// ppResultType - out parameter, yields to CordbType of the argument.
+//
+// Return:
+// S_OK on success.
+//
+HRESULT CordbFunction::GetArgumentType(DWORD dwIndex,
+ const Instantiation * pInst,
+ CordbType ** ppResultType)
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ HRESULT hr = S_OK;
+
+ // Get the method's signature, which contains the types for all the arguments.
+ SigParser sigParser;
+ ULONG cMethodArgs;
+ BOOL fMethodIsStatic;
+
+ IfFailRet(GetSig(&sigParser, &cMethodArgs, &fMethodIsStatic));
+
+ // Check the index
+ if (dwIndex >= cMethodArgs)
+ {
+ return E_INVALIDARG;
+ }
+
+ if (!fMethodIsStatic)
+ {
+ if (dwIndex == 0)
+ {
+ // Return the signature for the 'this' pointer for the
+ // class this method is in.
+ return m_pClass->GetThisType(pInst, ppResultType);
+ }
+ else
+ {
+ dwIndex--;
+ }
+ }
+
+ // Run the signature and find the required argument.
+ for (unsigned int i = 0; i < dwIndex; i++)
+ {
+ IfFailRet(sigParser.SkipExactlyOne());
+ }
+
+ hr = CordbType::SigToType(m_pModule, &sigParser, pInst, ppResultType);
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::NotifyCodeCreated
+// Internal method. Allows CordbFunctions to get access to a canonical native code entry
+// that they will return when asked for native code. The 1:1 mapping between
+// function and code was invalidated by generics but debuggers continue to use
+// the old API. When they do we need to have some code to hand them back even
+// though it is an arbitrary instantiation. Note that that the cannonical code
+// here is merely the first one that a user inspects... it is not guaranteed to
+// be the same in each debugging session but once set it will never change. It is
+// also definately NOT guaranteed to be the instantation over the runtime type
+// __Canon.
+//
+// Parameters:
+// nativeCode - the code which corresponds to this function
+//
+VOID CordbFunction::NotifyCodeCreated(CordbNativeCode* nativeCode)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ // Grab this native code as the canonical one if we don't already
+ // have a canonical entry
+ if(m_nativeCode == NULL)
+ m_nativeCode.Assign(nativeCode);
+}
+
+
+//-----------------------------------------------------------------------------
+// LookupOrCreateReJitILCode finds an existing version of CordbReJitILCode in the given function.
+// If the CordbReJitILCode doesn't exist, it creates it.
+//
+//
+HRESULT CordbFunction::LookupOrCreateReJitILCode(VMPTR_SharedReJitInfo vmSharedReJitInfo, CordbReJitILCode** ppILCode)
+{
+ INTERNAL_API_ENTRY(this);
+
+ HRESULT hr = S_OK;
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ CordbReJitILCode * pILCode = m_reJitILCodes.GetBase(VmPtrToCookie(vmSharedReJitInfo));
+
+ // special case non-existance as need to add to the hash table too
+ if (pILCode == NULL)
+ {
+ // we don't yet support ENC and ReJIT together, so the version should be 1
+ _ASSERTE(m_dwEnCVersionNumber == 1);
+ RSInitHolder<CordbReJitILCode> pILCodeHolder(new CordbReJitILCode(this, 1, vmSharedReJitInfo));
+ IfFailRet(m_reJitILCodes.AddBase(pILCodeHolder));
+ pILCode = pILCodeHolder;
+ pILCodeHolder.ClearAndMarkDontNeuter();
+ }
+
+ pILCode->InternalAddRef();
+ *ppILCode = pILCode;
+ return S_OK;
+}
diff --git a/src/debug/di/rsmain.cpp b/src/debug/di/rsmain.cpp
new file mode 100644
index 0000000000..b5685750db
--- /dev/null
+++ b/src/debug/di/rsmain.cpp
@@ -0,0 +1,2536 @@
+// 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.
+//*****************************************************************************
+// File: RsMain.cpp
+//
+
+// Random RS utility stuff, plus root ICorCordbug implementation
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "primitives.h"
+#include "safewrap.h"
+
+#include "check.h"
+
+#include <tlhelp32.h>
+#include "wtsapi32.h"
+
+#ifndef SM_REMOTESESSION
+#define SM_REMOTESESSION 0x1000
+#endif
+
+#include "corpriv.h"
+#include "../../dlls/mscorrc/resource.h"
+#include <limits.h>
+
+
+// The top level Cordb object is built around the Shim
+#include "shimpriv.h"
+
+//-----------------------------------------------------------------------------
+// For debugging ease, cache some global values.
+// Include these in retail & free because that's where we need them the most!!
+// Optimized builds may not let us view locals & parameters. So Having these
+// cached as global values should let us inspect almost all of
+// the interesting parts of the RS even in a Retail build!
+//-----------------------------------------------------------------------------
+
+RSDebuggingInfo g_RSDebuggingInfo_OutOfProc = {0 }; // set to NULL
+RSDebuggingInfo * g_pRSDebuggingInfo = &g_RSDebuggingInfo_OutOfProc;
+
+
+#ifdef _DEBUG
+// For logs, we can print the string name for the debug codes.
+const char * GetDebugCodeName(DWORD dwCode)
+{
+ if (dwCode < 1 || dwCode > 9)
+ {
+ return "!Invalid Debug Event Code!";
+ }
+
+ static const char * const szNames[] = {
+ "(1) EXCEPTION_DEBUG_EVENT",
+ "(2) CREATE_THREAD_DEBUG_EVENT",
+ "(3) CREATE_PROCESS_DEBUG_EVENT",
+ "(4) EXIT_THREAD_DEBUG_EVENT",
+ "(5) EXIT_PROCESS_DEBUG_EVENT",
+ "(6) LOAD_DLL_DEBUG_EVENT",
+ "(7) UNLOAD_DLL_DEBUG_EVENT",
+ "(8) OUTPUT_DEBUG_STRING_EVENT"
+ "(9) RIP_EVENT",// <-- only on Win9X
+ };
+
+ return szNames[dwCode - 1];
+}
+
+#endif
+
+
+//-----------------------------------------------------------------------------
+// Per-thread state for Debug builds...
+//-----------------------------------------------------------------------------
+#ifdef RSCONTRACTS
+DWORD DbgRSThread::s_TlsSlot = TLS_OUT_OF_INDEXES;
+LONG DbgRSThread::s_Total = 0;
+
+DbgRSThread::DbgRSThread()
+{
+ m_cInsideRS = 0;
+ m_fIsInCallback = false;
+ m_fIsUnrecoverableErrorCallback = false;
+
+ m_cTotalDbgApiLocks = 0;
+ for(int i = 0; i < RSLock::LL_MAX; i++)
+ {
+ m_cLocks[i] = 0;
+ }
+
+ // Initialize Identity info
+ m_Cookie = COOKIE_VALUE;
+ m_tid = GetCurrentThreadId();
+}
+
+// NotifyTakeLock & NotifyReleaseLock are called by RSLock to update the per-thread locking context.
+// This will assert if the operation is unsafe (ie, violates lock order).
+void DbgRSThread::NotifyTakeLock(RSLock * pLock)
+{
+ if (pLock->HasLock())
+ {
+ return;
+ }
+
+ int iLevel = pLock->GetLevel();
+
+ // Is it safe to take this lock?
+ // Must take "bigger" locks first. We shouldn't hold any locks at our current level either.
+ // If this lock is re-entrant and we're double-taking it, we would have returned already.
+ // And the locking model on the RS forbids taking multiple locks at the same level.
+ for(int i = iLevel; i >= 0; i --)
+ {
+ bool fHasLowerLock = m_cLocks[i] > 0;
+ CONSISTENCY_CHECK_MSGF(!fHasLowerLock, (
+ "RSLock violation. Trying to take lock '%s (%d)', but already have smaller lock at level %d'\n",
+ pLock->Name(), iLevel,
+ i));
+ }
+
+ // Update the counts
+ _ASSERTE(m_cLocks[iLevel] == 0);
+ m_cLocks[iLevel]++;
+
+ if (pLock->IsDbgApiLock())
+ m_cTotalDbgApiLocks++;
+}
+
+void DbgRSThread::NotifyReleaseLock(RSLock * pLock)
+{
+ if (pLock->HasLock())
+ {
+ return;
+ }
+
+ int iLevel = pLock->GetLevel();
+ m_cLocks[iLevel]--;
+ _ASSERTE(m_cLocks[iLevel] == 0);
+
+ if (pLock->IsDbgApiLock())
+ m_cTotalDbgApiLocks--;
+
+ _ASSERTE(m_cTotalDbgApiLocks >= 0);
+}
+
+void DbgRSThread::TakeVirtualLock(RSLock::ERSLockLevel level)
+{
+ m_cLocks[level]++;
+}
+
+void DbgRSThread::ReleaseVirtualLock(RSLock::ERSLockLevel level)
+{
+ m_cLocks[level]--;
+ _ASSERTE(m_cLocks[level] >= 0);
+}
+
+
+// Get a DbgRSThread for the current OS thread id; lazily create if needed.
+DbgRSThread * DbgRSThread::GetThread()
+{
+ _ASSERTE(DbgRSThread::s_TlsSlot != TLS_OUT_OF_INDEXES);
+
+ void * p2 = TlsGetValue(DbgRSThread::s_TlsSlot);
+ if (p2 == NULL)
+ {
+ // We lazily create for threads that haven't gone through DllMain
+ // Since this is per-thread, we don't need to lock.
+ p2 = DbgRSThread::Create();
+ }
+ DbgRSThread * p = reinterpret_cast<DbgRSThread*> (p2);
+
+ _ASSERTE(p->m_Cookie == COOKIE_VALUE);
+
+ return p;
+}
+
+
+
+#endif // RSCONTRACTS
+
+
+
+
+
+
+#ifdef _DEBUG
+LONG CordbCommonBase::s_TotalObjectCount = 0;
+LONG CordbCommonBase::s_CordbObjectUID = 0;
+
+
+LONG CordbCommonBase::m_saDwInstance[enumMaxDerived];
+LONG CordbCommonBase::m_saDwAlive[enumMaxDerived];
+PVOID CordbCommonBase::m_sdThis[enumMaxDerived][enumMaxThis];
+
+#endif
+
+#ifdef _DEBUG_IMPL
+// Mem tracking
+LONG Cordb::s_DbgMemTotalOutstandingCordb = 0;
+LONG Cordb::s_DbgMemTotalOutstandingInternalRefs = 0;
+#endif
+
+#ifdef TRACK_OUTSTANDING_OBJECTS
+void *Cordb::s_DbgMemOutstandingObjects[MAX_TRACKED_OUTSTANDING_OBJECTS] = { NULL };
+LONG Cordb::s_DbgMemOutstandingObjectMax = 0;
+#endif
+
+// Default implementation for neutering left-side resources.
+void CordbBase::NeuterLeftSideResources()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ Neuter();
+}
+
+// Default implementation for neutering.
+// All derived objects should eventually chain to this.
+void CordbBase::Neuter()
+{
+ // Neutering occurs under the process lock. Neuter can be called twice
+ // and so locking protects against races in double-delete.
+ // @dbgtodo - , some CordbBase objects (Cordb, CordbProcessEnum),
+ // don't have process affinity these should eventually be hoisted to the shim,
+ // and then we can enforce.
+ CordbProcess * pProcess = GetProcess();
+ if (pProcess != NULL)
+ {
+ _ASSERTE(pProcess->ThreadHoldsProcessLock());
+ }
+ CordbCommonBase::Neuter();
+}
+
+//-----------------------------------------------------------------------------
+// NeuterLists
+//-----------------------------------------------------------------------------
+
+NeuterList::NeuterList()
+{
+ m_pHead = NULL;
+}
+
+NeuterList::~NeuterList()
+{
+ // Our owner should have neutered us before deleting us.
+ // Thus we should be empty.
+ CONSISTENCY_CHECK_MSGF(m_pHead == NULL, ("NeuterList not empty on shutdown. this=0x%p", this));
+}
+
+// Wrapper around code:NeuterList::UnsafeAdd
+void NeuterList::Add(CordbProcess * pProcess, CordbBase * pObject)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ UnsafeAdd(pProcess, pObject);
+}
+
+//
+// Add an object to be neutered.
+//
+// Arguments:
+// pProcess - process that holds lock that will protect the neuter list
+// pObject - object to add
+//
+// Returns:
+// Throws on error.
+//
+// Notes:
+// This will add it to the list and maintain an internal reference to it.
+// This will take the process lock.
+//
+void NeuterList::UnsafeAdd(CordbProcess * pProcess, CordbBase * pObject)
+{
+ _ASSERTE(pObject != NULL);
+
+ // Lock if needed.
+ RSLock * pLock = (pProcess != NULL) ? pProcess->GetProcessLock() : NULL;
+ RSLockHolder lockHolder(pLock, FALSE);
+ if (pLock != NULL) lockHolder.Acquire();
+
+
+ Node * pNode = new Node(); // throws on error.
+ pNode->m_pObject.Assign(pObject);
+ pNode->m_pNext = m_pHead;
+
+ m_pHead = pNode;
+}
+
+// Neuter everything on the list and clear it
+//
+// Arguments:
+// pProcess - process tree that this neuterlist belongs in
+// ticket - neuter ticket proving caller ensured we're safe to neuter.
+//
+// Assumptions:
+// Caller ensures we're safe to neuter (required to obtain NeuterTicket)
+//
+// Notes:
+// This will release all internal references and empty the list.
+void NeuterList::NeuterAndClear(CordbProcess * pProcess)
+{
+ RSLock * pLock = (pProcess != NULL) ? pProcess->GetProcessLock() : NULL;
+ (void)pLock; //prevent "unused variable" error from GCC
+ _ASSERTE((pLock == NULL) || pLock->HasLock());
+
+ while (m_pHead != NULL)
+ {
+ Node * pTemp = m_pHead;
+ m_pHead = m_pHead->m_pNext;
+
+ pTemp->m_pObject->Neuter();
+ delete pTemp; // will implicitly release
+ }
+}
+
+// Only neuter objects that are marked.
+// Removes neutered objects from the list.
+void NeuterList::SweepAllNeuterAtWillObjects(CordbProcess * pProcess)
+{
+ _ASSERTE(pProcess != NULL);
+ RSLock * pLock = pProcess->GetProcessLock();
+ RSLockHolder lockHolder(pLock);
+
+ Node ** ppLast = &m_pHead;
+ Node * pCur = m_pHead;
+
+ while (pCur != NULL)
+ {
+ CordbBase * pObject = pCur->m_pObject;
+ if (pObject->IsNeuterAtWill() || pObject->IsNeutered())
+ {
+ // Delete
+ pObject->Neuter();
+
+ Node * pNext = pCur->m_pNext;
+ delete pCur; // dtor will implicitly release the internal ref to pObject
+ pCur = *ppLast = pNext;
+ }
+ else
+ {
+ // Move to next.
+ ppLast = &pCur->m_pNext;
+ pCur = pCur->m_pNext;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Neuters all objects in the list and empties the list.
+//
+// Notes:
+// See also code:LeftSideResourceCleanupList::SweepNeuterLeftSideResources,
+// which only neuters objects that have been marked as NeuterAtWill (external
+// ref count has gone to 0).
+void LeftSideResourceCleanupList::NeuterLeftSideResourcesAndClear(CordbProcess * pProcess)
+{
+ // Traversal protected under Process-lock.
+ // SG-lock must already be held to do neutering.
+ // Stop-Go lock is bigger than Process-lock.
+ // Neutering requires the Stop-Go lock (until we get rid of IPC events)
+ // But we want to be able to add to the Neuter list under the Process-lock.
+ // So we just need to protected m_pHead under process-lock.
+
+ // "Privatize" the list under the lock.
+ _ASSERTE(pProcess != NULL);
+ RSLock * pLock = pProcess->GetProcessLock();
+
+ Node * pCur = NULL;
+ {
+ RSLockHolder lockHolder(pLock); // only acquire lock if we have one
+ pCur = m_pHead;
+ m_pHead = NULL;
+ }
+
+ // @dbgtodo - eventually everything can be under the process lock.
+ _ASSERTE(!pLock->HasLock()); // Can't hold Process lock while calling NeuterLeftSideResources
+
+ // Now we're operating on local data, so traversing doesn't need to be under the lock.
+ while (pCur != NULL)
+ {
+ Node * pTemp = pCur;
+ pCur = pCur->m_pNext;
+
+ pTemp->m_pObject->NeuterLeftSideResources();
+ delete pTemp; // will implicitly release
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+// Only neuter objects that are marked. Removes neutered objects from the list.
+//
+// Arguments:
+// pProcess - non-null process owning the objects in the list
+//
+// Notes:
+// this cleans up left-side resources held by objects in the list.
+// It may send IPC events to do this.
+void LeftSideResourceCleanupList::SweepNeuterLeftSideResources(CordbProcess * pProcess)
+{
+ _ASSERTE(pProcess != NULL);
+
+ // Must be safe to send IPC events.
+ _ASSERTE(pProcess->GetStopGoLock()->HasLock()); // holds this for neutering
+ _ASSERTE(pProcess->GetSynchronized());
+
+ RSLock * pLock = pProcess->GetProcessLock();
+
+ // Lock while we "privatize" the head.
+ RSLockHolder lockHolder(pLock);
+ Node * pHead = m_pHead;
+ m_pHead = NULL;
+ lockHolder.Release();
+
+ Node ** ppLast = &pHead;
+ Node * pCur = pHead;
+
+ // Can't hold the process-lock while calling Neuter.
+ while (pCur != NULL)
+ {
+ CordbBase * pObject = pCur->m_pObject;
+ if (pObject->IsNeuterAtWill() || pObject->IsNeutered())
+ {
+ // HeavyNueter can not be done under the process-lock because
+ // it may take the Stop-Go lock and send events.
+ pObject->NeuterLeftSideResources();
+
+ // Delete
+ Node * pNext = pCur->m_pNext;
+ delete pCur; // dtor will implicitly release the internal ref to pObject
+ pCur = *ppLast = pNext;
+ }
+ else
+ {
+ // Move to next.
+ ppLast = &pCur->m_pNext;
+ pCur = pCur->m_pNext;
+ }
+ }
+
+ // Now link back in. m_pHead may have changed while we were unlocked.
+ // The list does not need to be ordered.
+
+ lockHolder.Acquire();
+ *ppLast = m_pHead;
+ m_pHead = pHead;
+}
+
+
+
+/* ------------------------------------------------------------------------- *
+ * CordbBase class
+ * ------------------------------------------------------------------------- */
+
+// Do any initialization necessary for both CorPublish and CorDebug
+// This includes enabling logging and adding the SEDebug priv.
+void CordbCommonBase::InitializeCommon()
+{
+ static bool IsInitialized = false;
+ if( IsInitialized )
+ {
+ return;
+ }
+
+#ifdef STRESS_LOG
+ {
+ bool fStressLog = false;
+
+#ifdef _DEBUG
+ // default for stress log is on debug build
+ fStressLog = true;
+#endif // DEBUG
+
+ // StressLog will turn on stress logging for the entire runtime.
+ // RSStressLog is only used here and only effects just the RS.
+ fStressLog =
+ (REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::UNSUPPORTED_StressLog, fStressLog) != 0) ||
+ (CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_RSStressLog) != 0);
+
+ if (fStressLog == true)
+ {
+ unsigned facilities = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_LogFacility, LF_ALL);
+ unsigned level = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::EXTERNAL_LogLevel, LL_INFO1000);
+ unsigned bytesPerThread = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::UNSUPPORTED_StressLogSize, STRESSLOG_CHUNK_SIZE * 2);
+ unsigned totalBytes = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::UNSUPPORTED_TotalStressLogSize, STRESSLOG_CHUNK_SIZE * 1024);
+#ifndef FEATURE_PAL
+ StressLog::Initialize(facilities, level, bytesPerThread, totalBytes, GetModuleInst());
+#else
+ StressLog::Initialize(facilities, level, bytesPerThread, totalBytes, NULL);
+#endif
+ }
+ }
+
+#endif // STRESS_LOG
+
+#ifdef LOGGING
+ InitializeLogging();
+#endif
+
+ // Add debug privilege. This will let us call OpenProcess() on anything, regardless of ACL.
+ AddDebugPrivilege();
+
+ IsInitialized = true;
+}
+
+// Adjust the permissions of this process to ensure that we have
+// the debugging priviledge. If we can't make the adjustment, it
+// only means that we won't be able to attach to a service under
+// NT, so we won't treat that as a critical failure.
+// This also will let us call OpenProcess() on anything, regardless of DACL. This allows an
+// Admin debugger to attach to a debuggee in the guest account.
+// Ideally, the debugger would set this (and we wouldn't mess with privileges at all). However, we've been
+// setting this since V1.0 and removing it may be a breaking change.
+void CordbCommonBase::AddDebugPrivilege()
+{
+#ifndef FEATURE_PAL
+ HANDLE hToken;
+ TOKEN_PRIVILEGES Privileges;
+ BOOL fSucc;
+
+ LUID SeDebugLuid = {0, 0};
+
+ fSucc = LookupPrivilegeValueW(NULL, SE_DEBUG_NAME, &SeDebugLuid);
+ DWORD err = GetLastError();
+
+ if (!fSucc)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "Unable to adjust permissions of this process to include SE_DEBUG. Lookup failed %d\n", err);
+ return;
+ }
+
+
+ // Retrieve a handle of the access token
+ fSucc = OpenProcessToken(GetCurrentProcess(),
+ TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
+ &hToken);
+
+ if (fSucc)
+ {
+ Privileges.PrivilegeCount = 1;
+ Privileges.Privileges[0].Luid = SeDebugLuid;
+ Privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+
+ AdjustTokenPrivileges(hToken,
+ FALSE,
+ &Privileges,
+ sizeof(TOKEN_PRIVILEGES),
+ (PTOKEN_PRIVILEGES) NULL,
+ (PDWORD) NULL);
+ err = GetLastError();
+ // The return value of AdjustTokenPrivileges cannot be tested.
+ if (err != ERROR_SUCCESS)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000,
+ "Unable to adjust permissions of this process to include SE_DEBUG. Adjust failed %d\n", err);
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000, "Adjusted process permissions to include SE_DEBUG.\n"));
+ }
+ CloseHandle(hToken);
+ }
+#endif
+}
+
+
+namespace
+{
+
+ //
+ // DefaultManagedCallback2
+ //
+ // In the event that the debugger is of an older version than the Right Side & Left Side, the Right Side may issue
+ // new callbacks that the debugger is not expecting. In this case, we need to provide a default behavior for those
+ // new callbacks, if for nothing else than to force the debugger to Continue().
+ //
+ class DefaultManagedCallback2 : public ICorDebugManagedCallback2
+ {
+ public:
+ DefaultManagedCallback2(ICorDebug* pDebug);
+ virtual ~DefaultManagedCallback2() { }
+ virtual HRESULT __stdcall QueryInterface(REFIID iid, void** pInterface);
+ virtual ULONG __stdcall AddRef();
+ virtual ULONG __stdcall Release();
+ COM_METHOD FunctionRemapOpportunity(ICorDebugAppDomain* pAppDomain,
+ ICorDebugThread* pThread,
+ ICorDebugFunction* pOldFunction,
+ ICorDebugFunction* pNewFunction,
+ ULONG32 oldILOffset);
+ COM_METHOD FunctionRemapComplete(ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugFunction *pFunction);
+
+ COM_METHOD CreateConnection(ICorDebugProcess *pProcess,
+ CONNID dwConnectionId,
+ __in_z WCHAR* pConnectionName);
+ COM_METHOD ChangeConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId);
+ COM_METHOD DestroyConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId);
+
+ COM_METHOD Exception(ICorDebugAppDomain *pAddDomain,
+ ICorDebugThread *pThread,
+ ICorDebugFrame *pFrame,
+ ULONG32 nOffset,
+ CorDebugExceptionCallbackType eventType,
+ DWORD dwFlags );
+
+ COM_METHOD ExceptionUnwind(ICorDebugAppDomain *pAddDomain,
+ ICorDebugThread *pThread,
+ CorDebugExceptionUnwindCallbackType eventType,
+ DWORD dwFlags );
+ COM_METHOD MDANotification(
+ ICorDebugController * pController,
+ ICorDebugThread *pThread,
+ ICorDebugMDA * pMDA
+ ) { return E_NOTIMPL; }
+
+ private:
+ // not implemented
+ DefaultManagedCallback2(const DefaultManagedCallback2&);
+ DefaultManagedCallback2& operator=(const DefaultManagedCallback2&);
+
+ ICorDebug* m_pDebug;
+ LONG m_refCount;
+ };
+
+
+
+
+ DefaultManagedCallback2::DefaultManagedCallback2(ICorDebug* pDebug) : m_pDebug(pDebug), m_refCount(0)
+ {
+ }
+
+ HRESULT
+ DefaultManagedCallback2::QueryInterface(REFIID iid, void** pInterface)
+ {
+ if (IID_ICorDebugManagedCallback2 == iid)
+ {
+ *pInterface = static_cast<ICorDebugManagedCallback2*>(this);
+ }
+ else if (IID_IUnknown == iid)
+ {
+ *pInterface = static_cast<IUnknown*>(this);
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ this->AddRef();
+ return S_OK;
+ }
+
+ ULONG
+ DefaultManagedCallback2::AddRef()
+ {
+ return InterlockedIncrement(&m_refCount);
+ }
+
+ ULONG
+ DefaultManagedCallback2::Release()
+ {
+ ULONG ulRef = InterlockedDecrement(&m_refCount);
+ if (0 == ulRef)
+ {
+ delete this;
+ }
+
+ return ulRef;
+ }
+
+ HRESULT
+ DefaultManagedCallback2::FunctionRemapOpportunity(ICorDebugAppDomain* pAppDomain,
+ ICorDebugThread* pThread,
+ ICorDebugFunction* pOldFunction,
+ ICorDebugFunction* pNewFunction,
+ ULONG32 oldILOffset)
+ {
+
+ //
+ // In theory, this function should never be reached. To get here, we'd have to have a debugger which doesn't
+ // support edit and continue somehow turn on edit & continue features.
+ //
+ _ASSERTE(!"Edit & Continue callback reached when debugger doesn't support Edit And Continue");
+
+
+ // If you ignore this assertion, or you're in a retail build, there are two options as far as how to proceed
+ // from this point
+ // o We can do nothing, and let the debugee process hang, or
+ // o We can silently ignore the FunctionRemapOpportunity, and tell the debugee to Continue running.
+ //
+ // For now, we'll silently ignore the function remapping.
+ pAppDomain->Continue(false);
+ pAppDomain->Release();
+
+ return S_OK;
+ }
+
+
+ HRESULT
+ DefaultManagedCallback2::FunctionRemapComplete(ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugFunction *pFunction)
+ {
+ //
+ // In theory, this function should never be reached. To get here, we'd have to have a debugger which doesn't
+ // support edit and continue somehow turn on edit & continue features.
+ //
+ _ASSERTE(!"Edit & Continue callback reached when debugger doesn't support Edit And Continue");
+ return E_NOTIMPL;
+ }
+
+ //
+ // <TODO>
+ // These methods are current left unimplemented.
+ //
+ // Create/Change/Destroy Connection *should* force the Process/AppDomain/Thread to Continue(). Currently the
+ // arguments to these functions don't provide the relevant Process/AppDomain/Thread, so there is no way to figure
+ // out which Threads should be forced to Continue().
+ //
+ // </TODO>
+ //
+ HRESULT
+ DefaultManagedCallback2::CreateConnection(ICorDebugProcess *pProcess,
+ CONNID dwConnectionId,
+ __in_z WCHAR* pConnectionName)
+ {
+ _ASSERTE(!"DefaultManagedCallback2::CreateConnection not implemented");
+ return E_NOTIMPL;
+ }
+
+ HRESULT
+ DefaultManagedCallback2::ChangeConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId)
+ {
+ _ASSERTE(!"DefaultManagedCallback2::ChangeConnection not implemented");
+ return E_NOTIMPL;
+ }
+
+ HRESULT
+ DefaultManagedCallback2::DestroyConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId)
+ {
+ _ASSERTE(!"DefaultManagedCallback2::DestroyConnection not implemented");
+ return E_NOTIMPL;
+ }
+
+ HRESULT
+ DefaultManagedCallback2::Exception(ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugFrame *pFrame,
+ ULONG32 nOffset,
+ CorDebugExceptionCallbackType eventType,
+ DWORD dwFlags )
+ {
+ //
+ // Just ignore and continue the process.
+ //
+ pAppDomain->Continue(false);
+ return S_OK;
+ }
+
+ HRESULT
+ DefaultManagedCallback2::ExceptionUnwind(ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ CorDebugExceptionUnwindCallbackType eventType,
+ DWORD dwFlags )
+ {
+ //
+ // Just ignore and continue the process.
+ //
+ pAppDomain->Continue(false);
+ return S_OK;
+ }
+
+ //
+ // DefaultManagedCallback3
+ //
+ // In the event that the debugger is of an older version than the Right Side & Left Side, the Right Side may issue
+ // new callbacks that the debugger is not expecting. In this case, we need to provide a default behavior for those
+ // new callbacks, if for nothing else than to force the debugger to Continue().
+ //
+ class DefaultManagedCallback3 : public ICorDebugManagedCallback3
+ {
+ public:
+ DefaultManagedCallback3(ICorDebug* pDebug);
+ virtual ~DefaultManagedCallback3() { }
+ virtual HRESULT __stdcall QueryInterface(REFIID iid, void** pInterface);
+ virtual ULONG __stdcall AddRef();
+ virtual ULONG __stdcall Release();
+ COM_METHOD CustomNotification(ICorDebugThread * pThread, ICorDebugAppDomain * pAppDomain);
+ private:
+ // not implemented
+ DefaultManagedCallback3(const DefaultManagedCallback3&);
+ DefaultManagedCallback3& operator=(const DefaultManagedCallback3&);
+
+ ICorDebug* m_pDebug;
+ LONG m_refCount;
+ };
+
+ DefaultManagedCallback3::DefaultManagedCallback3(ICorDebug* pDebug) : m_pDebug(pDebug), m_refCount(0)
+ {
+ }
+
+ HRESULT
+ DefaultManagedCallback3::QueryInterface(REFIID iid, void** pInterface)
+ {
+ if (IID_ICorDebugManagedCallback3 == iid)
+ {
+ *pInterface = static_cast<ICorDebugManagedCallback3*>(this);
+ }
+ else if (IID_IUnknown == iid)
+ {
+ *pInterface = static_cast<IUnknown*>(this);
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ this->AddRef();
+ return S_OK;
+ }
+
+ ULONG
+ DefaultManagedCallback3::AddRef()
+ {
+ return InterlockedIncrement(&m_refCount);
+ }
+
+ ULONG
+ DefaultManagedCallback3::Release()
+ {
+ ULONG ulRef = InterlockedDecrement(&m_refCount);
+ if (0 == ulRef)
+ {
+ delete this;
+ }
+
+ return ulRef;
+ }
+
+ HRESULT
+ DefaultManagedCallback3::CustomNotification(ICorDebugThread * pThread, ICorDebugAppDomain * pAppDomain)
+ {
+ //
+ // Just ignore and continue the process.
+ //
+ pAppDomain->Continue(false);
+ return S_OK;
+ }
+}
+
+/* ------------------------------------------------------------------------- *
+ * Cordb class
+ * ------------------------------------------------------------------------- */
+
+
+Cordb::Cordb(CorDebugInterfaceVersion iDebuggerVersion)
+ : CordbBase(NULL, 0, enumCordb),
+ m_processes(11),
+ m_initialized(false),
+ m_debuggerSpecifiedVersion(iDebuggerVersion)
+#ifdef FEATURE_CORESYSTEM
+ ,
+ m_targetCLR(0)
+#endif
+{
+ g_pRSDebuggingInfo->m_Cordb = this;
+
+#ifdef _DEBUG_IMPL
+ // Memory leak detection
+ InterlockedIncrement(&s_DbgMemTotalOutstandingCordb);
+#endif
+}
+
+Cordb::~Cordb()
+{
+ LOG((LF_CORDB, LL_INFO10, "C::~C Terminating Cordb object.\n"));
+ g_pRSDebuggingInfo->m_Cordb = NULL;
+}
+
+void Cordb::Neuter()
+{
+ if (this->IsNeutered())
+ {
+ return;
+ }
+
+
+ RSLockHolder lockHolder(&m_processListMutex);
+ m_pProcessEnumList.NeuterAndClear(NULL);
+
+
+ HRESULT hr = S_OK;
+ EX_TRY // @dbgtodo push this up.
+ {
+ // Iterating needs to be done under the processList lock (small), while neutering
+ // needs to be able to take the process lock (big).
+ RSPtrArray<CordbProcess> list;
+ m_processes.TransferToArray(&list); // throws
+
+ // can't hold list lock while calling CordbProcess::Neuter (which
+ // will take the Process-lock).
+ lockHolder.Release();
+
+ list.NeuterAndClear();
+ // List dtor calls release on each element
+ }
+ EX_CATCH_HRESULT(hr);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ CordbCommonBase::Neuter();
+
+ // Implicit release from smart ptr.
+}
+
+#ifdef _DEBUG_IMPL
+void CheckMemLeaks()
+{
+ // Memory leak detection.
+ LONG l = InterlockedDecrement(&Cordb::s_DbgMemTotalOutstandingCordb);
+ if (l == 0)
+ {
+ // If we just released our final Cordb root object, then we expect no internal references at all.
+ // Note that there may still be external references (and thus not all objects may have been
+ // deleted yet).
+ bool fLeakedInternal = (Cordb::s_DbgMemTotalOutstandingInternalRefs > 0);
+
+ // Some Cordb objects (such as CordbValues) may not be rooted, and thus we can't neuter
+ // them and thus an external ref may keep them alive. Since these objects may have internal refs,
+ // This means that external refs can keep internal refs.
+ // Thus this assert must be tempered if unrooted objects are leaked. (But that means we can always
+ // assert the tempered version; regardless of bugs in Cordbg).
+ CONSISTENCY_CHECK_MSGF(!fLeakedInternal,
+ ("'%d' Outstanding internal references at final Cordb::Terminate\n",
+ Cordb::s_DbgMemTotalOutstandingInternalRefs));
+
+ DWORD dLeakCheck = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgLeakCheck);
+ if (dLeakCheck > 0)
+ {
+ // We have 1 ref for this Cordb root object. All other refs should have been deleted.
+ CONSISTENCY_CHECK_MSGF(Cordb::s_TotalObjectCount == 1, ("'%d' total cordbBase objects are leaked.\n",
+ Cordb::s_TotalObjectCount-1));
+ }
+ }
+}
+#endif
+
+// This shuts down ICorDebug.
+// All CordbProcess objects owned by this Cordb object must have either:
+// - returned for a Detach() call
+// - returned from dispatching the ExitProcess() callback.
+// In both cases, CordbProcess::NeuterChildren has been called, although the Process object itself
+// may not yet be neutered. This condition will ensure that the CordbProcess objects don't need
+// any resources that we're about to release.
+HRESULT Cordb::Terminate()
+{
+ LOG((LF_CORDB, LL_INFO10000, "[%x] Terminating Cordb\n", GetCurrentThreadId()));
+
+ if (!m_initialized)
+ return E_FAIL;
+
+ FAIL_IF_NEUTERED(this);
+
+ // We can't terminate the debugging services from within a callback.
+ // Caller is supposed to be out of all callbacks when they call this.
+ // This also avoids a deadlock because we'll shutdown the RCET, which would block if we're
+ // in the RCET.
+ if (m_rcEventThread->IsRCEventThread())
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO10, "C::T: failed on RCET\n");
+ _ASSERTE(!"Gross API Misuse: Debugger shouldn't call ICorDebug::Terminate from within a managed callback.");
+ return CORDBG_E_CANT_CALL_ON_THIS_THREAD;
+ }
+
+ // @todo - do we need to throw some switch to prevent new processes from being added now?
+
+ // VS must stop all debugging before terminating. Fail if we have any non-neutered processes
+ // (b/c those processes should have been either shutdown or detached).
+ // We are in an undefined state if this check fails.
+ // Process are removed from this list before Process::Detach() returns and before the ExitProcess callback is dispatched.
+ // Thus any processes in this list should be live or have an unrecoverable error.
+ {
+ RSLockHolder ch(&m_processListMutex);
+
+ HASHFIND hfDT;
+ CordbProcess * pProcess;
+
+ for (pProcess= (CordbProcess*) m_processes.FindFirst(&hfDT);
+ pProcess != NULL;
+ pProcess = (CordbProcess*) m_processes.FindNext(&hfDT))
+ {
+ _ASSERTE(pProcess->IsSafeToSendEvents() || pProcess->m_unrecoverableError);
+ if (pProcess->IsSafeToSendEvents() && !pProcess->m_unrecoverableError)
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("Gross API misuses. Callling terminate with live process:0x%p\n", pProcess));
+ STRESS_LOG1(LF_CORDB, LL_INFO10, "Cordb::Terminate b/c of non-neutered process '%p'\n", pProcess);
+ // This is very bad.
+ // GROSS API MISUSES - Debugger is calling ICorDebug::Terminate while there
+ // are still outstanding (non-neutered) ICorDebugProcess.
+ // ICorDebug is now in an undefined state.
+ // We will also leak memory b/c we're leaving the EventThreads up (which will in turn
+ // keep a reference to this Cordb object).
+ return ErrWrapper(CORDBG_E_ILLEGAL_SHUTDOWN_ORDER);
+ }
+ }
+ }
+
+ // @todo- ideally, we'd wait for all threads to get outside of ICorDebug before we proceed.
+ // That's tough to implement in practice; but we at least wait for both ET to exit. As these
+ // guys dispatch callbacks, that means at least we'll wait until VS is outside of any callback.
+ //
+ // Stop the event handling threads.
+ //
+ if (m_rcEventThread != NULL)
+ {
+ // Stop may do significant work b/c if it drains the worker queue.
+ m_rcEventThread->Stop();
+ delete m_rcEventThread;
+ m_rcEventThread = NULL;
+ }
+
+
+#ifdef _DEBUG
+ // @todo - this disables thread-safety asserts on the process-list-hash. We clearly
+ // can't hold the lock while neutering it. (lock violation since who knows what neuter may do)
+ // @todo- we may have races beteen Cordb::Terminate and Cordb::CreateProcess as both
+ // modify the process list. This is mitigated since Terminate is supposed to be the last method called.
+ m_processes.DebugSetRSLock(NULL);
+#endif
+
+ //
+ // We expect the debugger to neuter all processes before calling Terminate(), so do not neuter them here.
+ //
+
+#ifdef _DEBUG
+ {
+ HASHFIND find;
+ _ASSERTE(m_processes.FindFirst(&find) == NULL); // should be emptied by neuter
+ }
+#endif //_DEBUG
+
+ // Officially mark us as neutered.
+ this->Neuter();
+
+ m_processListMutex.Destroy();
+
+ //
+ // Release the callbacks
+ //
+ m_managedCallback.Clear();
+ m_managedCallback2.Clear();
+ m_managedCallback3.Clear();
+ m_unmanagedCallback.Clear();
+
+ // The Shell may still have outstanding references, so we don't want to shutdown logging yet.
+ // But everything should be neutered anyways.
+
+ m_initialized = FALSE;
+
+
+ // After this, all outstanding Cordb objects should be neutered.
+ LOG((LF_CORDB, LL_EVERYTHING, "Cordb finished terminating.\n"));
+
+#if defined(_DEBUG)
+ //
+ // Assert that there are no outstanding object references within the debugging
+ // API itself.
+ //
+ CheckMemLeaks();
+#endif
+
+ return S_OK;
+}
+
+HRESULT Cordb::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebug)
+ *pInterface = static_cast<ICorDebug*>(this);
+ else if (id == IID_IUnknown)
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebug*>(this));
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+
+
+//
+// Initialize -- setup the ICorDebug object by creating any objects
+// that the object needs to operate and starting the two needed IPC
+// threads.
+//
+HRESULT Cordb::Initialize(void)
+{
+ HRESULT hr = S_OK;
+
+ FAIL_IF_NEUTERED(this);
+
+ if (!m_initialized)
+ {
+ CordbCommonBase::InitializeCommon();
+
+ // Since logging wasn't active when we called CordbBase, do it now.
+ LOG((LF_CORDB, LL_EVERYTHING, "Memory: CordbBase object allocated: this=%p, count=%d, RootObject\n", this, s_TotalObjectCount));
+ LOG((LF_CORDB, LL_INFO10, "Initializing ICorDebug...\n"));
+
+ // Ensure someone hasn't messed up the IPC buffer size
+ _ASSERTE(sizeof(DebuggerIPCEvent) <= CorDBIPC_BUFFER_SIZE);
+
+ //
+ // Init things that the Cordb will need to operate
+ //
+ m_processListMutex.Init("Process-List Lock", RSLock::cLockReentrant, RSLock::LL_PROCESS_LIST_LOCK);
+
+#ifdef _DEBUG
+ m_processes.DebugSetRSLock(&m_processListMutex);
+#endif
+
+ //
+ // Create the runtime controller event listening thread
+ //
+ m_rcEventThread = new (nothrow) CordbRCEventThread(this);
+
+ if (m_rcEventThread == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ }
+ else
+ {
+ // This stuff only creates events & starts the thread
+ hr = m_rcEventThread->Init();
+
+ if (SUCCEEDED(hr))
+ hr = m_rcEventThread->Start();
+
+ if (FAILED(hr))
+ {
+ delete m_rcEventThread;
+ m_rcEventThread = NULL;
+ }
+ }
+
+ if (FAILED(hr))
+ goto exit;
+
+ m_initialized = TRUE;
+ }
+
+exit:
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Throw if no more process can be debugged with this Cordb object.
+//
+// Notes:
+// This is highly dependent on the wait sets in the Win32 & RCET threads.
+// @dbgtodo- this will end up in the shim.
+
+void Cordb::EnsureAllowAnotherProcess()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ RSLockHolder ch(&m_processListMutex);
+
+ // Cordb, Win32, and RCET all have process sets, but Cordb's is the
+ // best count of total debuggees. The RCET set is volatile (processes
+ // are added / removed when they become synchronized), and Win32's set
+ // doesn't include all processes.
+ int cCurProcess = GetProcessList()->GetCount();
+
+ // In order to accept another debuggee, we must have a free slot in all
+ // wait sets. Currently, we don't expose the size of those sets, but
+ // we know they're MAXIMUM_WAIT_OBJECTS. Note that we lose one slot
+ // to the control event.
+ if (cCurProcess >= MAXIMUM_WAIT_OBJECTS - 1)
+ {
+ ThrowHR(CORDBG_E_TOO_MANY_PROCESSES);
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Add process to the list.
+//
+// Notes:
+// AddProcess -- add a process object to this ICorDebug's hash of processes.
+// This also tells this ICorDebug's runtime controller thread that the
+// process set has changed so it can update its list of wait events.
+//
+void Cordb::AddProcess(CordbProcess* process)
+{
+ // At this point, we should have already checked that we
+ // can have another debuggee.
+ STRESS_LOG1(LF_CORDB, LL_INFO10, "Cordb::AddProcess %08x...\n", process);
+
+ if ((m_managedCallback == NULL) || (m_managedCallback2 == NULL) || (m_managedCallback3 == NULL))
+ {
+ ThrowHR(E_FAIL);
+ }
+
+
+
+ RSLockHolder lockHolder(&m_processListMutex);
+
+ // Once we add another process, all outstanding process-enumerators become invalid.
+ m_pProcessEnumList.NeuterAndClear(NULL);
+
+ GetProcessList()->AddBaseOrThrow(process);
+ m_rcEventThread->ProcessStateChanged();
+}
+
+//
+// RemoveProcess -- remove a process object from this ICorDebug's hash of
+// processes. This also tells this ICorDebug's runtime controller thread
+// that the process set has changed so it can update its list of wait events.
+//
+void Cordb::RemoveProcess(CordbProcess* process)
+{
+ STRESS_LOG1(LF_CORDB, LL_INFO10, "Cordb::RemoveProcess %08x...\n", process);
+
+ LockProcessList();
+ GetProcessList()->RemoveBase((ULONG_PTR)process->m_id);
+
+ m_rcEventThread->ProcessStateChanged();
+
+ UnlockProcessList();
+}
+
+//
+// LockProcessList -- Lock the process list.
+//
+void Cordb::LockProcessList(void)
+{
+ m_processListMutex.Lock();
+}
+
+//
+// UnlockProcessList -- Unlock the process list.
+//
+void Cordb::UnlockProcessList(void)
+{
+ m_processListMutex.Unlock();
+}
+
+#ifdef _DEBUG
+// Return true iff this thread owns the ProcessList lock
+bool Cordb::ThreadHasProcessListLock()
+{
+ return m_processListMutex.HasLock();
+}
+#endif
+
+
+// Get the hash that has the process.
+CordbSafeHashTable<CordbProcess> *Cordb::GetProcessList()
+{
+ // If we're accessing the hash, we'd better be locked.
+ _ASSERTE(ThreadHasProcessListLock());
+
+ return &m_processes;
+}
+
+
+HRESULT Cordb::SendIPCEvent(CordbProcess * pProcess,
+ DebuggerIPCEvent * pEvent,
+ SIZE_T eventSize)
+{
+ HRESULT hr = S_OK;
+
+ LOG((LF_CORDB, LL_EVERYTHING, "SendIPCEvent in Cordb called\n"));
+ EX_TRY
+ {
+ hr = m_rcEventThread->SendIPCEvent(pProcess, pEvent, eventSize);
+ }
+ EX_CATCH_HRESULT(hr)
+ return hr;
+}
+
+
+void Cordb::ProcessStateChanged(void)
+{
+ m_rcEventThread->ProcessStateChanged();
+}
+
+
+HRESULT Cordb::WaitForIPCEventFromProcess(CordbProcess* process,
+ CordbAppDomain *pAppDomain,
+ DebuggerIPCEvent* event)
+{
+ return m_rcEventThread->WaitForIPCEventFromProcess(process,
+ pAppDomain,
+ event);
+}
+
+#ifdef FEATURE_CORECLR
+HRESULT Cordb::SetTargetCLR(HMODULE hmodTargetCLR)
+{
+ if (m_initialized)
+ return E_FAIL;
+
+#ifdef FEATURE_CORESYSTEM
+ m_targetCLR = hmodTargetCLR;
+#endif
+
+ // @REVIEW: are we happy with this workaround? It allows us to use the existing
+ // infrastructure for instance name decoration, but it really doesn't fit
+ // the same model because coreclr.dll isn't in this process and hmodTargetCLR
+ // is the debuggee target, not the coreclr.dll to bind utilcode to..
+
+ CoreClrCallbacks cccallbacks;
+ cccallbacks.m_hmodCoreCLR = hmodTargetCLR;
+ cccallbacks.m_pfnIEE = NULL;
+ cccallbacks.m_pfnGetCORSystemDirectory = NULL;
+ cccallbacks.m_pfnGetCLRFunction = NULL;
+ InitUtilcode(cccallbacks);
+
+ return S_OK;
+}
+#endif // FEATURE_CORECLR
+
+//-----------------------------------------------------------
+// ICorDebug
+//-----------------------------------------------------------
+
+// Set the handler for callbacks on managed events
+// This can not be NULL.
+// If we're debugging V2.0 apps, pCallback must implement ICDManagedCallback2
+// @todo- what if somebody calls this after we've already initialized? (eg, changes
+// the callback underneath us)
+HRESULT Cordb::SetManagedHandler(ICorDebugManagedCallback *pCallback)
+{
+ if (!m_initialized)
+ return E_FAIL;
+
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pCallback, ICorDebugManagedCallback*);
+
+ m_managedCallback.Clear();
+ m_managedCallback2.Clear();
+ m_managedCallback3.Clear();
+
+ // For SxS, V2.0 debuggers must implement ManagedCallback2 to handle v2.0 debug events.
+ // For Single-CLR, A v1.0 debugger may actually geta V2.0 debuggee.
+ pCallback->QueryInterface(IID_ICorDebugManagedCallback2, (void **)&m_managedCallback2);
+ if (m_managedCallback2 == NULL)
+ {
+ if (GetDebuggerVersion() >= CorDebugVersion_2_0)
+ {
+ // This will leave our internal callbacks null, which future operations (Create/Attach) will
+ // use to know that we're not sufficiently initialized.
+ return E_NOINTERFACE;
+ }
+ else
+ {
+ // This should only be used in a single-CLR shimming scenario.
+ m_managedCallback2.Assign(new (nothrow) DefaultManagedCallback2(this));
+
+ if (m_managedCallback2 == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+ }
+
+
+ pCallback->QueryInterface(IID_ICorDebugManagedCallback3, (void **)&m_managedCallback3);
+ if (m_managedCallback3 == NULL)
+ {
+ m_managedCallback3.Assign(new (nothrow) DefaultManagedCallback3(this));
+ }
+
+ if (m_managedCallback3 == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ m_managedCallback.Assign(pCallback);
+ return S_OK;
+}
+
+HRESULT Cordb::SetUnmanagedHandler(ICorDebugUnmanagedCallback *pCallback)
+{
+ if (!m_initialized)
+ return E_FAIL;
+
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pCallback, ICorDebugUnmanagedCallback*);
+
+ m_unmanagedCallback.Assign(pCallback);
+
+ return S_OK;
+}
+
+// CreateProcess() isn't supported on Windows CoreCLR.
+// It is currently supported on Mac CoreCLR, but that may change.
+bool Cordb::IsCreateProcessSupported()
+{
+#if defined(FEATURE_CORECLR) && !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ return false;
+#else
+ return true;
+#endif
+}
+
+// Given everything we know about our configuration, can we support interop-debugging
+bool Cordb::IsInteropDebuggingSupported()
+{
+ // We explicitly refrain from checking the unmanaged callback. See comment in
+ // ICorDebug::SetUnmanagedHandler for details.
+#ifdef FEATURE_INTEROP_DEBUGGING
+
+#if defined(FEATURE_CORECLR) && !defined(FEATURE_CORESYSTEM)
+ // Interop debugging is only supported internally on CoreCLR.
+ // Check if the special reg key is set. If not, then we don't allow interop debugging.
+ if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgEnableMixedModeDebugging) == 0)
+ {
+ return false;
+ }
+#endif // FEATURE_CORECLR
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Implementation of ICorDebug::CreateProcess.
+// Creates a process.
+//
+// Arguments:
+// The following arguments are passed thru unmodified to the OS CreateProcess API and
+// are defined by that API.
+// lpApplicationName
+// lpCommandLine
+// lpProcessAttributes
+// lpThreadAttributes
+// bInheritHandles
+// dwCreationFlags
+// lpCurrentDirectory
+// lpStartupInfo
+// lpProcessInformation
+// debuggingFlags
+//
+// ppProcess - Space to fill in for the resulting process, returned as a valid pointer
+// on any success HRESULT.
+//
+// Return Value:
+// Normal HRESULT semantics.
+//
+//---------------------------------------------------------------------------------------
+HRESULT Cordb::CreateProcess(LPCWSTR lpApplicationName,
+ __in_z LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ PVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation,
+ CorDebugCreateProcessFlags debuggingFlags,
+ ICorDebugProcess **ppProcess)
+{
+ return CreateProcessCommon(NULL,
+ lpApplicationName,
+ lpCommandLine,
+ lpProcessAttributes,
+ lpThreadAttributes,
+ bInheritHandles,
+ dwCreationFlags,
+ lpEnvironment,
+ lpCurrentDirectory,
+ lpStartupInfo,
+ lpProcessInformation,
+ debuggingFlags,
+ ppProcess);
+}
+
+HRESULT Cordb::CreateProcessCommon(ICorDebugRemoteTarget * pRemoteTarget,
+ LPCWSTR lpApplicationName,
+ __in_z LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ PVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation,
+ CorDebugCreateProcessFlags debuggingFlags,
+ ICorDebugProcess ** ppProcess)
+{
+ // If you hit this assert, it means that you are attempting to create a process without specifying the version
+ // number.
+ _ASSERTE(CorDebugInvalidVersion != m_debuggerSpecifiedVersion);
+
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppProcess, ICorDebugProcess**);
+
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ if (!m_initialized)
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ // Check that we support the debugger version
+ CheckCompatibility();
+
+ #ifdef FEATURE_INTEROP_DEBUGGING
+ // DEBUG_PROCESS (=0x1) means debug this process & all future children.
+ // DEBUG_ONLY_THIS_PROCESS =(0x2) means just debug the immediate process.
+ // If we want to support DEBUG_PROCESS, then we need to have the RS sniff for new CREATE_PROCESS
+ // events and spawn new CordbProcess for them.
+ switch(dwCreationFlags & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS))
+ {
+ // 1) managed-only debugging
+ case 0:
+ break;
+
+ // 2) failure - returns E_NOTIMPL. (as this would involve debugging all of our children processes).
+ case DEBUG_PROCESS:
+ ThrowHR(E_NOTIMPL);
+
+ // 3) Interop-debugging.
+ // Note that MSDN (at least as of Jan 2003) is wrong about this flag. MSDN claims
+ // DEBUG_ONLY_THIS_PROCESS w/o DEBUG_PROCESS should be ignored.
+ // But it really should do launch as a debuggee (but not auto-attach to child processes).
+ case DEBUG_ONLY_THIS_PROCESS:
+ // Emprically, this is the common case for native / interop-debugging.
+ break;
+
+ // 4) Interop.
+ // The spec for ICorDebug::CreateProcess says this is the one to use for interop-debugging.
+ case DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS:
+ // Win2k does not honor these flags properly. So we just use
+ // It treats (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS) as if it were DEBUG_PROCESS.
+ // We'll just always touch up the flags, even though WinXP and above is fine here.
+ // Per win2k issue, strip off DEBUG_PROCESS, so that we're just left w/ DEBUG_ONLY_THIS_PROCESS.
+ dwCreationFlags &= ~(DEBUG_PROCESS);
+ break;
+
+ default:
+ __assume(0);
+ }
+
+ #endif // FEATURE_INTEROP_DEBUGGING
+
+ // Must have a managed-callback by now.
+ if ((m_managedCallback == NULL) || (m_managedCallback2 == NULL) || (m_managedCallback3 == NULL))
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ if (!IsCreateProcessSupported())
+ {
+ ThrowHR(E_NOTIMPL);
+ }
+
+ if (!IsInteropDebuggingSupported() &&
+ ((dwCreationFlags & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS)) != 0))
+ {
+ ThrowHR(CORDBG_E_INTEROP_NOT_SUPPORTED);
+ }
+
+ // Check that we can even accept another debuggee before trying anything.
+ EnsureAllowAnotherProcess();
+
+ } EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ hr = ShimProcess::CreateProcess(this,
+ pRemoteTarget,
+ lpApplicationName,
+ lpCommandLine,
+ lpProcessAttributes,
+ lpThreadAttributes,
+ bInheritHandles,
+ dwCreationFlags,
+ lpEnvironment,
+ lpCurrentDirectory,
+ lpStartupInfo,
+ lpProcessInformation,
+ debuggingFlags
+ );
+
+ LOG((LF_CORDB, LL_EVERYTHING, "Handle in Cordb::CreateProcess is: %.I64x\n", lpProcessInformation->hProcess));
+
+ if (SUCCEEDED(hr))
+ {
+ LockProcessList();
+
+ CordbProcess * pProcess = GetProcessList()->GetBase(lpProcessInformation->dwProcessId);
+
+ UnlockProcessList();
+
+ PREFIX_ASSUME(pProcess != NULL);
+
+ pProcess->ExternalAddRef();
+ *ppProcess = (ICorDebugProcess *)pProcess;
+ }
+
+ return hr;
+}
+
+
+HRESULT Cordb::CreateProcessEx(ICorDebugRemoteTarget * pRemoteTarget,
+ LPCWSTR lpApplicationName,
+ __in_z LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ PVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation,
+ CorDebugCreateProcessFlags debuggingFlags,
+ ICorDebugProcess ** ppProcess)
+{
+ if (pRemoteTarget == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ return CreateProcessCommon(pRemoteTarget,
+ lpApplicationName,
+ lpCommandLine,
+ lpProcessAttributes,
+ lpThreadAttributes,
+ bInheritHandles,
+ dwCreationFlags,
+ lpEnvironment,
+ lpCurrentDirectory,
+ lpStartupInfo,
+ lpProcessInformation,
+ debuggingFlags,
+ ppProcess);
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Attachs to an existing process.
+//
+// Arguments:
+// dwProcessID - The PID to attach to
+// fWin32Attach - Flag to tell whether to attach as the Win32 debugger or not.
+// ppProcess - Space to fill in for the resulting process, returned as a valid pointer
+// on any success HRESULT.
+//
+// Return Value:
+// Normal HRESULT semantics.
+//
+//---------------------------------------------------------------------------------------
+HRESULT Cordb::DebugActiveProcess(DWORD dwProcessId,
+ BOOL fWin32Attach,
+ ICorDebugProcess **ppProcess)
+{
+ return DebugActiveProcessCommon(NULL, dwProcessId, fWin32Attach, ppProcess);
+}
+
+HRESULT Cordb::DebugActiveProcessCommon(ICorDebugRemoteTarget * pRemoteTarget,
+ DWORD dwProcessId,
+ BOOL fWin32Attach,
+ ICorDebugProcess ** ppProcess)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppProcess, ICorDebugProcess **);
+
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ if (!m_initialized)
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ // Must have a managed-callback by now.
+ if ((m_managedCallback == NULL) || (m_managedCallback2 == NULL) || (m_managedCallback3 == NULL))
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ // See the comment in Cordb::CreateProcess
+ _ASSERTE(CorDebugInvalidVersion != m_debuggerSpecifiedVersion);
+
+ // Check that we support the debugger version
+ CheckCompatibility();
+
+ // Check that we can even accept another debuggee before trying anything.
+ EnsureAllowAnotherProcess();
+
+ // Check if we're allowed to do interop.
+ bool fAllowInterop = IsInteropDebuggingSupported();
+
+ if (!fAllowInterop && fWin32Attach)
+ {
+ ThrowHR(CORDBG_E_INTEROP_NOT_SUPPORTED);
+ }
+
+ } EX_CATCH_HRESULT(hr)
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ hr = ShimProcess::DebugActiveProcess(
+ this,
+ pRemoteTarget,
+ dwProcessId,
+ fWin32Attach == TRUE);
+
+ // If that worked, then there will be a process object...
+ if (SUCCEEDED(hr))
+ {
+ LockProcessList();
+ CordbProcess * pProcess = GetProcessList()->GetBase(dwProcessId);
+
+ if (pProcess != NULL)
+ {
+ // Add a reference now so process won't go away
+ pProcess->ExternalAddRef();
+ }
+ UnlockProcessList();
+
+ if (pProcess == NULL)
+ {
+ // This can happen if we add the process into process hash in
+ // SendDebugActiveProcessEvent and then process exit
+ // before we attemp to retrieve it again from GetBase.
+ //
+ *ppProcess = NULL;
+ return S_FALSE;
+ }
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ // This is where we queue the managed attach event in Whidbey. In the new architecture, the Windows
+ // pipeline gets a loader breakpoint when native attach is completed, and that's where we queue the
+ // managed attach event. See how we handle the loader breakpoint in code:ShimProcess::DefaultEventHandler.
+ // However, the Mac debugging transport gets no such breakpoint, and so we need to do this here.
+ //
+ // @dbgtodo Mac - Ideally we should hide this in our pipeline implementation, or at least move
+ // this to the shim.
+ _ASSERTE(!fWin32Attach);
+ {
+ pProcess->Lock();
+ hr = pProcess->QueueManagedAttach();
+ pProcess->Unlock();
+ }
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+ *ppProcess = (ICorDebugProcess*) pProcess;
+ }
+
+ return hr;
+}
+
+// Make sure we want to support the debugger that's using us
+void Cordb::CheckCompatibility()
+{
+ // Get the debugger version specified by the startup APIs and convert it to a CLR major version number
+ CorDebugInterfaceVersion debuggerVersion = GetDebuggerVersion();
+ DWORD clrMajor;
+ if (debuggerVersion <= CorDebugVersion_1_0 || debuggerVersion == CorDebugVersion_1_1)
+ clrMajor = 1;
+ else if (debuggerVersion <= CorDebugVersion_2_0)
+ clrMajor = 2;
+ else if (debuggerVersion <= CorDebugVersion_4_0)
+ clrMajor = 4;
+ else
+ clrMajor = 5; // some unrecognized future version
+
+ if(!CordbProcess::IsCompatibleWith(clrMajor))
+ {
+ // Carefully choose our error-code to get an appropriate error-message from VS 2008
+ // If GetDebuggerVersion is >= 4, we could consider using the more-appropriate (but not
+ // added until V4) HRESULT CORDBG_E_UNSUPPORTED_FORWARD_COMPAT that is used by
+ // OpenVirtualProcess, but it's probably simpler to keep ICorDebug APIs returning
+ // consistent error codes.
+ ThrowHR(CORDBG_E_INCOMPATIBLE_PROTOCOL);
+ }
+}
+
+HRESULT Cordb::DebugActiveProcessEx(ICorDebugRemoteTarget * pRemoteTarget,
+ DWORD dwProcessId,
+ BOOL fWin32Attach,
+ ICorDebugProcess ** ppProcess)
+{
+ if (pRemoteTarget == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ return DebugActiveProcessCommon(pRemoteTarget, dwProcessId, fWin32Attach, ppProcess);
+}
+
+
+HRESULT Cordb::GetProcess(DWORD dwProcessId, ICorDebugProcess **ppProcess)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppProcess, ICorDebugProcess**);
+
+ if (!m_initialized)
+ {
+ return E_FAIL;
+ }
+
+ LockProcessList();
+ CordbProcess *p = GetProcessList()->GetBase(dwProcessId);
+ UnlockProcessList();
+
+ if (p == NULL)
+ return E_INVALIDARG;
+
+ p->ExternalAddRef();
+ *ppProcess = static_cast<ICorDebugProcess*> (p);
+
+ return S_OK;
+}
+
+HRESULT Cordb::EnumerateProcesses(ICorDebugProcessEnum **ppProcesses)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppProcesses, ICorDebugProcessEnum **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (!m_initialized)
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ // Locking here just means that the enumerator gets initialized against a consistent
+ // process-list. If we add/remove processes w/ an outstanding enumerator, things
+ // could still get out of sync.
+ RSLockHolder lockHolder(&this->m_processListMutex);
+
+ RSInitHolder<CordbHashTableEnum> pEnum;
+ CordbHashTableEnum::BuildOrThrow(
+ this,
+ &m_pProcessEnumList,
+ GetProcessList(),
+ IID_ICorDebugProcessEnum,
+ pEnum.GetAddr());
+
+
+ pEnum.TransferOwnershipExternal(ppProcesses);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+
+//
+// Note: the following defs and structs are copied from various NT headers. I wasn't able to include those headers (like
+// ntexapi.h) due to loads of redef problems and other conflicts with headers that we already pull in.
+//
+typedef LONG NTSTATUS;
+
+#ifndef FEATURE_PAL
+typedef BOOL (*NTQUERYSYSTEMINFORMATION)(SYSTEM_INFORMATION_CLASS SystemInformationClass,
+ PVOID SystemInformation,
+ ULONG SystemInformationLength,
+ PULONG ReturnLength);
+#endif
+
+// Implementation of ICorDebug::CanLaunchOrAttach
+// @dbgtodo- this all goes away in V3.
+// @dbgtodo- this should go away in Dev11.
+HRESULT Cordb::CanLaunchOrAttach(DWORD dwProcessId, BOOL fWin32DebuggingEnabled)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ EnsureCanLaunchOrAttach(fWin32DebuggingEnabled);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Throw an expcetion if we can't launch/attach.
+//
+// Arguments:
+// fWin32DebuggingEnabled - true if interop-debugging, else false
+//
+// Return Value:
+// None. If this returns, then it's safe to launch/attach.
+// Else this throws an exception on failure.
+//
+// Assumptions:
+//
+// Notes:
+// It should always be safe to launch/attach except in exceptional cases.
+// @dbgtodo- this all goes away in V3.
+// @dbgtodo- this should go away in Dev11.
+//
+void Cordb::EnsureCanLaunchOrAttach(BOOL fWin32DebuggingEnabled)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+ if (!m_initialized)
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ EnsureAllowAnotherProcess();
+
+ if (!IsInteropDebuggingSupported() && fWin32DebuggingEnabled)
+ {
+ ThrowHR(CORDBG_E_INTEROP_NOT_SUPPORTED);
+ }
+
+ // Made it this far, we succeeded.
+}
+
+HRESULT Cordb::CreateObjectV1(REFIID id, void **object)
+{
+ return CreateObject(CorDebugVersion_1_0, id, object);
+}
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+// CoreCLR activates debugger objects via direct COM rather than the shim (just like V1). For now we share the
+// same debug engine version as V2, though this may change in the future.
+HRESULT Cordb::CreateObjectTelesto(REFIID id, void ** pObject)
+{
+ return CreateObject(CorDebugVersion_2_0, id, pObject);
+}
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+// Static
+// Used to create an instance for a ClassFactory (thus an external ref).
+HRESULT Cordb::CreateObject(CorDebugInterfaceVersion iDebuggerVersion, REFIID id, void **object)
+{
+ if (id != IID_IUnknown && id != IID_ICorDebug)
+ return (E_NOINTERFACE);
+
+ Cordb *db = new (nothrow) Cordb(iDebuggerVersion);
+
+ if (db == NULL)
+ return (E_OUTOFMEMORY);
+
+ *object = static_cast<ICorDebug*> (db);
+ db->ExternalAddRef();
+
+ return (S_OK);
+}
+
+
+// This is the version of the ICorDebug APIs that the debugger believes it's consuming.
+// If this is a different version than that of the debuggee, we have the option of shimming
+// behavior.
+CorDebugInterfaceVersion
+Cordb::GetDebuggerVersion() const
+{
+ return m_debuggerSpecifiedVersion;
+}
+
+//***********************************************************************
+// ICorDebugTMEnum (Thread and Module enumerator)
+//***********************************************************************
+CordbEnumFilter::CordbEnumFilter(CordbBase * pOwnerObj, NeuterList * pOwnerList)
+ : CordbBase (pOwnerObj->GetProcess(), 0),
+ m_pOwnerObj(pOwnerObj),
+ m_pOwnerNeuterList(pOwnerList),
+ m_pFirst (NULL),
+ m_pCurrent (NULL),
+ m_iCount (0)
+{
+ _ASSERTE(m_pOwnerNeuterList != NULL);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ m_pOwnerNeuterList->Add(pOwnerObj->GetProcess(), this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+
+}
+
+CordbEnumFilter::CordbEnumFilter(CordbEnumFilter *src)
+ : CordbBase (src->GetProcess(), 0),
+ m_pOwnerObj(src->m_pOwnerObj),
+ m_pOwnerNeuterList(src->m_pOwnerNeuterList),
+ m_pFirst (NULL),
+ m_pCurrent (NULL)
+{
+ _ASSERTE(m_pOwnerNeuterList != NULL);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ m_pOwnerNeuterList->Add(src->GetProcess(), this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+
+
+
+ int iCountSanityCheck = 0;
+ EnumElement *pElementCur = NULL;
+ EnumElement *pElementNew = NULL;
+ EnumElement *pElementNewPrev = NULL;
+
+ m_iCount = src->m_iCount;
+
+ pElementCur = src->m_pFirst;
+
+ while (pElementCur != NULL)
+ {
+ pElementNew = new (nothrow) EnumElement;
+ if (pElementNew == NULL)
+ {
+ // Out of memory. Clean up and bail out.
+ goto Error;
+ }
+
+ if (pElementNewPrev == NULL)
+ {
+ m_pFirst = pElementNew;
+ }
+ else
+ {
+ pElementNewPrev->SetNext(pElementNew);
+ }
+
+ pElementNewPrev = pElementNew;
+
+ // Copy the element, including the AddRef part
+ pElementNew->SetData(pElementCur->GetData());
+ IUnknown *iu = (IUnknown *)pElementCur->GetData();
+ iu->AddRef();
+
+ if (pElementCur == src->m_pCurrent)
+ m_pCurrent = pElementNew;
+
+ pElementCur = pElementCur->GetNext();
+ iCountSanityCheck++;
+ }
+
+ _ASSERTE(iCountSanityCheck == m_iCount);
+
+ return;
+Error:
+ // release all the allocated memory before returning
+ pElementCur = m_pFirst;
+
+ while (pElementCur != NULL)
+ {
+ pElementNewPrev = pElementCur;
+ pElementCur = pElementCur->GetNext();
+
+ ((ICorDebugModule *)pElementNewPrev->GetData())->Release();
+ delete pElementNewPrev;
+ }
+}
+
+CordbEnumFilter::~CordbEnumFilter()
+{
+ _ASSERTE(this->IsNeutered());
+
+ _ASSERTE(m_pFirst == NULL);
+}
+
+void CordbEnumFilter::Neuter()
+{
+ EnumElement *pElement = m_pFirst;
+ EnumElement *pPrevious = NULL;
+
+ while (pElement != NULL)
+ {
+ pPrevious = pElement;
+ pElement = pElement->GetNext();
+ delete pPrevious;
+ }
+
+ // Null out the head in case we get neutered again.
+ m_pFirst = NULL;
+ m_pCurrent = NULL;
+
+ CordbBase::Neuter();
+}
+
+
+
+HRESULT CordbEnumFilter::QueryInterface(REFIID id, void **ppInterface)
+{
+ // if we QI with the IID of the base type, we can't just return a pointer ICorDebugEnum directly, because
+ // the cast is ambiguous. This happens because CordbEnumFilter implements both ICorDebugModuleEnum and
+ // ICorDebugThreadEnum, both of which derive in turn from ICorDebugEnum. This produces a diamond inheritance
+ // graph. Thus we need a double cast. It doesn't really matter whether we pick ICorDebugThreadEnum or
+ // ICorDebugModuleEnum, because it will be backed by the same object regardless.
+ if (id == IID_ICorDebugEnum)
+ *ppInterface = static_cast<ICorDebugEnum *>(static_cast<ICorDebugThreadEnum *>(this));
+ else if (id == IID_ICorDebugModuleEnum)
+ *ppInterface = (ICorDebugModuleEnum*)this;
+ else if (id == IID_ICorDebugThreadEnum)
+ *ppInterface = (ICorDebugThreadEnum*)this;
+ else if (id == IID_IUnknown)
+ *ppInterface = this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbEnumFilter::Skip(ULONG celt)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ while ((celt-- > 0) && (m_pCurrent != NULL))
+ {
+ m_pCurrent = m_pCurrent->GetNext();
+ }
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+HRESULT CordbEnumFilter::Reset()
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ m_pCurrent = m_pFirst;
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+HRESULT CordbEnumFilter::Clone(ICorDebugEnum **ppEnum)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ ValidateOrThrow(ppEnum);
+
+ CordbEnumFilter * pClone = new CordbEnumFilter(this);
+
+ // Ambigous conversion from CordbEnumFilter to ICorDebugEnum, so
+ // we explicitly convert it through ICorDebugThreadEnum.
+ pClone->ExternalAddRef();
+ (*ppEnum) = static_cast<ICorDebugThreadEnum *> (pClone);
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+HRESULT CordbEnumFilter::GetCount(ULONG *pcelt)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ ValidateOrThrow(pcelt);
+ *pcelt = (ULONG)m_iCount;
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+HRESULT CordbEnumFilter::Next(ULONG celt,
+ ICorDebugModule *objects[],
+ ULONG *pceltFetched)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ hr = NextWorker(celt, objects, pceltFetched);
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+HRESULT CordbEnumFilter::NextWorker(ULONG celt, ICorDebugModule *objects[], ULONG *pceltFetched)
+{
+ // <TODO>
+ //
+ // nickbe 11/20/2002 10:43:39
+ // This function allows you to enumerate threads that "belong" to a
+ // particular AppDomain. While this operation makes some sense, it makes
+ // very little sense to
+ // (a) enumerate the list of threads in the enter process
+ // (b) build up a hand-rolled singly linked list (grrr)
+ // </TODO>
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(objects, ICorDebugModule *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched, ULONG *);
+
+ if ((pceltFetched == NULL) && (celt != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ if (celt == 0)
+ {
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = 0;
+ }
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+
+ ULONG count = 0;
+
+ while ((m_pCurrent != NULL) && (count < celt))
+ {
+ objects[count] = (ICorDebugModule *)m_pCurrent->GetData();
+ m_pCurrent = m_pCurrent->GetNext();
+ count++;
+ }
+
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = count;
+ }
+
+ //
+ // If we reached the end of the enumeration, but not the end
+ // of the number of requested items, we return S_FALSE.
+ //
+ if (count < celt)
+ {
+ return S_FALSE;
+ }
+
+ return hr;
+}
+
+
+HRESULT CordbEnumFilter::Next(ULONG celt,
+ ICorDebugThread *objects[],
+ ULONG *pceltFetched)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ hr = NextWorker(celt, objects, pceltFetched);
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+HRESULT CordbEnumFilter::NextWorker(ULONG celt, ICorDebugThread *objects[], ULONG *pceltFetched)
+{
+ // @TODO remove this class
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(objects, ICorDebugThread *, celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched, ULONG *);
+
+ if ((pceltFetched == NULL) && (celt != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ if (celt == 0)
+ {
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = 0;
+ }
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+
+ ULONG count = 0;
+
+ while ((m_pCurrent != NULL) && (count < celt))
+ {
+ objects[count] = (ICorDebugThread *)m_pCurrent->GetData();
+ m_pCurrent = m_pCurrent->GetNext();
+ count++;
+ }
+
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = count;
+ }
+
+ //
+ // If we reached the end of the enumeration, but not the end
+ // of the number of requested items, we return S_FALSE.
+ //
+ if (count < celt)
+ {
+ return S_FALSE;
+ }
+
+ return hr;
+}
+
+
+
+HRESULT CordbEnumFilter::Init (ICorDebugModuleEnum * pModEnum, CordbAssembly *pAssembly)
+{
+ INTERNAL_API_ENTRY(GetProcess());
+
+ ICorDebugModule *pCorModule = NULL;
+ CordbModule *pModule = NULL;
+ ULONG ulDummy = 0;
+
+ HRESULT hr = pModEnum->Next(1, &pCorModule, &ulDummy);
+
+ //
+ // Next returns E_FAIL if there is no next item, along with
+ // the count being 0. Convert that to just being S_OK.
+ //
+ if ((hr == E_FAIL) && (ulDummy == 0))
+ {
+ hr = S_OK;
+ }
+
+ if (FAILED (hr))
+ return hr;
+
+ EnumElement *pPrevious = NULL;
+ EnumElement *pElement = NULL;
+
+ while (ulDummy != 0)
+ {
+ pModule = (CordbModule *)(ICorDebugModule *)pCorModule;
+ // Is this module part of the assembly for which we're enumerating?
+ if (pModule->m_pAssembly == pAssembly)
+ {
+ pElement = new (nothrow) EnumElement;
+ if (pElement == NULL)
+ {
+ // Out of memory. Clean up and bail out.
+ hr = E_OUTOFMEMORY;
+ goto Error;
+ }
+
+ pElement->SetData ((void *)pCorModule);
+ m_iCount++;
+
+ if (m_pFirst == NULL)
+ {
+ m_pFirst = pElement;
+ }
+ else
+ {
+ PREFIX_ASSUME(pPrevious != NULL);
+ pPrevious->SetNext (pElement);
+ }
+ pPrevious = pElement;
+ }
+ else
+ ((ICorDebugModule *)pModule)->Release();
+
+ hr = pModEnum->Next(1, &pCorModule, &ulDummy);
+
+ //
+ // Next returns E_FAIL if there is no next item, along with
+ // the count being 0. Convert that to just being S_OK.
+ //
+ if ((hr == E_FAIL) && (ulDummy == 0))
+ {
+ hr = S_OK;
+ }
+
+ if (FAILED (hr))
+ goto Error;
+ }
+
+ m_pCurrent = m_pFirst;
+
+ return S_OK;
+
+Error:
+ // release all the allocated memory before returning
+ pElement = m_pFirst;
+
+ while (pElement != NULL)
+ {
+ pPrevious = pElement;
+ pElement = pElement->GetNext();
+
+ ((ICorDebugModule *)pPrevious->GetData())->Release();
+ delete pPrevious;
+ }
+
+ return hr;
+}
+
+HRESULT CordbEnumFilter::Init (ICorDebugThreadEnum *pThreadEnum, CordbAppDomain *pAppDomain)
+{
+ INTERNAL_API_ENTRY(GetProcess());
+
+ ICorDebugThread *pCorThread = NULL;
+ CordbThread *pThread = NULL;
+ ULONG ulDummy = 0;
+
+ HRESULT hr = pThreadEnum->Next(1, &pCorThread, &ulDummy);
+
+ //
+ // Next returns E_FAIL if there is no next item, but we want to consider this
+ // ok in this context.
+ //
+ if ((hr == E_FAIL) && (ulDummy == 0))
+ {
+ hr = S_OK;
+ }
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ EnumElement *pPrevious = NULL;
+ EnumElement *pElement = NULL;
+
+ while (ulDummy > 0)
+ {
+ pThread = (CordbThread *)(ICorDebugThread *) pCorThread;
+
+ // Is this module part of the appdomain for which we're enumerating?
+ // Note that this is rather inefficient (we call into the left side for every AppDomain),
+ // but the whole idea of enumerating the threads of an AppDomain is pretty bad,
+ // and we don't expect this to be used much if at all.
+ CordbAppDomain* pThreadDomain;
+ hr = pThread->GetCurrentAppDomain( &pThreadDomain );
+ if( FAILED(hr) )
+ {
+ goto Error;
+ }
+
+ if (pThreadDomain == pAppDomain)
+ {
+ pElement = new (nothrow) EnumElement;
+ if (pElement == NULL)
+ {
+ // Out of memory. Clean up and bail out.
+ hr = E_OUTOFMEMORY;
+ goto Error;
+ }
+
+ pElement->SetData ((void *)pCorThread);
+ m_iCount++;
+
+ if (m_pFirst == NULL)
+ {
+ m_pFirst = pElement;
+ }
+ else
+ {
+ PREFIX_ASSUME(pPrevious != NULL);
+ pPrevious->SetNext (pElement);
+ }
+
+ pPrevious = pElement;
+ }
+ else
+ {
+ ((ICorDebugThread *)pThread)->Release();
+ }
+
+ // get the next thread in the thread list
+ hr = pThreadEnum->Next(1, &pCorThread, &ulDummy);
+
+ //
+ // Next returns E_FAIL if there is no next item, along with
+ // the count being 0. Convert that to just being S_OK.
+ //
+ if ((hr == E_FAIL) && (ulDummy == 0))
+ {
+ hr = S_OK;
+ }
+
+ if (FAILED (hr))
+ goto Error;
+ }
+
+ m_pCurrent = m_pFirst;
+
+ return S_OK;
+
+Error:
+ // release all the allocated memory before returning
+ pElement = m_pFirst;
+
+ while (pElement != NULL)
+ {
+ pPrevious = pElement;
+ pElement = pElement->GetNext();
+
+ ((ICorDebugThread *)pPrevious->GetData())->Release();
+ delete pPrevious;
+ }
+
+ return hr;
+}
+
diff --git a/src/debug/di/rsmda.cpp b/src/debug/di/rsmda.cpp
new file mode 100644
index 0000000000..d69b448309
--- /dev/null
+++ b/src/debug/di/rsmda.cpp
@@ -0,0 +1,243 @@
+// 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.
+//*****************************************************************************
+// File: RsMda.cpp
+//
+
+// Manage Debug Assistant support in the Right-Side
+//
+//*****************************************************************************
+#include "stdafx.h"
+
+#include "winbase.h"
+#include "corpriv.h"
+
+//-----------------------------------------------------------------------------
+// Cordb MDA notification
+//-----------------------------------------------------------------------------
+CordbMDA::CordbMDA(CordbProcess * pProc, DebuggerMDANotification * pData)
+: CordbBase(pProc, 0, enumCordbMDA)
+{
+ _ASSERTE(pData != NULL);
+
+ // Owning Parent process should add us to process'es neuter list.
+
+ // Pick up ownership of strings
+ m_szName = pData->szName.TransferStringData();
+ m_szDescription = pData->szDescription.TransferStringData();
+ m_szXml = pData->szXml.TransferStringData();
+
+ m_dwOSTID = pData->dwOSThreadId;
+ m_flags = pData->flags;
+}
+
+//-----------------------------------------------------------------------------
+// Destructor for CordbMDA object. Not much to do here since neutering should
+// have taken care of it all.
+//-----------------------------------------------------------------------------
+CordbMDA::~CordbMDA()
+{
+ // Strings protected w/ holders that will automatically free them.
+ _ASSERTE(IsNeutered());
+}
+
+//-----------------------------------------------------------------------------
+// Neuter the CordbMDA object.
+//-----------------------------------------------------------------------------
+void CordbMDA::Neuter()
+{
+ // Release buffers. Once we're neutered, these can no longer be accessed anyways,
+ // so may as well free them now.
+ // This is being done under the process-lock, and our accessors are also done
+ // under that lock, so we don't have to worry about any races here. :)
+ m_szName.Clear();
+ m_szDescription.Clear();
+ m_szXml.Clear();
+
+ CordbBase::Neuter();
+};
+
+//-----------------------------------------------------------------------------
+// Implement IUnknown::QueryInterface.
+//-----------------------------------------------------------------------------
+HRESULT CordbMDA::QueryInterface(REFIID riid, void **ppInterface)
+{
+ if (riid == IID_ICorDebugMDA)
+ *ppInterface = static_cast<ICorDebugMDA*>(this);
+ else if (riid == IID_IUnknown)
+ *ppInterface = static_cast<IUnknown*>(static_cast<ICorDebugMDA*>(this));
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Helper to marshal a string object out through the ICorDebug interfaces
+// *GetName() functions using the common triple design pattern.
+//
+// parameters:
+// pInputString - the string that we want to marshal out via the triple
+// cchName, pcchName, szName - triple used to marshal out a string.
+// Same usage as CordbModule::GetName and other string getters on the API.
+//
+// *pcchName is always set to the length of pInputString (including NULL). This lets
+// callers know the full size of buffer they'd need to allocate to get the full string.
+//
+// if (cchName == 0) then we're in "query" mode:
+// szName must be null. pcchName must be non-null and this function will just set
+// *pcchName to let the caller know how large of a buffer to allocate.
+
+// if (cchName != 0) then we copy as much as can fit into szName. We will always
+// null terminate szName.
+// pcchName can be null. If it's non-null, we set it.
+//
+//
+// Expected usage is that caller calls us twice, once in query mode to allocate
+// buffer, then a 2nd time to fill the buffer.
+//
+// Returns: S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CopyOutString(LPCWSTR pInputString, ULONG32 cchName, ULONG32 * pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ _ASSERTE(pInputString != NULL);
+ ULONG32 len = (ULONG32) wcslen(pInputString) + 1;
+
+ if (cchName == 0)
+ {
+ // Query length
+ if ((szName != NULL) || (pcchName == NULL))
+ {
+ return E_INVALIDARG;
+ }
+ *pcchName = len;
+ return S_OK;
+ }
+ else
+ {
+ // Get data
+ if (szName == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ // Just copy whatever we can fit into the buffer. If we truncate, that's ok.
+ // This will also guarantee that we null terminate.
+ wcsncpy_s(szName, cchName, pInputString, _TRUNCATE);
+
+ if (pcchName != 0)
+ {
+ *pcchName = len;
+ }
+
+ return S_OK;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Get the string for the type of the MDA. Never empty.
+// This is a convenient performant alternative to getting the XML stream and extracting
+// the type from that based off the schema.
+// See CopyOutString for parameter details.
+//-----------------------------------------------------------------------------
+HRESULT CordbMDA::GetName(ULONG32 cchName, ULONG32 * pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this)
+ {
+#if defined(FEATURE_CORECLR)
+ hr = E_NOTIMPL;
+#else // !FEATURE_CORECLR
+ hr = CopyOutString(m_szName, cchName, pcchName, szName);
+#endif // FEATURE_CORECLR
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Get a string description of the MDA. This may be empty (0-length).
+// See CopyOutString for parameter details.
+//-----------------------------------------------------------------------------
+HRESULT CordbMDA::GetDescription(ULONG32 cchName, ULONG32 * pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this)
+ {
+#if defined(FEATURE_CORECLR)
+ hr = E_NOTIMPL;
+#else // !FEATURE_CORECLR
+ hr = CopyOutString(m_szDescription, cchName, pcchName, szName);
+#endif // FEATURE_CORECLR
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Get the full associated XML for the MDA. This may be empty.
+// This could be a potentially expensive operation if the xml stream is large.
+// See the MDA documentation for the schema for this XML stream.
+// See CopyOutString for parameter details.
+//-----------------------------------------------------------------------------
+HRESULT CordbMDA::GetXML(ULONG32 cchName, ULONG32 * pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this)
+ {
+#if defined(FEATURE_CORECLR)
+ hr = E_NOTIMPL;
+#else // !FEATURE_CORECLR
+ hr = CopyOutString(m_szXml, cchName, pcchName, szName);
+#endif // FEATURE_CORECLR
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Get flags for this MDA object.
+//-----------------------------------------------------------------------------
+HRESULT CordbMDA::GetFlags(CorDebugMDAFlags * pFlags)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this)
+ {
+#if defined(FEATURE_CORECLR)
+ hr = E_NOTIMPL;
+#else // !FEATURE_CORECLR
+ ValidateOrThrow(pFlags);
+ *pFlags = this->m_flags;
+#endif // FEATURE_CORECLR
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Thread that the MDA is fired on. We use the os tid instead of an ICDThread in case an MDA is fired on a
+// native thread (or a managed thread that hasn't yet entered managed code and so we don't have a ICDThread
+// object for it yet)
+//-----------------------------------------------------------------------------
+HRESULT CordbMDA::GetOSThreadId(DWORD * pOsTid)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this)
+ {
+#if defined(FEATURE_CORECLR)
+ hr = E_NOTIMPL;
+#else // !FEATURE_CORECLR
+ ValidateOrThrow(pOsTid);
+
+ *pOsTid = this->m_dwOSTID;
+#endif // FEATURE_CORECLR
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
diff --git a/src/debug/di/rspriv.h b/src/debug/di/rspriv.h
new file mode 100644
index 0000000000..853767804a
--- /dev/null
+++ b/src/debug/di/rspriv.h
@@ -0,0 +1,11756 @@
+// 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.
+//*****************************************************************************
+// rspriv.
+//
+
+//
+// Common include file for right-side of debugger.
+//*****************************************************************************
+
+#ifndef RSPRIV_H
+#define RSPRIV_H
+
+#include <winwrap.h>
+#include <windows.h>
+
+#include <utilcode.h>
+
+
+#ifdef _DEBUG
+#define LOGGING
+#endif
+
+#include <log.h>
+#include <corerror.h>
+
+#include "cor.h"
+
+#include "cordebug.h"
+#include "xcordebug.h"
+#include "cordbpriv.h"
+#include "mscoree.h"
+
+#include <cordbpriv.h>
+#include <dbgipcevents.h>
+
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+#include <ipcmanagerinterface.h>
+#endif // !FEATURE_DBGIPC_TRANSPORT_DI
+
+#include "common.h"
+#include "primitives.h"
+
+#include "dacdbiinterface.h"
+
+#include "helpers.h"
+
+struct MachineInfo;
+
+#include "nativepipeline.h"
+#include "stringcopyholder.h"
+
+
+#include "eventchannel.h"
+
+#undef ASSERT
+#define CRASH(x) _ASSERTE(!x)
+#define ASSERT(x) _ASSERTE(x)
+
+// We want to keep the 'worst' HRESULT - if one has failed (..._E_...) & the
+// other hasn't, take the failing one. If they've both/neither failed, then
+// it doesn't matter which we take.
+// Note that this macro favors retaining the first argument
+#define WORST_HR(hr1,hr2) (FAILED(hr1)?hr1:hr2)
+
+// #UseDataTarget
+// Forbid usage of OS APIs that we should be using the data-target for
+#define ReadProcessMemory DONT_USE_READPROCESS_MEMORY
+#define WriteProcessMemory DONT_USE_WRITEPROCESS_MEMORY
+
+
+/* ------------------------------------------------------------------------- *
+ * Forward class declarations
+ * ------------------------------------------------------------------------- */
+
+class CordbBase;
+class CordbValue;
+class CordbModule;
+class CordbClass;
+class CordbFunction;
+class CordbCode;
+class CordbFrame;
+class CordbJITILFrame;
+class CordbInternalFrame;
+class CordbContext;
+class CordbThread;
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+class CordbUnmanagedThread;
+struct CordbUnmanagedEvent;
+#endif
+
+class CordbProcess;
+class CordbAppDomain;
+class CordbAssembly;
+class CordbBreakpoint;
+class CordbStepper;
+class Cordb;
+class CordbEnCSnapshot;
+class CordbWin32EventThread;
+class CordbRCEventThread;
+class CordbRegisterSet;
+class CordbNativeFrame;
+class CordbObjectValue;
+class CordbEnCErrorInfo;
+class CordbEnCErrorInfoEnum;
+class Instantiation;
+class CordbType;
+class CordbNativeCode;
+class CordbILCode;
+class CordbReJitILCode;
+class CordbEval;
+
+class CordbMDA;
+
+class CorpubPublish;
+class CorpubProcess;
+class CorpubAppDomain;
+class CorpubProcessEnum;
+class CorpubAppDomainEnum;
+
+
+class RSLock;
+class NeuterList;
+
+class IDacDbiInterface;
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+class DbgTransportTarget;
+class DbgTransportSession;
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+// @dbgtodo private shim hook - the RS has private hooks into the shim to help bridge the V2/V3 gap.
+// This helps provide a working dogfooding story throughout our transition.
+// These hooks must be removed before shipping.
+class ShimProcess;
+
+
+#ifndef FEATURE_PAL
+extern HINSTANCE GetModuleInst();
+#endif
+
+
+template <class T>
+class CordbSafeHashTable;
+
+
+//---------------------------------------------------------------------------------------
+//
+// This is an encapsulation of the information necessary to connect to the debugger proxy on a remote machine.
+// It includes the IP address and the port number. The IP address can be set via the env var
+// COMPlus_DbgTransportProxyAddress, and the port number is fixed when Mac debugging is configured.
+//
+
+struct MachineInfo
+{
+public:
+ void Init(DWORD dwIPAddress, USHORT usPort)
+ {
+ m_dwIPAddress = dwIPAddress;
+ m_usPort = usPort;
+ }
+
+ void Clear()
+ {
+ m_dwIPAddress = 0;
+ m_usPort = 0;
+ }
+
+ DWORD GetIPAddress() {return m_dwIPAddress;};
+ USHORT GetPort() {return m_usPort;};
+
+private:
+ DWORD m_dwIPAddress;
+ USHORT m_usPort;
+};
+
+#define forDbi (*(forDbiWorker *)NULL)
+
+// for dbi we just default to new, but we need to have these defined for both dac and dbi
+inline void * operator new(size_t lenBytes, const forDbiWorker &)
+{
+ void * result = new BYTE[lenBytes];
+ if (result == NULL)
+ {
+ ThrowOutOfMemory();
+ }
+ return result;
+}
+
+inline void * operator new[](size_t lenBytes, const forDbiWorker &)
+{
+ void * result = new BYTE[lenBytes];
+ if (result == NULL)
+ {
+ ThrowOutOfMemory();
+ }
+ return result;
+}
+
+// Helper to delete memory used with the IDacDbiInterface::IAllocator interface.
+template<class T> inline
+void DeleteDbiMemory(T *p)
+{
+ delete p;
+}
+
+
+
+//---------------------------------------------------------------------------------------
+//
+// Simple array of holders (either RSSmartPtrs or RSExtSmartPtrs).
+// Holds a reference to each element.
+//
+// Notes:
+// T is the base type and HOLDER_T is the type of the holder. All functions implemented on this base
+// class must work for both RSSmartPtrs and RSExtSmartPtrs. For example, there is no concept of neutering
+// for RSExtSmartPtrs.
+//
+
+template<typename T, typename HOLDER_T>
+class BaseRSPtrArray
+{
+public:
+ BaseRSPtrArray()
+ {
+ m_pArray = NULL;
+ m_cElements = 0;
+ }
+
+ // Is the array emtpy?
+ bool IsEmpty() const
+ {
+ return (m_pArray == NULL);
+ }
+
+ // Allocate an array of ptrs.
+ // Returns false if not enough memory; else true.
+ bool Alloc(unsigned int cElements)
+ {
+ // Caller should have already Neutered
+ _ASSERTE(IsEmpty());
+
+ // It's legal to allocate 0 items. We'll succeed the allocation, but still claim that IsEmpty() == true.
+ if (cElements == 0)
+ {
+ return true;
+ }
+
+ // RSSmartPtr ctor will ensure all elements are null initialized.
+ m_pArray = new (nothrow) HOLDER_T [cElements];
+ if (m_pArray == NULL)
+ {
+ return false;
+ }
+
+ m_cElements = cElements;
+ return true;
+ }
+
+ // Allocate an array of ptrs.
+ // Throw on failure
+ void AllocOrThrow(unsigned int cElements)
+ {
+ if (!Alloc(cElements))
+ {
+ ThrowOutOfMemory();
+ }
+ }
+
+ // Release each element and empty the array.
+ void Clear()
+ {
+ // this Invoke dtors on each element which will release each element
+ delete [] m_pArray;
+
+ m_pArray = NULL;
+ m_cElements = 0;
+ }
+
+ // Array lookup. Caller gaurantees this is in range.
+ // Used for reading
+ T* operator [] (unsigned int index) const
+ {
+ _ASSERTE(m_pArray != NULL);
+ CONSISTENCY_CHECK_MSGF((index <= m_cElements), ("Index out of range. Index=%u, Max=%u\n", index, m_cElements));
+
+ return m_pArray[index];
+ }
+
+ // Assign a given index to the given value. The array holder will increment the internal reference on the value.
+ void Assign(unsigned int index, T* pValue)
+ {
+ _ASSERTE(m_pArray != NULL);
+ CONSISTENCY_CHECK_MSGF((index <= m_cElements), ("Index out of range. Index=%u, Max=%u\n", index, m_cElements));
+
+ m_pArray[index].Assign(pValue);
+ }
+
+ // Get lenght of array in elements.
+ unsigned int Length() const
+ {
+ return m_cElements;
+ }
+
+ // Some things need to get the address of an element in the table.
+ // For example, CordbThreads have an array of CordbFrame objects, and then CordbChains describe a range
+ // or frames via pointers into the CordbThread's array.
+ // This is a dangerous operation because it lets us side-step reference counting and protection.
+ T ** UnsafeGetAddrOfIndex(unsigned int index)
+ {
+ return m_pArray[index].UnsafeGetAddr();
+ }
+
+protected:
+ // Raw array of values.
+ HOLDER_T * m_pArray;
+
+ // Number of elements in m_pArray. Note the following is always true: (m_cElements == 0) == (m_pArray == NULL);
+ unsigned int m_cElements;
+};
+
+
+//-----------------------------------------------------------------------------
+//
+// Simple array holder of RSSmartPtrs (internal pointers).
+// Holds a reference to each element.
+//
+// Notes:
+// This derived class adds the concept of neutering to the base pointer array.
+// Allows automatic Clear()ing; do not use this unless it is safe to do so in
+// all cases - e.g. you're holding a local.
+//
+
+template< typename T, typename HOLDER_T = RSSmartPtr<T> > // We need to use HOLDER_T to make gcc happy.
+class RSPtrArray : public BaseRSPtrArray<T, HOLDER_T>
+{
+private:
+ typedef BaseRSPtrArray<T, HOLDER_T> Super;
+ BOOL m_autoClear;
+
+public:
+ RSPtrArray() : m_autoClear(FALSE)
+ {
+ }
+
+ ~RSPtrArray()
+ {
+ if (m_autoClear)
+ {
+ Super::Clear();
+ }
+ else
+ {
+ // Caller should have already Neutered
+ _ASSERTE(Super::IsEmpty());
+ }
+ }
+
+ void EnableAutoClear()
+ {
+ m_autoClear = TRUE;
+ }
+
+ // Neuter all elements in the array.
+ void NeuterAndClear()
+ {
+ for(unsigned int i = 0; i < Super::m_cElements; i++)
+ {
+ if (Super::m_pArray[i] != NULL)
+ {
+ Super::m_pArray[i]->Neuter();
+ }
+ }
+
+ Super::Clear();
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+//
+// Simple array holder of RSExtSmartPtrs (external pointers).
+// Holds a reference to each element.
+//
+// Notes:
+// This derived class clears the array in its destructor.
+//
+
+template< typename T, typename HOLDER_T = RSExtSmartPtr<T> > // We need to use HOLDER_T to make gcc happy.
+class RSExtPtrArray : public BaseRSPtrArray<T, HOLDER_T>
+{
+private:
+ typedef BaseRSPtrArray<T, HOLDER_T> Super;
+
+public:
+ ~RSExtPtrArray()
+ {
+ Super::Clear();
+ }
+};
+
+
+
+//-----------------------------------------------------------------------------
+// Table for RSptrs
+// This lets us map cookies <--> RSPTR_*,
+// Then we just put the cookie in the IPC block instead of the raw RSPTR.
+// This will also adjust the internal-reference count on the T* object.
+// This isolates the RS from bugs in the LS.
+// We templatize by type for type safety.
+// Caller must syncrhonize all access (preferably w/ the stop-go lock).
+//-----------------------------------------------------------------------------
+template <class T>
+class RsPtrTable
+{
+public:
+ RsPtrTable()
+ {
+ m_pTable = NULL;
+ m_cEntries = 0;
+ }
+ ~RsPtrTable()
+ {
+ Clear();
+ }
+ void Clear()
+ {
+ for(UINT i = 0; i < m_cEntries; i++)
+ {
+ if (m_pTable[i])
+ {
+ m_pTable[i]->InternalRelease();
+ }
+ }
+ delete [] m_pTable;
+ m_pTable = NULL;
+ m_cEntries = 0;
+ }
+
+ // Add a value into table. Value can't be NULL.
+ // Returns 0 on failure (such as oom),
+ // Returns a non-zero cookie on success.
+ UINT Add(T* pValue)
+ {
+ _ASSERTE(pValue != NULL);
+ // skip 0 because it's an invalid handle.
+ for(UINT i = 1; ; i++)
+ {
+ // If we've run out of space, allocate new space
+ if( i >= m_cEntries )
+ {
+ if( !Grow() )
+ {
+ return 0; // failed to grow
+ }
+ _ASSERTE( i < m_cEntries );
+ _ASSERTE( m_pTable[i] == NULL );
+ // Since we grew, the next slot should now be open.
+ }
+
+ if (m_pTable[i] == NULL)
+ {
+ m_pTable[i] = pValue;
+ pValue->InternalAddRef();
+ return i;
+ }
+ }
+ UNREACHABLE();
+ }
+
+ // Lookup the value based off the cookie, which was obtained via "Add".
+ // return NULL on error.
+ T* Lookup(UINT cookie)
+ {
+ _ASSERTE(cookie != 0);
+ if (cookie >= m_cEntries)
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("Cookie out of range.Cookie=0x%x. Size=0x%x.\n", cookie, m_cEntries));
+ return NULL;
+ }
+ T* p = m_pTable[cookie];
+ if (p == NULL)
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("Cookie is for empty slot.Cookie=0x%x.\n", cookie));
+ return NULL; // empty!
+ }
+ return p;
+ }
+
+ T* LookupAndRemove(UINT cookie)
+ {
+ _ASSERTE(cookie != 0);
+ T* p = Lookup(cookie);
+ if (p != NULL)
+ {
+ m_pTable[cookie] = NULL;
+ p->InternalRelease();
+ }
+ return p;
+ }
+
+protected:
+ // Resize the m_pTable array.
+ bool Grow()
+ {
+ if (m_pTable == NULL)
+ {
+ _ASSERTE(m_cEntries == 0);
+ size_t cSize = 10;
+ m_pTable = new (nothrow) T*[cSize];
+ if (m_pTable == NULL)
+ {
+ return false;
+ }
+ m_cEntries = cSize;
+ ZeroMemory(m_pTable, sizeof(T*) * m_cEntries);
+ return true;
+ }
+ size_t cNewSize = (m_cEntries * 3 / 2) + 1;
+ _ASSERTE(cNewSize > m_cEntries);
+ T** p = new (nothrow) T*[cNewSize];
+ if (p == NULL)
+ {
+ return false;
+ }
+ ZeroMemory(p, sizeof(T*) * cNewSize);
+
+
+ // Copy over old stuff
+ memcpy(p, m_pTable, sizeof(T*) * m_cEntries);
+ delete [] m_pTable;
+
+ m_pTable = p;
+ m_cEntries = cNewSize;
+ return true;
+ }
+
+ T** m_pTable;
+ size_t m_cEntries;
+};
+
+
+
+//-----------------------------------------------------------------------------
+// Simple Holder for RS object intialization to cooperate with Neutering
+// semantics.
+// The ctor will do an addref.
+// The dtor (invoked in exception) will neuter and release the object. This
+// release will likely be the final release to cause a delete.
+// If the object is created successfully, caller should do a SuppressRelease()
+// to avoid it getting neutered.
+//
+// Example:
+// RSInitHolder<CordbFoo> pFoo(new CordbFoo(x,y,z));
+// pFoo->InitMore(a,b,c);
+// GiveOwnershipToSomebodyElse(pFoo); // now somebody else owns and will clean up
+// pFoo.ClearAndMarkDontNeuter(); // we no longer need to
+//
+// So if an exception is thrown before ClearAndMarkDontNeuter(), the dtor is invoked
+// and the object is properly destroyed (deleted and neutered).
+//
+// Another common pattern is when initializing an object to hand off to an external:
+// RSInitHolder<CordbFoo> pFoo(new CordbFoo(x,y,z));
+// pFoo->InitMore(a,b,c);
+// pFoo.TransferOwnershipExternal(ppOutParameter);
+// TransferOwnershipExternal will assign to ppOutParameter, inc external ref, and
+// call ClearAndMarkDontNeuter()
+//-----------------------------------------------------------------------------
+template<class T>
+class RSInitHolder
+{
+public:
+ // Default ctor. Must call Assign() later.
+ RSInitHolder()
+ {
+ };
+ RSInitHolder(T * pObject)
+ {
+ Assign(pObject);
+ }
+
+ void Assign(T * pObject)
+ {
+ _ASSERTE(m_pObject == NULL); // only assign once.
+ m_pObject.Assign(pObject);
+ }
+ ~RSInitHolder();
+
+ FORCEINLINE operator T *() const
+ {
+ return m_pObject;
+
+ }
+ FORCEINLINE T * operator->()
+ {
+ return m_pObject;
+ }
+
+ // This will null out m_pObject such that the dtor will not neuter it.
+ // This will also release the ref we took in the ctor.
+ // This will clear the current pointer.
+ void ClearAndMarkDontNeuter()
+ {
+ m_pObject.Clear();
+ }
+
+ //
+ // Transfer ownership to a pointer
+ //
+ // Arguments:
+ // ppOutParam - pointer to get ownership. External Reference is incremented.
+ // this pointer should do an external release.
+ //
+ // Notes:
+ // This calls ClearAndMarkDontNeuter(). This holder is Empty after this.
+ template <class TOther>
+ void TransferOwnershipExternal(TOther ** ppOutParam)
+ {
+ *ppOutParam = static_cast<TOther*> (m_pObject);
+ m_pObject->ExternalAddRef();
+
+ ClearAndMarkDontNeuter();
+ }
+
+
+ //
+ // Transfer the ownership of the wrapped object to the given hash table.
+ //
+ // Arguments:
+ // pHashTable - hash table to take ownership.
+ //
+ // Returns:
+ // the contianing object for convenience. Throws on error (particularly
+ // if it fails adding to the hash).
+ //
+ // Notes:
+ // This calls ClearAndMarkDontNeuter(). This holder is Empty after this.
+ T* TransferOwnershipToHash(CordbSafeHashTable<T> * pHashtable)
+ {
+ T* pObject = m_pObject;
+ pHashtable->AddBaseOrThrow(m_pObject);
+ ClearAndMarkDontNeuter();
+ return pObject;
+ }
+
+ //
+ // Used to pass into a function that will assign to us.
+ //
+ // Returns:
+ // Address of this holder. This is like the & operator.
+ // This is provided for consistency with other holders which
+ // override the &operator.
+ RSInitHolder<T> * GetAddr()
+ {
+ return this;
+ }
+
+
+protected:
+ RSSmartPtr<T> m_pObject;
+};
+
+
+
+//-----------------------------------------------------------------------------
+// Have the extra level of indirection is useful for catching Cordbg errors.
+//-----------------------------------------------------------------------------
+#ifdef _DEBUG
+ // On debug, we have an opportunity to catch failing hresults during reproes.
+ #define ErrWrapper(hr) ErrWrapperHelper(hr, __FILE__, __LINE__)
+
+ inline HRESULT ErrWrapperHelper(HRESULT hr, const char * szFile, int line)
+ {
+ if (FAILED(hr))
+ {
+ DWORD dwErr = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgBreakOnErr);
+ if (dwErr)
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("Dbg Error break, hr=0x%08x, '%s':%d", hr, szFile, line));
+ }
+ }
+ return hr;
+ }
+#else
+ // On release, it's just an identity function
+ #define ErrWrapper(hr) (hr)
+#endif
+
+//-----------------------------------------------------------------------------
+// Quick helpers for threading semantics
+//-----------------------------------------------------------------------------
+
+bool IsWin32EventThread(CordbProcess* p);
+bool IsRCEventThread(Cordb* p);
+
+/* ------------------------------------------------------------------------- *
+ * Typedefs
+ * ------------------------------------------------------------------------- */
+
+typedef void* REMOTE_PTR;
+
+
+//-----------------------------------------------------------------------------
+// Wrapper class for locks. This is like Crst on the LS
+//-----------------------------------------------------------------------------
+
+class RSLock
+{
+public:
+ // Attrs, can be bitwise-or together.
+ enum ELockAttr
+ {
+ cLockUninit = 0x00000000,
+ cLockReentrant = 0x00000001,
+ cLockFlat = 0x00000002,
+
+ // (unusual). Not considered a debug API lock, for purposes of deciding whether
+ // to count this lock in m_cTotalDbgApiLocks, which is asserted to be 0 on entry
+ // to public APIs. Example of such a lock: LL_SHIM_PROCESS_DISPOSE_LOCK
+ cLockNonDbgApi = 0x00000004,
+ };
+
+ // To prevent deadlocks, we order all locks.
+ // A thread must acquire higher-numbered locks before lower numbered locks.
+ // These are used as indices into an array, so number them accordingly!
+ enum ERSLockLevel
+ {
+ // Size of the array..
+ LL_MAX = 6,
+
+ // The Stop-Go lock is used to make Stop + Continue be atomic operations.
+ // These methods will toggle the Process-lock b/c they go between multiple threads.
+ // This lock can never be taken on the Win32 ET.
+ LL_STOP_GO_LOCK = 5,
+
+ // The win32-event-thread behaves as if it held a lock at this level.
+ LL_WIN32_EVENT_THREAD = 4,
+
+ // This held for the duration of ShimProcess::Dispose(), and protects
+ // ShimProcess::m_fIsDisposed, so that other ShimProcess functions can
+ // safely execute serially with ShimProcess::Dispose(). This needs to be
+ // a high-level lock, since ShimProcess methods that take this lock also
+ // call into CorDb* objects which take many of the other locks. In contrast,
+ // LL_SHIM_LOCK must remain low-level, as there exists at least one place where
+ // LL_SHIM_LOCK is taken while the CorDbProcess lock is also held (see
+ // CordbThread::GetActiveFunctions which takes the CorDbProcess lock while
+ // calling GetProcess()->GetShim()->LookupOrCreateShimStackWalk(this), which
+ // takes LL_SHIM_LOCK).
+ LL_SHIM_PROCESS_DISPOSE_LOCK = 3,
+
+ // The process lock is the primary lock for a CordbProcess object. It synchronizes
+ // between RCET, W32ET, and user threads.
+ LL_PROCESS_LOCK = 2,
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ LL_DBG_TRANSPORT_MANAGER_LOCK = 1,
+
+ LL_DBG_TRANSPORT_TARGET_LOCK = 0,
+
+ LL_DD_MARSHAL_LOCK = 0,
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+ // These are all leaf locks (they don't take any other lock once they're held).
+ LL_PROCESS_LIST_LOCK = 0,
+
+ // Win32 send lock is shared by all processes accessing a single w32et.
+ LL_WIN32_SEND_LOCK = 0,
+
+ // Small lock around sending IPC events to support workarounds in func-eval abort.
+ // See code:CordbEval::Abort for details.
+ LL_FUNC_EVAL_ABORT_HACK_LOCK = 0,
+
+ // Leaf-level lock used in the shim.
+ LL_SHIM_LOCK = 0
+ };
+
+ // Initialize a lock w/ debugging info. szTag must be a string literal.
+ void Init(const char * szTag, int eAttr, ERSLockLevel level);
+ void Destroy();
+
+ void Lock();
+ void Unlock();
+
+protected:
+ // Accessors for holders.
+ static void HolderEnter(RSLock * pLock)
+ {
+ pLock->Lock();
+ }
+ static void HolderLeave(RSLock * pLock)
+ {
+ pLock->Unlock();
+ }
+
+
+ CRITICAL_SECTION m_lock;
+
+#ifdef _DEBUG
+public:
+ RSLock();
+ ~RSLock();
+
+ const char * Name() { return m_szTag; }
+
+ // Returns true if this thread has the lock.
+ bool HasLock();
+
+ // Returns true if this is safe to take on this thread (ie, this thread
+ // doesn't already hold bigger locks).
+ // bool IsSafeToTake();
+
+ ERSLockLevel GetLevel() { return m_level; }
+
+ // If we're inited, we must have either cLockReentrant or cLockFlat specified.
+ bool IsInit() { return m_eAttr != 0; }
+ bool IsReentrant() { return (m_eAttr & cLockReentrant) == cLockReentrant; }
+ bool IsDbgApiLock() { return ((m_eAttr & cLockNonDbgApi) == 0); }
+
+protected:
+ ERSLockLevel m_level;
+ int m_eAttr; // Bitwise combination of ELockAttr values
+ int m_count;
+ DWORD m_tidOwner;
+ const char * m_szTag;
+
+#endif // #if debug
+
+public:
+ typedef Holder<RSLock *, RSLock::HolderEnter, RSLock::HolderLeave> RSLockHolder;
+ typedef Holder<RSLock *, RSLock::HolderLeave, RSLock::HolderEnter> RSInverseLockHolder;
+
+};
+
+typedef RSLock::RSLockHolder RSLockHolder;
+typedef RSLock::RSInverseLockHolder RSInverseLockHolder;
+
+// In the RS, we should be using RSLocks instead of raw critical sections.
+#define CRITICAL_SECTION USE_RSLOCK_INSTEAD_OF_CRITICAL_SECTION
+
+
+/* ------------------------------------------------------------------------- *
+ * Helper macros. Use the ATT_* macros below instead of these.
+ * ------------------------------------------------------------------------- */
+
+// This serves as glue for exceptions. Eventually, we shouldn't have unrecoverable
+// error, and instead, errors should just propogate up.
+#define SetUnrecoverableIfFailed(__p, __hr) \
+ if (FAILED(__hr)) \
+ { \
+ CORDBSetUnrecoverableError(__p, __hr, 0); \
+ }
+
+#define CORDBSetUnrecoverableError(__p, __hr, __code) \
+ ((__p)->UnrecoverableError((__hr), (__code), __FILE__, __LINE__))
+
+#define _CORDBCheckProcessStateOK(__p) \
+ (!((__p)->m_unrecoverableError) && !((__p)->m_terminated) && !((__p)->m_detached))
+
+#define _CORDBCheckProcessStateOKAndSync(__p, __c) \
+ (!((__p)->m_unrecoverableError) && !((__p)->m_terminated) && !((__p)->m_detached) && \
+ (__p)->GetSynchronized())
+
+// Worker to get failure HR from given state. If not in a failure state, it yields __defaultHR.
+// If a caller knows that we're in a failure state, it can pass in a failure value for __defaultHR.
+#define CORDBHRFromProcessStateWorker(__p, __c, __defaultHR) \
+ ((__p)->m_unrecoverableError ? CORDBG_E_UNRECOVERABLE_ERROR : \
+ ((__p)->m_detached ? CORDBG_E_PROCESS_DETACHED : \
+ ((__p)->m_terminated ? CORDBG_E_PROCESS_TERMINATED : \
+ (!(__p)->GetSynchronized() ? CORDBG_E_PROCESS_NOT_SYNCHRONIZED \
+ : (__defaultHR)))))
+
+#define CORDBHRFromProcessState(__p, __c) \
+ CORDBHRFromProcessStateWorker(__p, __c, S_OK) \
+
+
+// Have a set of helper macros to check the process state and return a failure code.
+// These only should be used at public interface boundaries, in which case we should
+// not be holding the process lock. But we have enough places where we use them internally,
+// so we can't really assert that we're not holding the lock.
+
+// We're very restricted in what APIs we can call on the w32et. Have
+// a convenient check for this.
+// If we have no shim, then nop this check because everything becomes like the w32-event-thread.
+#define CORDBFailOrThrowIfOnWin32EventThread(__p, errorAction) \
+ { \
+ if (((__p)->GetShim() != NULL) && (__p)->IsWin32EventThread()) \
+ { \
+ _ASSERTE(!"Don't call on this thread"); \
+ errorAction(ErrWrapper(CORDBG_E_CANT_CALL_ON_THIS_THREAD)); \
+ } \
+ }
+
+#define CORDBFailIfOnWin32EventThread(__p) CORDBFailOrThrowIfOnWin32EventThread(__p, return)
+
+#define CORDBRequireProcessStateOK(__p) { \
+ if (!_CORDBCheckProcessStateOK(__p)) \
+ return ErrWrapper(CORDBHRFromProcessState(__p, NULL)); }
+
+// If we need to be synced, then we shouldn't be on the win32 Event-Thread.
+#define CORDBRequireProcessStateOKAndSync(__p,__c) { \
+ CORDBFailIfOnWin32EventThread(__p); \
+ if (!_CORDBCheckProcessStateOKAndSync(__p, __c)) \
+ return ErrWrapper(CORDBHRFromProcessState(__p, __c)); }
+
+#define CORDBRequireProcessSynchronized(__p, __c) { \
+ CORDBFailIfOnWin32EventThread(__p); \
+ if (!(__p)->GetSynchronized()) return ErrWrapper(CORDBG_E_PROCESS_NOT_SYNCHRONIZED);}
+
+
+
+
+//-----------------------------------------------------------------------------
+// All public APIS fall into 2 categories regarding their API Threading Type (ATT)
+// We use a standard set of macros to define & enforce each type.
+//
+// (1) ATT_REQUIRE_STOPPED
+// We must be stopped (either synced or at a win32 event) to call this API.
+// - We'll fail if we're not stopped.
+// - If we're stopped, we'll sync. Thus after this API, we're always synced,
+// and Cordbg must call Continue to resume the process.
+// - We'll take the Stop-Go-lock. This prevents another thread from continuing underneath us.
+// - We may send IPC events.
+// Common for APIs like Stacktracing
+//
+// (2) ATT_ALLOW_LIVE
+// We do not have to be stopped to call this API.
+// - We can be live, thus we can not take the stop-go lock (unless it's from a SC-holder).
+// - If we're going to send IPC events, we must use a Stop-Continue holder.
+// - Our stop-status is the same after this API as it was before.
+// Common usage: read-only APIs.
+//
+// (2b) ATT_ALLOW_LIVE_DO_STOPGO.
+// - shortcut macro to do #2, but throw in a stop-continue holder. These really
+// should be in camp #1, but that would require an interface change.
+//-----------------------------------------------------------------------------
+
+// Helper macros for the ATT stuff
+
+// Do checks that need to be done before we take the SG lock. These include checks
+// where if we fail them, taking the SG lock could deadlock (such as being on win32 thread).
+#define DO_PRE_STOP_GO_CHECKS(errorAction) \
+ CORDBFailOrThrowIfOnWin32EventThread(__proc_for_ATT, errorAction) \
+ if ((__proc_for_ATT)->m_unrecoverableError) { errorAction(CORDBG_E_UNRECOVERABLE_ERROR); } \
+
+// Do checks after we take the SG lock. These include checks that rely on state protected
+// by the SG lock.
+#define DO_POST_STOP_GO_CHECKS(errorAction) \
+ _ASSERTE((this->GetProcess() == __proc_for_ATT) || this->IsNeutered()); \
+ if (this->IsNeutered()) { errorAction(CORDBG_E_OBJECT_NEUTERED); } \
+
+// #1
+// The exact details here are rocket-science.
+// We cache the __proc value to a local variable (__proc_for_ATT) so that we don't re-evaluate __proc. (It also forces type-safety).
+// This is essential in case __proc is something like "this->GetProcess()" and which can start returning NULL if 'this'
+// gets neutered underneath us. Caching guarantees that we'll be able to make it to the StopGo-lock.
+//
+// We explicitily check some things before taking the Stop-Go lock:
+// - CORDBG_E_UNRECOVERABLE_ERROR before the lock because if that's set,
+// we may have leaked locks to the outside world, so taking the StopGo lock later could fail.
+// - Are we on the W32et - can't take sg lock if on W32et
+// Then we immediately take the stop-go lock to prevent another thread from continuing underneath us.
+// Then, if we're stopped, we ensure that we're also synced.
+// Stopped includes:
+// - Win32-stopped
+// - fake win32-stopped. Eg, between SuspendUnmanagedThreads & ResumeUnmanagedThreads
+// (one way to get here is getting debug events during the special-deferment region)
+// - synchronized
+// If we're not stopped, then we fail. This macro must never return S_OK.
+//
+// If not-shimmed (using V3 pipeline), then skip all checks about stop-state.
+#define ATT_REQUIRE_STOPPED_MAY_FAIL_OR_THROW(__proc, errorAction) \
+ CordbProcess * __proc_for_ATT = (__proc); \
+ DO_PRE_STOP_GO_CHECKS(errorAction); \
+ RSLockHolder __ch(__proc_for_ATT->GetStopGoLock()); \
+ DO_POST_STOP_GO_CHECKS(errorAction); \
+ if ((__proc_for_ATT)->GetShim() != NULL) { \
+ if (!__proc_for_ATT->m_initialized) { errorAction(CORDBG_E_NOTREADY); } \
+ if ((__proc_for_ATT)->IsStopped()) { \
+ HRESULT _hr2 = (__proc_for_ATT)->StartSyncFromWin32Stop(NULL); \
+ if (FAILED(_hr2)) errorAction(_hr2); \
+ } \
+ if (!_CORDBCheckProcessStateOKAndSync(__proc_for_ATT, NULL)) \
+ errorAction(CORDBHRFromProcessStateWorker(__proc_for_ATT, NULL, E_FAIL)); \
+ }
+
+#define ATT_REQUIRE_STOPPED_MAY_FAIL(__proc)ATT_REQUIRE_STOPPED_MAY_FAIL_OR_THROW(__proc, return)
+
+// #1b - allows it to be non-inited. This should look just like ATT_REQUIRE_STOPPED_MAY_FAIL_OR_THROW
+// except it doesn't do SSFW32Stop and doesn't have the m_initialized check.
+#define ATT_REQUIRE_SYNCED_OR_NONINIT_MAY_FAIL(__proc) \
+ CordbProcess * __proc_for_ATT = (__proc); \
+ DO_PRE_STOP_GO_CHECKS(return); \
+ RSLockHolder __ch(__proc_for_ATT->GetStopGoLock()); \
+ DO_POST_STOP_GO_CHECKS(return); \
+ if ((__proc_for_ATT)->GetShim() != NULL) { \
+ if (!_CORDBCheckProcessStateOKAndSync(__proc_for_ATT, NULL)) \
+ return CORDBHRFromProcessStateWorker(__proc_for_ATT, NULL, E_FAIL); \
+ }
+
+
+
+// Gross variant on #1.
+// This is a very dangerous ATT contract; but we need to support it for backwards compat.
+// Some APIs, like ICDProcess:EnumerateThreads can be used before the process is actually
+// initialized (kind of for interop-debugging).
+// These can't check the m_initialized flag b/c that may not be set yet.
+// They also can't sync the runtime.
+// This should only be used for non-blocking leaf activity.
+#define ATT_EVERETT_HACK_REQUIRE_STOPPED_ALLOW_NONINIT(__proc) \
+ CordbProcess * __proc_for_ATT = (__proc); \
+ DO_PRE_STOP_GO_CHECKS(return); \
+ RSLockHolder __ch(__proc_for_ATT->GetStopGoLock()); \
+ DO_POST_STOP_GO_CHECKS(return); \
+ if (((__proc_for_ATT)->GetShim() != NULL) && !(__proc_for_ATT)->IsStopped()) { return CORDBG_E_PROCESS_NOT_SYNCHRONIZED; } \
+
+
+// #2 - caller may think debuggee is live, but throw in a Stop-Continue holder.
+#define ATT_ALLOW_LIVE_DO_STOPGO(__proc) \
+ CordbProcess * __proc_for_ATT = (__proc); \
+ DO_PRE_STOP_GO_CHECKS(return); \
+ CORDBRequireProcessStateOK(__proc_for_ATT); \
+ RSLockHolder __ch(__proc_for_ATT->GetStopGoLock()); \
+ DO_POST_STOP_GO_CHECKS(return); \
+ StopContinueHolder __hStopGo; \
+ if ((__proc_for_ATT)->GetShim() != NULL) \
+ { \
+ HRESULT _hr2 = __hStopGo.Init(__proc_for_ATT); \
+ if (FAILED(_hr2)) return _hr2; \
+ _ASSERTE((__proc_for_ATT)->GetSynchronized()); \
+ } \
+
+
+
+
+//-----------------------------------------------------------------------------
+// StopContinueHolder. Ensure that we're synced during a certain region.
+// (Particularly when sending an IPCEvent)
+// Calls ICorDebugProcess::Stop & IMDArocess::Continue.
+// Example usage:
+//
+// {
+// StopContinueHolder h;
+// IfFailRet(h.Init(process))
+// SendIPCEvent
+// } // continue automatically called.
+//-----------------------------------------------------------------------------
+
+class CordbProcess;
+class StopContinueHolder
+{
+public:
+ StopContinueHolder() : m_p(NULL) { };
+
+ HRESULT Init(CordbProcess * p);
+ ~StopContinueHolder();
+
+protected:
+ CordbProcess * m_p;
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Base class
+ * ------------------------------------------------------------------------- */
+
+#define COM_METHOD HRESULT STDMETHODCALLTYPE
+
+typedef enum {
+ enumCordbUnknown, // 0
+ enumCordb, // 1 1 [1]x1
+ enumCordbProcess, // 2 1 [1]x1
+ enumCordbAppDomain, // 3 1 [1]x1
+ enumCordbAssembly, // 4
+ enumCordbModule, // 5 15 [27-38,55-57]x1
+ enumCordbClass, // 6
+ enumCordbFunction, // 7
+ enumCordbThread, // 8 2 [4,7]x1
+ enumCordbCode, // 9
+ enumCordbChain, // 10
+ enumCordbChainEnum, // 11
+ enumCordbContext, // 12
+ enumCordbFrame, // 13
+ enumCordbFrameEnum, // 14
+ enumCordbValueEnum, // 15
+ enumCordbRegisterSet, // 16
+ enumCordbJITILFrame, // 17
+ enumCordbBreakpoint, // 18
+ enumCordbStepper, // 19
+ enumCordbValue, // 20
+ enumCordbEnCSnapshot, // 21
+ enumCordbEval, // 22
+ enumCordbUnmanagedThread,// 23
+ enumCorpubPublish, // 24
+ enumCorpubProcess, // 25
+ enumCorpubAppDomain, // 26
+ enumCorpubProcessEnum, // 27
+ enumCorpubAppDomainEnum,// 28
+ enumCordbEnumFilter, // 29
+ enumCordbEnCErrorInfo, // 30
+ enumCordbEnCErrorInfoEnum,//31
+ enumCordbUnmanagedEvent,// 32
+ enumCordbWin32EventThread,//33
+ enumCordbRCEventThread, // 34
+ enumCordbNativeFrame, // 35
+ enumCordbObjectValue, // 36
+ enumCordbType, // 37
+ enumCordbNativeCode, // 38
+ enumCordbILCode, // 39
+ enumCordbEval2, // 40
+ enumCordbMDA, // 41
+ enumCordbHashTableEnum, // 42
+ enumCordbCodeEnum, // 43
+ enumCordbStackWalk, // 44
+ enumCordbEnumerator, // 45
+ enumCordbHeap, // 48
+ enumCordbHeapSegments, // 47
+ enumMaxDerived, //
+ enumMaxThis = 1024
+} enumCordbDerived;
+
+
+
+//-----------------------------------------------------------------------------
+// Support for Native Breakpoints
+//-----------------------------------------------------------------------------
+struct NativePatch
+{
+ void * pAddress; // pointer into the LS address space.
+ PRD_TYPE opcode; // opcode to restore with.
+
+ inline bool operator==(NativePatch p2)
+ {
+ return memcmp(this, &p2, sizeof(p2)) == 0;
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Cross-platform patch operations
+//-----------------------------------------------------------------------------
+
+// Remove the int3 from the remote address
+HRESULT RemoveRemotePatch(CordbProcess * pProcess, const void * pRemoteAddress, PRD_TYPE opcode);
+
+// This flavor is assuming our caller already knows the opcode.
+HRESULT ApplyRemotePatch(CordbProcess * pProcess, const void * pRemoteAddress);
+
+// Apply the patch and get the opcode that we're replacing.
+HRESULT ApplyRemotePatch(CordbProcess * pProcess, const void * pRemoteAddress, PRD_TYPE * pOpcode);
+
+
+class CordbHashTable;
+
+#define CORDB_COMMON_BASE_SIGNATURE 0x0d00d96a
+#define CORDB_COMMON_BASE_SIGNATURE_DEAD 0x0dead0b1
+
+// Common base for both CorPublish + CorDebug objects.
+class CordbCommonBase : public IUnknown
+{
+public:
+ // GENERIC: made this private as I'm changing the use of m_id for CordbClass, and
+ // I want to make sure I catch all the places where m_id is used directly and cast
+ // to/from tokens and/or (void*).
+ UINT_PTR m_id;
+
+#ifdef _DEBUG
+ static LONG m_saDwInstance[enumMaxDerived]; // instance x this
+ static LONG m_saDwAlive[enumMaxDerived];
+ static PVOID m_sdThis[enumMaxDerived][enumMaxThis];
+ DWORD m_dwInstance;
+ enumCordbDerived m_type;
+#endif
+
+
+
+private:
+ DWORD m_signature : 30;
+
+ // Sticky bit set when we neuter an object. All methods (besides AddRef,Release,QI)
+ // should check this bit and fail via the FAIL_IF_NEUTERED macro.
+ DWORD m_fIsNeutered : 1;
+
+ // Mark that this object can be "neutered at will". NeuterList::SweepAllNeuterAtWillObjects
+ // looks at this bit.
+ // For some objects, we don't explicitly mark when the lifetime is up. The only way
+ // we know is when external count goes to 0. This avoids forcing us to do cleanup
+ // in the dtor (which may come at a bad time). Sticky bit set in BaseRelease().
+ DWORD m_fNeuterAtWill : 1;
+public:
+
+ static LONG s_CordbObjectUID; // Unique ID for each object.
+ static LONG s_TotalObjectCount; // total number of outstanding objects.
+
+
+ void ValidateObject()
+ {
+ if( !IsValidObject() )
+ {
+ STRESS_LOG1(LF_ASSERT, LL_ALWAYS, "CordbCommonBase::IsValidObject() failed: %x\n", this);
+ _ASSERTE(!"CordbCommonBase::IsValidObject() failed");
+ FreeBuildDebugBreak();
+ }
+ }
+
+ bool IsValidObject()
+ {
+ return (m_signature == CORDB_COMMON_BASE_SIGNATURE);
+ }
+
+ CordbCommonBase(UINT_PTR id, enumCordbDerived type)
+ {
+ init(id, type);
+ }
+
+ CordbCommonBase(UINT_PTR id)
+ {
+ init(id, enumCordbUnknown);
+ }
+
+ void init(UINT_PTR id, enumCordbDerived type)
+ {
+ // To help us track object leaks, we want to log when we create & destory CordbBase objects.
+#ifdef _DEBUG
+ InterlockedIncrement(&s_TotalObjectCount);
+ InterlockedIncrement(&s_CordbObjectUID);
+
+ LOG((LF_CORDB, LL_EVERYTHING, "Memory: CordbBase object allocated: this=%p, count=%d, id=%p, Type=%d\n", this, s_CordbObjectUID, id, type));
+#endif
+
+ m_signature = CORDB_COMMON_BASE_SIGNATURE;
+ m_fNeuterAtWill = 0;
+ m_fIsNeutered = 0;
+
+ m_id = id;
+ m_RefCount = 0;
+
+#ifdef _DEBUG
+ m_type = type;
+ //m_dwInstance = CordbBase::m_saDwInstance[m_type];
+ //InterlockedIncrement(&CordbBase::m_saDwInstance[m_type]);
+ //InterlockedIncrement(&CordbBase::m_saDwAlive[m_type]);
+ //if (m_dwInstance < enumMaxThis)
+ //{
+ // m_sdThis[m_type][m_dwInstance] = this;
+ //}
+#endif
+ }
+
+ virtual ~CordbCommonBase()
+ {
+ // If we're deleting, we really should have released any outstanding reference.
+ // If we call Release() on a deleted object, we'll av (especially b/c Release
+ // may call delete again).
+ CONSISTENCY_CHECK_MSGF(m_RefCount == 0, ("Deleting w/ non-zero ref count. 0x%08x", m_RefCount));
+
+#ifdef _DEBUG
+ //InterlockedDecrement(&CordbBase::m_saDwAlive[m_type]);
+ //if (m_dwInstance < enumMaxThis)
+ //{
+ // m_sdThis[m_type][m_dwInstance] = NULL;
+ //}
+#endif
+ // To help us track object leaks, we want to log when we create & destory CordbBase objects.
+ LOG((LF_CORDB, LL_EVERYTHING, "Memory: CordbBase object deleted: this=%p, id=%p, Refcount=0x%x\n", this, m_id, m_RefCount));
+
+#ifdef _DEBUG
+ LONG newTotalObjectsCount = InterlockedDecrement(&s_TotalObjectCount);
+ _ASSERTE(newTotalObjectsCount >= 0);
+#endif
+
+ // Don't shutdown logic until everybody is done with it.
+ // If we leak objects, this may mean that we never shutdown logging at all!
+#if defined(_DEBUG) && defined(LOGGING)
+ if (newTotalObjectsCount == 0)
+ {
+ ShutdownLogging();
+ }
+#endif
+ }
+
+ /*
+ Member function behavior of a neutered COM object:
+
+ 1. AddRef(), Release(), QueryInterface() work as normal.
+ a. This gives folks who are responsible for pairing a Release() with
+ an AddRef() a chance to dereference their pointer and call Release()
+ when they are informed, explicitly or implicitly, that the object is neutered.
+
+ 2. Any other member function will return an error code unless documented.
+ a. If a member function returns information when the COM object is
+ neutered then the semantics of that function need to be documented.
+ (ie. If an AppDomain is unloaded and you have a reference to the COM
+ object representing the AppDomain, how _should_ it behave? That behavior
+ should be documented)
+
+
+ Postcondions of Neuter():
+
+ 1. All circular references (aka back-pointers) are "broken". They are broken
+ by calling Release() on all "Weak References" to the object. If you're a purist,
+ these pointers should also be NULLed out.
+ a. Weak References/Strong References:
+ i. If any objects are not "reachable" from the root (ie. stack or from global pointers)
+ they should be reclaimed. If they are not, they are leaked and there is an issue.
+ ii. There must be a partial order on the objects such that if A < B then:
+ 1. A has a reference to B. This reference is a "strong reference"
+ 2. A, and thus B, is reachable from the root
+ iii. If a reference belongs in the partial order then it is a "strong reference" else
+ it is a weak reference.
+ *** 2. Sufficient conditions to ensure no COM objects are leaked: ***
+ a. When Neuter() is invoked:
+ i. Calles Release on all its weak references.
+ ii. Then, for each strong reference:
+ 1. invoke Neuter()
+ 2. invoke Release()
+ iii. If it's derived from a CordbXXX class, call Neuter() on the base class.
+ 1. Sense Neuter() is virtual, use the scope specifier Cordb[BaseClass]::Neuter().
+ 3. All members return error codes, except:
+ a. Members of IUknown, AddRef(), Release(), QueryInterfac()
+ b. Those documented to have functionality when the object is neutered.
+ i. Neuter() still works w/o error. If it is invoke a second time it will have already
+ released all its strong and weak references so it could just return.
+
+
+ Alternate design ideas:
+
+ DESIGN: Note that it's possible for object B to have two parents in the partial order
+ and it must be documented which one is responsible for calling Neuter() on B.
+ 1. For example, CordbCode could reasonably be a sibling of CordbFunction and CordbNativeFrame.
+ Which one should call Release()? For now we have CordbFunction call Release() on CordbCode.
+
+ DESIGN: It is not a necessary condition in that Neuter() invoke Release() on all
+ it's strong references. Instead, it would be sufficient to ensure all object are released, that
+ each object call Release() on all its strong pointers in its destructor.
+ 1. This might be done if its necessary for some member to return "tombstone"
+ information after the object has been netuered() which involves the siblings (wrt poset)
+ of the object. However, no sibling could access a parent (wrt poset) because
+ Neuter called Release() on all its weak pointers.
+
+ DESIGN: Rename Neuter() to some name that more accurately reflect the semantics.
+ 1. The three operations are:
+ a. ReleaseWeakPointers()
+ b. NeuterStrongPointers()
+ c. ReleaseStrongPointers()
+ 1. Assert that it's done after NeuterStrongPointers()
+ 2. That would introduce a bunch of functions... but it would be clear.
+
+ DESIGN: CordbBase could provide a function to register strong and weak references. That way CordbBase
+ could implement a general version of ReleaseWeak/ReleaseStrong/NeuterStrongPointers(). This
+ would provide a very error resistant framework for extending the object model plus it would
+ be very explicit about what is going on.
+ One thing that might trip this is idea up is that if an object has two parents,
+ like the CordbCode might, then either both objects call Neuter or one is reference
+ is made weak.
+
+
+ Our implementation:
+
+ The graph formed by the strong references must remain acyclic.
+ It's up to the developer (YOU!) to ensure that each Neuter
+ function maintains that invariant.
+
+ Here is the current Partial Order on CordbXXX objects. (All these classes
+ eventually chain to CordbBase.Neuter() for completeness.)
+
+ Cordb
+ CordbProcess
+ CordbAppDomain
+ CordbBreakPoints
+ CordbAssembly
+ CordbModule
+ CordbClass
+ CordbFunction
+ CordbCode (Can we assert a thread will not reference
+ the same CordbCode as a CordbFunction?)
+ CordbThread
+ CordbChains
+ CordbNativeFrame -> CordbFrame (Chain to baseClass)
+ CordbJITILFrame
+
+
+ <TODO>TODO: Some Neuter functions have not yet been implemented due to time restrictions.</TODO>
+
+ <TODO>TODO: Some weak references never have AddRef() called on them. If that's cool then
+ it should be stated in the documentation. Else it should be changed.</TODO>
+*/
+
+ virtual void Neuter();
+
+ // Unsafe neuter for an object that's already dead.
+ void UnsafeNeuterDeadObject();
+
+
+#ifdef _DEBUG
+ // For debugging (asserts, logging, etc) provide a pretty name (this is 1:1 w/ the VTable)
+ // We provide a default impl in the base object in case this gets called from a dtor (virtuals
+ // called from dtors use the base version, not the derived). A pure call would AV in that case.
+ virtual const char * DbgGetName() { return "CordbBase"; };
+#endif
+
+ bool IsNeutered() const {LIMITED_METHOD_CONTRACT; return m_fIsNeutered == 1; }
+ bool IsNeuterAtWill() const { LIMITED_METHOD_CONTRACT; return m_fNeuterAtWill == 1; }
+ void MarkNeuterAtWill() { LIMITED_METHOD_CONTRACT; m_fNeuterAtWill = 1; }
+
+ //-----------------------------------------------------------
+ // IUnknown support
+ //----------------------------------------------------------
+
+private:
+ // We maintain both an internal + external refcount. This allows us to catch
+ // if an external caller has too many releases.
+ // low bits are internal count, high bits are external count
+ // so Total count = (m_RefCount & CordbBase_InternalRefCountMask) + (m_RefCount >> CordbBase_ExternalRefCountShift);
+ typedef LONGLONG MixedRefCountSigned;
+ typedef ULONGLONG MixedRefCountUnsigned;
+ typedef LONG ExternalRefCount;
+ MixedRefCountUnsigned m_RefCount;
+public:
+
+ // Adjust the internal ref count.
+ // These aren't available to the external world, so only internal code can manipulate the internal count.
+ void InternalAddRef();
+ void InternalRelease();
+
+ // Derived versions of AddRef / Release will call these.
+ // External AddRef & Release
+ // These do not have any additional Asserts to enforce that we're not manipulating the external count
+ // from internal.
+ ULONG STDMETHODCALLTYPE BaseAddRef();
+ ULONG STDMETHODCALLTYPE BaseRelease();
+
+ // External ref count versions, with extra debug count to enforce that this is done externally.
+ // When derive classes use these versions, it Asserts that we're not adjusting external counts from inside.
+ // Thus we can be confident that we're *never* leaking external refs to these objects.
+ // @todo - eventually everything should use these.
+ ULONG STDMETHODCALLTYPE BaseAddRefEnforceExternal();
+ ULONG STDMETHODCALLTYPE BaseReleaseEnforceExternal();
+
+ // Do an AddRef against the External count. This is a semantics issue.
+ // We use this when an internal component Addrefs out-parameters (which Cordbg will call Release on).
+ // This just does a regular external AddRef().
+ void ExternalAddRef();
+
+protected:
+
+ static void InitializeCommon();
+
+private:
+ static void AddDebugPrivilege();
+};
+
+#define CordbBase_ExternalRefCountShift 32
+#define CordbBase_InternalRefCountMask 0xFFFFFFFF
+#define CordbBase_InternalRefCountMax 0x7FFFFFFF
+
+#ifdef _DEBUG
+// Does the given Cordb object type have affinity to a CordbProcess object?
+// This is only used for certain asserts.
+inline bool DoesCordbObjectTypeHaveProcessPtr(enumCordbDerived type)
+{
+ return
+ (type != enumCordbCodeEnum) &&
+ (type != enumCordb) &&
+ (type != enumCordbHashTableEnum);
+}
+#endif
+
+// Base class specifically for CorDebug objects
+class CordbBase : public CordbCommonBase
+{
+public:
+ CordbBase(CordbProcess * pProcess, UINT_PTR id, enumCordbDerived type) : CordbCommonBase(id, type)
+ {
+ // CordbProcess can't pass 'this' to base class, per error C4355. So we pass null and set later.
+ _ASSERTE((pProcess != NULL) ||
+ ((type) == enumCordbProcess) ||
+ !DoesCordbObjectTypeHaveProcessPtr(type));
+
+ m_pProcess.Assign(pProcess);
+ }
+
+ CordbBase(CordbProcess * pProcess, UINT_PTR id) : CordbCommonBase(id)
+ {
+ _ASSERTE(pProcess != NULL);
+ m_pProcess.Assign(pProcess);
+ }
+
+ virtual ~CordbBase()
+ {
+ // Derived classes should not have cleared out our pointer.
+ // CordbProcess's Neuter explicitly nulls out its pointer to avoid circular reference.
+ _ASSERTE(m_pProcess!= NULL ||
+ (CordbCommonBase::m_type == enumCordbProcess) ||
+ !DoesCordbObjectTypeHaveProcessPtr(CordbCommonBase::m_type));
+
+ // Ideally, all CorDebug objects to be neutered by the time their dtor is called.
+ // @todo - we're still working out neutering semantics for a few remaining objects, so we exclude
+ // those from the assert.
+ _ASSERTE(IsNeutered() ||
+ (m_type == enumCordbBreakpoint) ||
+ (m_type == enumCordbStepper));
+ }
+
+ // Neuter just the right-side state.
+ virtual void Neuter();
+
+ // Neuter both left-side state and right-side state.
+ virtual void NeuterLeftSideResources();
+
+ // Get the CordbProcess object that this CordbBase object is associated with (or NULL if there's none).
+ CordbProcess * GetProcess() const
+ {
+ return m_pProcess;
+ }
+protected:
+ // All objects need a strong pointer back to the process so that they can get access to key locks
+ // held by the process (StopGo lock) so that they can synchronize their operations against neutering.
+ // This pointer is cleared in our dtor, and not when we're neutered. Since we can't control when the
+ // dtor is called (it's controlled by external references), we classify this as an external reference too.
+ //
+ // This is the only "strong" reference backpointer that objects need have. All other backpointers can be weak references
+ // because when a parent object is neutered, it will null out all weak reference pointers in all of its children.
+ // That will also break any potential cycles.
+ RSUnsafeExternalSmartPtr<CordbProcess> m_pProcess;
+
+};
+
+
+
+
+
+//-----------------------------------------------------------------------------
+// Macro to check if a CordbXXX object is neutered, and return a standard
+// error code if it is.
+// We pass the 'this' pointer of the object in because it gives us some extra
+// flexibility and lets us log debug info.
+// It is an API breach to access a neutered object.
+//-----------------------------------------------------------------------------
+#define FAIL_IF_NEUTERED(pThis) \
+int _____Neuter_Status_Already_Marked; \
+_____Neuter_Status_Already_Marked = 0; \
+{\
+ if (pThis->IsNeutered()) { \
+ LOG((LF_CORDB, LL_ALWAYS, "Accessing a neutered object at %p\n", pThis)); \
+ return ErrWrapper(CORDBG_E_OBJECT_NEUTERED); \
+ } \
+}
+
+//-----------------------------------------------------------------------------
+// Macro to check if a CordbXXX object is neutered, and return a standard
+// error code if it is.
+// We pass the 'this' pointer of the object in because it gives us some extra
+// flexibility and lets us log debug info.
+// It is an API breach to access a neutered object.
+//-----------------------------------------------------------------------------
+#define THROW_IF_NEUTERED(pThis) \
+int _____Neuter_Status_Already_Marked; \
+_____Neuter_Status_Already_Marked = 0; \
+{\
+ if (pThis->IsNeutered()) { \
+ LOG((LF_CORDB, LL_ALWAYS, "Accessing a neutered object at %p\n", pThis)); \
+ ThrowHR(CORDBG_E_OBJECT_NEUTERED); \
+ } \
+}
+
+// We have an OK_IF_NEUTERED macro to say that this method can be safely
+// called if we're neutered. Mostly for semantic benefits.
+// Also, if a method is marked OK, then somebody won't go and add a 'fail'
+// This is an extremely dangerous quality because:
+// 1) it means that we have no synchronization (can't take the Stop-Go lock)
+// 2) none of our backpointers are usable (they may be nulled out at anytime by another thread).
+// - this also means we absolutely can't send IPC events (since that requires a CordbProcess)
+// 3) The only safe data are blittalbe embedded fields (eg, a pid or stack range)
+//
+// Any usage of this macro should clearly specify why this is safe.
+#define OK_IF_NEUTERED(pThis) \
+int _____Neuter_Status_Already_Marked; \
+_____Neuter_Status_Already_Marked = 0;
+
+
+//-------------------------------------------------------------------------------
+// Simple COM enumerator pattern on a fixed list of items
+//--------------------------------------------------------------------------------
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+class CordbEnumerator : public CordbBase, EnumInterfaceType
+{
+private:
+ // the list of items being enumerated over
+ ElemType *m_items;
+ // the number of items in the list
+ DWORD m_countItems;
+ // the index of the next item to be returned in the enumeration
+ DWORD m_nextIndex;
+
+public:
+ // makes a copy of the elements in the "items" array
+ CordbEnumerator(CordbProcess* pProcess, ElemType *items, DWORD elemCount);
+ // assumes ownership of the elements in the "*items" array.
+ // this avoids an extra allocation + copy
+ CordbEnumerator(CordbProcess* pProcess, ElemType **items, DWORD elemCount);
+ ~CordbEnumerator();
+
+// IUnknown interface
+ virtual COM_METHOD QueryInterface(REFIID riid, VOID** ppInterface);
+ virtual ULONG __stdcall AddRef();
+ virtual ULONG __stdcall Release();
+
+// ICorDebugEnum interface
+ virtual COM_METHOD Clone(ICorDebugEnum **ppEnum);
+ virtual COM_METHOD GetCount(ULONG *pcelt);
+ virtual COM_METHOD Reset();
+ virtual COM_METHOD Skip(ULONG celt);
+
+// ICorDebugXXXEnum interface
+ virtual COM_METHOD Next(ULONG celt, ElemPublicType items[], ULONG *pceltFetched);
+
+// CordbBase overrides
+ virtual VOID Neuter();
+};
+
+// Converts T to U* by using QueryInterface
+template<typename T, typename U>
+U* QueryInterfaceConvert(T obj);
+
+// No conversion, just returns the argument
+template<typename T>
+T IdentityConvert(T obj);
+
+// CorDebugGuidToTypeMapping-adapter used by CordbGuidToTypeEnumerator
+// in the CordbEnumerator pattern
+struct RsGuidToTypeMapping
+{
+ GUID iid;
+ RSSmartPtr<CordbType> spType;
+};
+
+inline
+CorDebugGuidToTypeMapping GuidToTypeMappingConvert(RsGuidToTypeMapping m)
+{
+ CorDebugGuidToTypeMapping result;
+ result.iid = m.iid;
+ result.pType = (ICorDebugType*)(m.spType.GetValue());
+ result.pType->AddRef();
+ return result;
+}
+
+//
+// Some useful enumerators
+//
+typedef CordbEnumerator<RSSmartPtr<CordbThread>,
+ ICorDebugThread*,
+ ICorDebugThreadEnum,
+ QueryInterfaceConvert<RSSmartPtr<CordbThread>, ICorDebugThread> > CordbThreadEnumerator;
+
+typedef CordbEnumerator<CorDebugBlockingObject,
+ CorDebugBlockingObject,
+ ICorDebugBlockingObjectEnum,
+ IdentityConvert<CorDebugBlockingObject> > CordbBlockingObjectEnumerator;
+
+// Template classes must be fully defined rather than just declared in the header
+#include "rsenumerator.hpp"
+
+
+typedef CordbEnumerator<COR_SEGMENT,
+ COR_SEGMENT,
+ ICorDebugHeapSegmentEnum,
+ IdentityConvert<COR_SEGMENT> > CordbHeapSegmentEnumerator;
+
+typedef CordbEnumerator<CorDebugExceptionObjectStackFrame,
+ CorDebugExceptionObjectStackFrame,
+ ICorDebugExceptionObjectCallStackEnum,
+ IdentityConvert<CorDebugExceptionObjectStackFrame> > CordbExceptionObjectCallStackEnumerator;
+
+typedef CordbEnumerator<RsGuidToTypeMapping,
+ CorDebugGuidToTypeMapping,
+ ICorDebugGuidToTypeEnum,
+ GuidToTypeMappingConvert > CordbGuidToTypeEnumerator;
+
+// ----------------------------------------------------------------------------
+// Hash table for CordbBase objects.
+// - Uses Internal AddRef/Release (not external)
+// - Templatize for type-safety w/ Cordb objects
+// - Many hashtables are implicitly protected by a lock. For debug-only, we
+// explicitly associate w/ an optional RSLock and assert that lock is held on access.
+// ----------------------------------------------------------------------------
+
+struct CordbHashEntry
+{
+ FREEHASHENTRY entry;
+ CordbBase *pBase;
+};
+
+class CordbHashTable : private CHashTableAndData<CNewDataNoThrow>
+{
+private:
+ bool m_initialized;
+ SIZE_T m_count;
+
+ BOOL Cmp(SIZE_T k1, const HASHENTRY * pc2)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ return ((ULONG_PTR)k1) != (reinterpret_cast<const CordbHashEntry *>(pc2))->pBase->m_id;
+ }
+
+ ULONG HASH(ULONG_PTR id)
+ {
+ return (ULONG)(id);
+ }
+
+ SIZE_T KEY(UINT_PTR id)
+ {
+ return (SIZE_T)id;
+ }
+
+public:
+ bool IsInitialized();
+
+#ifndef DACCESS_COMPILE
+ CordbHashTable(ULONG size)
+ : CHashTableAndData<CNewDataNoThrow>(size), m_initialized(false), m_count(0)
+ {
+#ifdef _DEBUG
+ m_pDbgLock = NULL;
+ m_dbgChangeCount = 0;
+#endif
+ }
+ virtual ~CordbHashTable();
+
+#ifdef _DEBUG
+ // CordbHashTables may be protected by a lock. For debug-builds, we can associate
+ // the hash w/ that lock and then assert if it's not held.
+ void DebugSetRSLock(RSLock * pLock)
+ {
+ m_pDbgLock = pLock;
+ }
+ int GetChangeCount() { return m_dbgChangeCount; }
+private:
+ void AssertIsProtected();
+
+ // Increment the Change count. This can be used to check if the hashtable changes while being enumerated.
+ void DbgIncChangeCount() { m_dbgChangeCount++; }
+
+ int m_dbgChangeCount;
+ RSLock * m_pDbgLock;
+#else
+ // RSLock association is a no-op on free builds.
+ void AssertIsProtected() { };
+ void DbgIncChangeCount() { };
+#endif // _DEBUG
+
+public:
+
+
+#endif
+
+ ULONG32 GetCount()
+ {
+ return ((ULONG32)m_count);
+ }
+
+ // These operators are unsafe b/c they have no typesafety.
+ // Use a derived CordbSafeHashTable<T> instead.
+ HRESULT UnsafeAddBase(CordbBase *pBase);
+ HRESULT UnsafeSwapBase(CordbBase* pBaseOld, CordbBase* pBaseNew);
+ CordbBase *UnsafeGetBase(ULONG_PTR id, BOOL fFab = TRUE);
+ CordbBase *UnsafeRemoveBase(ULONG_PTR id);
+
+ CordbBase *UnsafeFindFirst(HASHFIND *find);
+ CordbBase *UnsafeFindNext(HASHFIND *find);
+
+ // Unlocked versions don't assert that the lock us held.
+ CordbBase *UnsafeUnlockedFindFirst(HASHFIND *find);
+ CordbBase *UnsafeUnlockedFindNext(HASHFIND *find);
+
+};
+
+
+// Typesafe wrapper around a normal hash table
+// T is expected to be a derived clas of CordbBase
+// Note that this still isn't fully typesafe. Ideally we'd take a strongly-typed key
+// instead of UINT_PTR (the type could have a fixed relationship to T, or could be
+// an additional template argument like standard template hash tables like std::hash_map<K,V>)
+template <class T>
+class CordbSafeHashTable : public CordbHashTable
+{
+public:
+#ifndef DACCESS_COMPILE
+ CordbSafeHashTable<T>(ULONG size) : CordbHashTable(size)
+ {
+ }
+#endif
+ // Typesafe wrappers
+ HRESULT AddBase(T * pBase) { return UnsafeAddBase(pBase); }
+
+ // Either add (eg, future cals to GetBase will succeed) or throw.
+ void AddBaseOrThrow(T * pBase)
+ {
+ HRESULT hr = AddBase(pBase);
+ IfFailThrow(hr);
+ }
+ HRESULT SwapBase(T* pBaseOld, T* pBaseNew) { return UnsafeSwapBase(pBaseOld, pBaseNew); }
+ // Move the function definition of GetBase to rspriv.inl to work around gcc 2.9.5 warnings
+ T* GetBase(ULONG_PTR id, BOOL fFab = TRUE);
+ T* GetBaseOrThrow(ULONG_PTR id, BOOL fFab = TRUE);
+
+ T* RemoveBase(ULONG_PTR id) { return static_cast<T*>(UnsafeRemoveBase(id)); }
+
+ T* FindFirst(HASHFIND *find) { return static_cast<T*>(UnsafeFindFirst(find)); }
+ T* FindNext(HASHFIND *find) { return static_cast<T*>(UnsafeFindNext(find)); }
+
+ // Neuter all items and clear
+ void NeuterAndClear(RSLock * pLock);
+
+ void CopyToArray(RSPtrArray<T> * pArray);
+ void TransferToArray(RSPtrArray<T> * pArray);
+};
+
+
+class CordbHashTableEnum : public CordbBase,
+public ICorDebugProcessEnum,
+public ICorDebugBreakpointEnum,
+public ICorDebugStepperEnum,
+public ICorDebugThreadEnum,
+public ICorDebugModuleEnum,
+public ICorDebugAppDomainEnum,
+public ICorDebugAssemblyEnum
+{
+ // Private ctors. Use build function to access.
+ CordbHashTableEnum(
+ CordbBase * pOwnerObj,
+ NeuterList * pOwnerList,
+ CordbHashTable *table,
+ const _GUID &id);
+
+public:
+ static void BuildOrThrow(
+ CordbBase * pOwnerObj,
+ NeuterList * pOwnerList,
+ CordbHashTable *table,
+ const _GUID &id,
+ RSInitHolder<CordbHashTableEnum> * pHolder);
+
+ CordbHashTableEnum(CordbHashTableEnum *cloneSrc);
+
+ ~CordbHashTableEnum();
+ virtual void Neuter();
+
+
+#ifdef _DEBUG
+ // For debugging (asserts, logging, etc) provide a pretty name (this is 1:1 w/ the VTable)
+ virtual const char * DbgGetName() { return "CordbHashTableEnum"; };
+#endif
+
+
+ HRESULT Next(ULONG celt, CordbBase *bases[], ULONG *pceltFetched);
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Skip(ULONG celt);
+ COM_METHOD Reset();
+ COM_METHOD Clone(ICorDebugEnum **ppEnum);
+ COM_METHOD GetCount(ULONG *pcelt);
+
+ //-----------------------------------------------------------
+ // ICorDebugProcessEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Next(ULONG celt, ICorDebugProcess *processes[],
+ ULONG *pceltFetched)
+ {
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(processes, ICorDebugProcess *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT(pceltFetched, ULONG *);
+
+ return (Next(celt, (CordbBase **)processes, pceltFetched));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugBreakpointEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Next(ULONG celt, ICorDebugBreakpoint *breakpoints[],
+ ULONG *pceltFetched)
+ {
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(breakpoints, ICorDebugBreakpoint *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT(pceltFetched, ULONG *);
+
+ return (Next(celt, (CordbBase **)breakpoints, pceltFetched));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugStepperEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Next(ULONG celt, ICorDebugStepper *steppers[],
+ ULONG *pceltFetched)
+ {
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(steppers, ICorDebugStepper *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT(pceltFetched, ULONG *);
+
+ return (Next(celt, (CordbBase **)steppers, pceltFetched));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugThreadEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Next(ULONG celt, ICorDebugThread *threads[],
+ ULONG *pceltFetched)
+ {
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(threads, ICorDebugThread *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT(pceltFetched, ULONG *);
+
+ return (Next(celt, (CordbBase **)threads, pceltFetched));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugModuleEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Next(ULONG celt, ICorDebugModule *modules[],
+ ULONG *pceltFetched)
+ {
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(modules, ICorDebugModule *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT(pceltFetched, ULONG *);
+
+ return (Next(celt, (CordbBase **)modules, pceltFetched));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugAppDomainEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Next(ULONG celt, ICorDebugAppDomain *appdomains[],
+ ULONG *pceltFetched)
+ {
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(appdomains, ICorDebugAppDomain *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT(pceltFetched, ULONG *);
+
+ return (Next(celt, (CordbBase **)appdomains, pceltFetched));
+ }
+ //-----------------------------------------------------------
+ // ICorDebugAssemblyEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Next(ULONG celt, ICorDebugAssembly *assemblies[],
+ ULONG *pceltFetched)
+ {
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(assemblies, ICorDebugAssembly *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT(pceltFetched, ULONG *);
+
+ return (Next(celt, (CordbBase **)assemblies, pceltFetched));
+ }
+private:
+ // Owning object is our link to the CordbProcess* tree. Never null until we're neutered.
+ // NeuterList is related to the owning object. Need to cache it so that we can pass it on
+ // to our clones.
+ CordbBase * m_pOwnerObj; // provides us w/ a CordbProcess*
+ NeuterList * m_pOwnerNeuterList;
+
+
+ CordbHashTable *m_table;
+ bool m_started;
+ bool m_done;
+ HASHFIND m_hashfind;
+ REFIID m_guid;
+ ULONG m_iCurElt;
+ ULONG m_count;
+ BOOL m_fCountInit;
+
+#ifdef _DEBUG
+ // timestampt of hashtable when we start enumerating it. Useful for detecting if the table
+ // changes underneath us.
+ int m_DbgChangeCount;
+ void AssertValid();
+#else
+ void AssertValid() { }
+#endif
+
+private:
+ //These factor code between Next & Skip
+ HRESULT PrepForEnum(CordbBase **pBase);
+
+ // Note that the set of types advanced by Pre & by Post are disjoint, and
+ // that the union of these two sets are all possible types enuerated by
+ // the CordbHashTableEnum.
+ HRESULT AdvancePreAssign(CordbBase **pBase);
+ HRESULT AdvancePostAssign(CordbBase **pBase,
+ CordbBase **b,
+ CordbBase **bEnd);
+
+ // This factors some code that initializes the module enumerator.
+ HRESULT SetupModuleEnum();
+
+};
+
+
+//-----------------------------------------------------------------------------
+// Neuter List
+// Dtors can be called at any time (whenever Cordbg calls Release, which is outside
+// of our control), so we never want to do significant work in a dtor
+// (this includes sending IPC events + neutering).
+// So objects can queue themselves up to be neutered at a safe time.
+//
+// Items in a NeuterList should only contain state in the Right-Side.
+// If the item holds resources in the left-side, it should be placed on a
+// code:LeftSideResourceCleanupList
+//-----------------------------------------------------------------------------
+class NeuterList
+{
+public:
+ NeuterList();
+ ~NeuterList();
+
+ // Add an object to be neutered.
+ // Anybody calls this to add themselves to the list.
+ // This will add it to the list and maintain an internal reference to it.
+ void Add(CordbProcess * pProcess, CordbBase * pObject);
+
+ // Add w/o checking for safety. Should only be used by Process-list enum.
+ void UnsafeAdd(CordbProcess * pProcess, CordbBase * pObject);
+
+ // Neuter everything on the list.
+ // This should only be called by the "owner", but we can't really enforce that.
+ // This will release all internal references and empty the list.
+ void NeuterAndClear(CordbProcess * pProcess);
+
+ // Sweep for all objects that are marked as 'm_fNeuterAtWill'.
+ // Neuter and remove these.
+ void SweepAllNeuterAtWillObjects(CordbProcess * pProcess);
+
+protected:
+ struct Node
+ {
+ RSSmartPtr<CordbBase> m_pObject;
+ Node * m_pNext;
+ };
+
+ // Manipulating the list is done under the Process lock.
+ Node * m_pHead;
+};
+
+//-----------------------------------------------------------------------------
+// This list is for objects that hold left-side resources.
+// If the object does not hold left-side resources, it can be placed on a
+// code:NeuterList
+//-----------------------------------------------------------------------------
+class LeftSideResourceCleanupList : public NeuterList
+{
+public:
+ // dispose everything contained in the list by calling SafeDispose() on each element
+ void SweepNeuterLeftSideResources(CordbProcess * pProcess);
+ void NeuterLeftSideResourcesAndClear(CordbProcess * pProcess);
+};
+
+//-------------------------------------------------------------------------
+//
+// Optional<T>
+// Stores a value along with a bit indicating whether the value is valid.
+//
+// This is particularly useful for LS data read via DAC. We need to gracefully
+// handle missing data, and we may want to track independent pieces of data
+// separately (often with lazy initialization). It's essential that we can't
+// easily lose track of whether the data has been cached yet or not. So
+// rather than have extra "isValid" bools everywhere, we use this class to
+// encapsulate the validity bit in with the data, and ASSERT that it is true
+// whenever reading out the data.
+// Note that the client must still remember to call GetValue only when HasValue
+// is true. Since C++ doesn't have type-safe sum types, we can't enforce this
+// explicitly at compile time (ML-style datatypes and pattern matching is perfect
+// for this).
+//
+// Note that we could consider adding some operator overloads to make using
+// instances of this class more transparent. Experience will tell if this
+// is a good idea or not.
+//
+template <typename T>
+class Optional
+{
+public:
+ // By default, initialize to invalid
+ Optional() : m_fHasValue(false), m_value(T()) {}
+
+ // Allow implicit initialization from a value (for copyable T)
+ Optional(const T& val) : m_fHasValue(true), m_value(val) {}
+
+ // Returns true if a value has been stored
+ bool HasValue() const { return m_fHasValue; }
+
+ // Extract the value. Can only be called when HasValue is true.
+ const T& GetValue() { _ASSERTE(m_fHasValue); return m_value; }
+
+ // Get a writable pointer to the value structure, for filling in uncopyable data structures
+ T * GetValueAddr() { return &m_value; }
+
+ // Explicitly mark this object as having a value (for use after writing to it directly using
+ // GetValueAddr. Not necessary for simple/primitive types).
+ void SetHasValue() { m_fHasValue = true; }
+
+ // Also gets compiler-default copy constructor and assignment operator if T has them
+
+private:
+ bool m_fHasValue;
+ T m_value;
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Cordb class
+ * ------------------------------------------------------------------------- */
+
+class Cordb : public CordbBase, public ICorDebug, public ICorDebugRemote
+{
+public:
+ Cordb(CorDebugInterfaceVersion iDebuggerVersion);
+ virtual ~Cordb();
+ virtual void Neuter();
+
+
+
+#ifdef _DEBUG_IMPL
+ virtual const char * DbgGetName() { return "Cordb"; }
+
+ // Under Debug, we keep some extra state for tracking leaks. The goal is that
+ // we can assert that we aren't leaking internal refs. We'd like to assert that
+ // we're not leaking external refs, but since we can't force Cordbg to release,
+ // we can't really assert that.
+ // So the idea is that when Cordbg has released its last Cordb object, that
+ // all internal references have been released.
+ // Unfortunately, certain CordbBase objects are unrooted and thus we have no
+ // good time to neuter them and clean up any internal references they may hold.
+ // So we keep count of those guys too.
+ static LONG s_DbgMemTotalOutstandingCordb;
+ static LONG s_DbgMemTotalOutstandingInternalRefs;
+#endif
+
+ //
+ // Turn this on to enable an array which will contain all objects that have
+ // not been completely released.
+ //
+ // #define TRACK_OUTSTANDING_OBJECTS 1
+
+#ifdef TRACK_OUTSTANDING_OBJECTS
+
+#define MAX_TRACKED_OUTSTANDING_OBJECTS 256
+ static void *Cordb::s_DbgMemOutstandingObjects[MAX_TRACKED_OUTSTANDING_OBJECTS];
+ static LONG Cordb::s_DbgMemOutstandingObjectMax;
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebug
+ //-----------------------------------------------------------
+
+#ifdef FEATURE_CORECLR
+ HRESULT SetTargetCLR(HMODULE hmodTargetCLR);
+#endif // FEATURE_CORECLR
+
+ COM_METHOD Initialize();
+ COM_METHOD Terminate();
+ COM_METHOD SetManagedHandler(ICorDebugManagedCallback *pCallback);
+ COM_METHOD SetUnmanagedHandler(ICorDebugUnmanagedCallback *pCallback);
+ COM_METHOD CreateProcess(LPCWSTR lpApplicationName,
+ __in_z LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ PVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation,
+ CorDebugCreateProcessFlags debuggingFlags,
+ ICorDebugProcess **ppProcess);
+ COM_METHOD DebugActiveProcess(DWORD dwProcessId, BOOL fWin32Attach, ICorDebugProcess **ppProcess);
+ COM_METHOD EnumerateProcesses(ICorDebugProcessEnum **ppProcess);
+ COM_METHOD GetProcess(DWORD dwProcessId, ICorDebugProcess **ppProcess);
+ COM_METHOD CanLaunchOrAttach(DWORD dwProcessId, BOOL win32DebuggingEnabled);
+
+ //-----------------------------------------------------------
+ // CorDebug
+ //-----------------------------------------------------------
+
+ static COM_METHOD CreateObjectV1(REFIID id, void **object);
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ static COM_METHOD CreateObjectTelesto(REFIID id, void ** pObject);
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+ static COM_METHOD CreateObject(CorDebugInterfaceVersion iDebuggerVersion, REFIID id, void **object);
+
+ //-----------------------------------------------------------
+ // ICorDebugRemote
+ //-----------------------------------------------------------
+
+ COM_METHOD CreateProcessEx(ICorDebugRemoteTarget * pRemoteTarget,
+ LPCWSTR lpApplicationName,
+ __in_z LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ PVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation,
+ CorDebugCreateProcessFlags debuggingFlags,
+ ICorDebugProcess ** ppProcess);
+
+ COM_METHOD DebugActiveProcessEx(ICorDebugRemoteTarget * pRemoteTarget,
+ DWORD dwProcessId,
+ BOOL fWin32Attach,
+ ICorDebugProcess ** ppProcess);
+
+
+ //-----------------------------------------------------------
+ // Methods not exposed via a COM interface.
+ //-----------------------------------------------------------
+
+ HRESULT CreateProcessCommon(ICorDebugRemoteTarget * pRemoteTarget,
+ LPCWSTR lpApplicationName,
+ __in_z LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ PVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation,
+ CorDebugCreateProcessFlags debuggingFlags,
+ ICorDebugProcess **ppProcess);
+
+ HRESULT DebugActiveProcessCommon(ICorDebugRemoteTarget * pRemoteTarget, DWORD id, BOOL win32Attach, ICorDebugProcess **ppProcess);
+
+ void EnsureCanLaunchOrAttach(BOOL fWin32DebuggingEnabled);
+
+ void EnsureAllowAnotherProcess();
+ void AddProcess(CordbProcess* process);
+ void RemoveProcess(CordbProcess* process);
+ CordbSafeHashTable<CordbProcess> *GetProcessList();
+
+ void LockProcessList();
+ void UnlockProcessList();
+
+ #ifdef _DEBUG
+ bool ThreadHasProcessListLock();
+ #endif
+
+
+ HRESULT SendIPCEvent(CordbProcess * pProcess,
+ DebuggerIPCEvent * pEvent,
+ SIZE_T eventSize);
+
+ void ProcessStateChanged();
+
+ HRESULT WaitForIPCEventFromProcess(CordbProcess* process,
+ CordbAppDomain *appDomain,
+ DebuggerIPCEvent* event);
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+public:
+ RSExtSmartPtr<ICorDebugManagedCallback> m_managedCallback;
+ RSExtSmartPtr<ICorDebugManagedCallback2> m_managedCallback2;
+ RSExtSmartPtr<ICorDebugManagedCallback3> m_managedCallback3;
+ RSExtSmartPtr<ICorDebugUnmanagedCallback> m_unmanagedCallback;
+
+ CordbRCEventThread* m_rcEventThread;
+
+ CorDebugInterfaceVersion GetDebuggerVersion() const;
+
+#ifdef FEATURE_CORESYSTEM
+ HMODULE GetTargetCLR() { return m_targetCLR; }
+#endif
+
+private:
+ bool IsCreateProcessSupported();
+ bool IsInteropDebuggingSupported();
+ void CheckCompatibility();
+
+ CordbSafeHashTable<CordbProcess> m_processes;
+
+ // List to track outstanding CordbProcessEnum objects.
+ NeuterList m_pProcessEnumList;
+
+ RSLock m_processListMutex;
+ BOOL m_initialized;
+
+ // This is the version of the ICorDebug APIs that the debugger believes it's consuming.
+ CorDebugInterfaceVersion m_debuggerSpecifiedVersion;
+
+//Note - this code could be useful outside coresystem, but keeping the change localized
+// because we are late in the win8 release
+#ifdef FEATURE_CORESYSTEM
+ HMODULE m_targetCLR;
+#endif
+};
+
+
+
+
+/* ------------------------------------------------------------------------- *
+ * AppDomain class
+ * ------------------------------------------------------------------------- */
+
+// Provides the implementation for ICorDebugAppDomain, ICorDebugAppDomain2,
+// and ICorDebugAppDomain3
+class CordbAppDomain : public CordbBase,
+ public ICorDebugAppDomain,
+ public ICorDebugAppDomain2,
+ public ICorDebugAppDomain3,
+ public ICorDebugAppDomain4
+{
+public:
+ // Create a CordbAppDomain object based on a pointer to the AppDomain instance in the CLR
+ CordbAppDomain(CordbProcess * pProcess,
+ VMPTR_AppDomain vmAppDomain);
+
+ virtual ~CordbAppDomain();
+
+ virtual void Neuter();
+
+ using CordbBase::GetProcess;
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbAppDomain"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugController
+ //-----------------------------------------------------------
+
+ COM_METHOD Stop(DWORD dwTimeout);
+ COM_METHOD Continue(BOOL fIsOutOfBand);
+ COM_METHOD IsRunning(BOOL * pbRunning);
+ COM_METHOD HasQueuedCallbacks(ICorDebugThread * pThread,
+ BOOL * pbQueued);
+ COM_METHOD EnumerateThreads(ICorDebugThreadEnum ** ppThreads);
+ COM_METHOD SetAllThreadsDebugState(CorDebugThreadState state, ICorDebugThread * pExceptThisThread);
+
+ // Deprecated, returns E_NOTIMPL
+ COM_METHOD Detach();
+
+ COM_METHOD Terminate(unsigned int exitCode);
+
+ COM_METHOD CanCommitChanges(
+ ULONG cSnapshots,
+ ICorDebugEditAndContinueSnapshot * pSnapshots[],
+ ICorDebugErrorInfoEnum ** pError);
+
+ COM_METHOD CommitChanges(
+ ULONG cSnapshots,
+ ICorDebugEditAndContinueSnapshot * pSnapshots[],
+ ICorDebugErrorInfoEnum ** pError);
+
+ //-----------------------------------------------------------
+ // ICorDebugAppDomain
+ //-----------------------------------------------------------
+ /*
+ * GetProcess returns the process containing the app domain
+ */
+
+ COM_METHOD GetProcess(ICorDebugProcess ** ppProcess);
+
+ /*
+ * EnumerateAssemblies enumerates all assemblies in the app domain
+ */
+
+ COM_METHOD EnumerateAssemblies(ICorDebugAssemblyEnum ** ppAssemblies);
+
+ COM_METHOD GetModuleFromMetaDataInterface(IUnknown * pIMetaData,
+ ICorDebugModule ** ppModule);
+ /*
+ * EnumerateBreakpoints returns an enum of all active breakpoints
+ * in the app domain. This includes all types of breakpoints :
+ * function breakpoints, data breakpoints, etc.
+ */
+
+ COM_METHOD EnumerateBreakpoints(ICorDebugBreakpointEnum ** ppBreakpoints);
+
+ /*
+ * EnumerateSteppers returns an enum of all active steppers in the app domain.
+ */
+
+ COM_METHOD EnumerateSteppers(ICorDebugStepperEnum ** ppSteppers);
+
+ // Deprecated, always returns true.
+ COM_METHOD IsAttached(BOOL * pfAttached);
+
+ // Returns the friendly name of the AppDomain
+ COM_METHOD GetName(ULONG32 cchName,
+ ULONG32 * pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+
+ /*
+ * GetObject returns the runtime app domain object.
+ * Note: This method is not yet implemented.
+ */
+
+ COM_METHOD GetObject(ICorDebugValue ** ppObject);
+
+ // Deprecated, does nothing
+ COM_METHOD Attach();
+ COM_METHOD GetID(ULONG32 * pId);
+
+ //-----------------------------------------------------------
+ // ICorDebugAppDomain2 APIs
+ //-----------------------------------------------------------
+ COM_METHOD GetArrayOrPointerType(CorElementType elementType,
+ ULONG32 nRank,
+ ICorDebugType * pTypeArg,
+ ICorDebugType ** ppResultType);
+
+ COM_METHOD GetFunctionPointerType(ULONG32 cTypeArgs,
+ ICorDebugType * rgpTypeArgs[],
+ ICorDebugType ** ppResultType);
+
+ //-----------------------------------------------------------
+ // ICorDebugAppDomain3 APIs
+ //-----------------------------------------------------------
+ COM_METHOD GetCachedWinRTTypesForIIDs(
+ ULONG32 cGuids,
+ GUID * guids,
+ ICorDebugTypeEnum * * ppTypesEnum);
+
+ COM_METHOD GetCachedWinRTTypes(
+ ICorDebugGuidToTypeEnum * * ppType);
+
+ //-----------------------------------------------------------
+ // ICorDebugAppDomain4
+ //-----------------------------------------------------------
+ COM_METHOD GetObjectForCCW(CORDB_ADDRESS ccwPointer, ICorDebugValue **ppManagedObject);
+
+ // Get the VMPTR for this appdomain.
+ VMPTR_AppDomain GetADToken() { return m_vmAppDomain; }
+
+ // Given a metadata interface, find the module in this appdomain that matches it.
+ CordbModule * GetModuleFromMetaDataInterface(IUnknown *pIMetaData);
+
+ // Lookup a module from the cache. Create and to the cache if needed.
+ CordbModule * LookupOrCreateModule(VMPTR_Module vmModuleToken, VMPTR_DomainFile vmDomainFileToken);
+
+ // Lookup a module from the cache. Create and to the cache if needed.
+ CordbModule * LookupOrCreateModule(VMPTR_DomainFile vmDomainFileToken);
+
+ // Callback from DAC for module enumeration
+ static void ModuleEnumerationCallback(VMPTR_DomainFile vmModule, void * pUserData);
+
+ // Use DAC to add any modules for this assembly.
+ void PrepopulateModules();
+
+ void InvalidateName() { m_strAppDomainName.Clear(); }
+
+public:
+ ULONG m_AppDomainId;
+
+ CordbAssembly * LookupOrCreateAssembly(VMPTR_DomainAssembly vmDomainAssembly);
+ CordbAssembly * LookupOrCreateAssembly(VMPTR_Assembly vmAssembly);
+ void RemoveAssemblyFromCache(VMPTR_DomainAssembly vmDomainAssembly);
+
+
+ CordbSafeHashTable<CordbBreakpoint> m_breakpoints;
+
+ // Unique objects that represent the use of some
+ // basic ELEMENT_TYPE's as type parameters. These
+ // are shared acrosss the entire process. We could
+ // go and try to find the classes corresponding to these
+ // element types but it seems simpler just to keep
+ // them as special cases.
+ CordbSafeHashTable<CordbType> m_sharedtypes;
+
+ CordbAssembly * CacheAssembly(VMPTR_DomainAssembly vmDomainAssembly);
+ CordbAssembly * CacheAssembly(VMPTR_Assembly vmAssembly);
+
+
+ // Cache of modules in this appdomain. In the VM, modules live in an assembly.
+ // This cache lives on the appdomain because we generally want to do appdomain (or process)
+ // wide lookup.
+ // This is indexed by VMPTR_DomainFile, which has appdomain affinity.
+ // This is populated by code:CordbAppDomain::LookupOrCreateModule (which may be invoked
+ // anytime the RS gets hold of a VMPTR), and are removed at the unload event.
+ CordbSafeHashTable<CordbModule> m_modules;
+private:
+ // Cache of assemblies in this appdomain.
+ // This is indexed by VMPTR_DomainAssembly, which has appdomain affinity.
+ // This is populated by code:CordbAppDomain::LookupOrCreateAssembly (which may be invoked
+ // anytime the RS gets hold of a VMPTR), and are removed at the unload event.
+ CordbSafeHashTable<CordbAssembly> m_assemblies;
+
+ static void AssemblyEnumerationCallback(VMPTR_DomainAssembly vmDomainAssembly, void * pThis);
+ void PrepopulateAssembliesOrThrow();
+
+ // Use DAC to refresh our name
+ HRESULT RefreshName();
+
+ StringCopyHolder m_strAppDomainName;
+
+ NeuterList m_TypeNeuterList; // List of types owned by this AppDomain.
+
+ // List of Sweepable objects owned by this AppDomain.
+ // This includes some objects taht hold resources in the left-side (mainly
+ // as CordbHandleValue, see code:CordbHandleValue::Dispose), as well as:
+ // - Cordb*Value objects that survive across continues and have appdomain affinity.
+ LeftSideResourceCleanupList m_SweepableNeuterList;
+
+ VMPTR_AppDomain m_vmAppDomain;
+public:
+ // The "Long" exit list is for items that don't get neutered until the appdomain exits.
+ // The "Sweepable" exit list is for items that may be neuterable sooner than AD exit.
+ // By splitting out the list, we can just try to sweep the "Sweepable" list and we
+ // don't waste any time sweeping things on the "Long" list that aren't neuterable anyways.
+ NeuterList * GetLongExitNeuterList() { return &m_TypeNeuterList; }
+ LeftSideResourceCleanupList * GetSweepableExitNeuterList() { return &m_SweepableNeuterList; }
+
+ void AddToTypeList(CordbBase *pObject);
+
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Assembly class
+ * ------------------------------------------------------------------------- */
+
+class CordbAssembly : public CordbBase, public ICorDebugAssembly, ICorDebugAssembly2
+{
+public:
+ CordbAssembly(CordbAppDomain * pAppDomain,
+ VMPTR_Assembly vmAssembly,
+ VMPTR_DomainAssembly vmDomainAssembly);
+ virtual ~CordbAssembly();
+ virtual void Neuter();
+
+ using CordbBase::GetProcess;
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbAssembly"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugAssembly
+ //-----------------------------------------------------------
+
+ /*
+ * GetProcess returns the process containing the assembly
+ */
+ COM_METHOD GetProcess(ICorDebugProcess ** ppProcess);
+
+ // Gets the AppDomain containing this assembly
+ COM_METHOD GetAppDomain(ICorDebugAppDomain ** ppAppDomain);
+
+ /*
+ * EnumerateModules enumerates all modules in the assembly
+ */
+ COM_METHOD EnumerateModules(ICorDebugModuleEnum ** ppModules);
+
+ /*
+ * GetCodeBase returns the code base used to load the assembly
+ */
+ COM_METHOD GetCodeBase(ULONG32 cchName,
+ ULONG32 * pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+
+ // returns the filename of the assembly, or "<unknown>" for in-memory assemblies
+ COM_METHOD GetName(ULONG32 cchName,
+ ULONG32 * pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+
+
+ //-----------------------------------------------------------
+ // ICorDebugAssembly2
+ //-----------------------------------------------------------
+
+ /*
+ * IsFullyTrusted returns a flag indicating whether the security system
+ * has granted the assembly full trust.
+ */
+ COM_METHOD IsFullyTrusted(BOOL * pbFullyTrusted);
+
+ //-----------------------------------------------------------
+ // internal accessors
+ //-----------------------------------------------------------
+
+#ifdef _DEBUG
+ void DbgAssertAssemblyDeleted();
+
+ static void DbgAssertAssemblyDeletedCallback(VMPTR_DomainAssembly vmDomainAssembly, void * pUserData);
+#endif // _DEBUG
+
+ CordbAppDomain * GetAppDomain() { return m_pAppDomain; }
+
+ VMPTR_DomainAssembly GetDomainAssemblyPtr() { return m_vmDomainAssembly; }
+private:
+ VMPTR_Assembly m_vmAssembly;
+ VMPTR_DomainAssembly m_vmDomainAssembly;
+ CordbAppDomain * m_pAppDomain;
+
+ StringCopyHolder m_strAssemblyFileName;
+ Optional<BOOL> m_foptIsFullTrust;
+};
+
+
+//-----------------------------------------------------------------------------
+// Describe what to do w/ a win32 debug event
+//-----------------------------------------------------------------------------
+class Reaction
+{
+public:
+ enum Type
+ {
+ // Inband events: Dispatch to Cordbg
+ // safe for stopping the shell and communicating with the runtime
+ cInband,
+
+ // workaround. Inband event, but NewEvent =false
+ cInband_NotNewEvent,
+
+ // This is a debug event that corresponds with getting to the beginning
+ // of a first chance hijack.
+ cFirstChanceHijackStarted,
+
+ // This is the debug event that corresponds with getting to the end of
+ // a hijack. To continue we need to restore an unhijacked context
+ cInbandHijackComplete,
+
+ // This is a debug event which corresponds to re-hiting a previous
+ // IB event after returning from the hijack. Now we have already dispatched it
+ // so we know how the user wants it to be continued
+ // Continue immediately with the previously determined
+ cInbandExceptionRetrigger,
+
+ // This debug event is a breakpoint in unmanaged code that we placed. It will need
+ // the M2UHandoffHijack to run the in process breakpoint handling code.
+ cBreakpointRequiringHijack,
+
+ // Oob events: Dispatch to Cordbg
+ // Not safe stopping events. They must be continued immediately.
+ cOOB,
+
+ // CLR internal exception, Continue(not_handled), don't dispatch
+ // The CLR expects this exception and will deal with it properly.
+ cCLR,
+
+ // Don't dispatch. Continue(DBG_CONTINUE).
+ // Common for flare.
+ cIgnore
+ };
+
+ Type GetType() const { return m_type; };
+
+#ifdef _DEBUG
+ const char * GetReactionName()
+ {
+ switch(m_type)
+ {
+ case cInband: return "cInband";
+ case cInband_NotNewEvent: return "cInband_NotNewEvent";
+ case cInbandHijackComplete: return "cInbandHijackComplete";
+ case cInbandExceptionRetrigger: return "cInbandExceptionRetrigger";
+ case cBreakpointRequiringHijack: return "cBreakpointRequiringHijack";
+ case cOOB: return "cOOB";
+ case cCLR: return "cCLR";
+ case cIgnore: return "cIgnore";
+ default: return "<unknown>";
+ }
+ }
+ int GetLine()
+ {
+ return m_line;
+ }
+#endif
+
+ Reaction(Type t, int line) : m_type(t) {
+#ifdef _DEBUG
+ m_line = line;
+
+ LOG((LF_CORDB, LL_EVERYTHING, "Reaction:%s (determined on line: %d)\n", GetReactionName(), line));
+#endif
+ };
+
+ void operator=(const Reaction & other)
+ {
+ m_type = other.m_type;
+#ifdef _DEBUG
+ m_line = other.m_line;
+#endif
+ }
+
+protected:
+ Type m_type;
+
+#ifdef _DEBUG
+ // Under a debug build, track the line # for where this came from.
+ int m_line;
+#endif
+};
+
+// Macro for creating a Reaction.
+#define REACTION(type) Reaction(Reaction::type, __LINE__)
+
+// Different forms of Unmanaged Continue
+enum EUMContinueType
+{
+ cOobUMContinue,
+ cInternalUMContinue,
+ cRealUMContinue
+};
+
+/* ------------------------------------------------------------------------- *
+ * Process class
+ * ------------------------------------------------------------------------- */
+
+
+#ifdef _DEBUG
+// On debug, we can afford a larger native event queue..
+const int DEBUG_EVENTQUEUE_SIZE = 30;
+#else
+const int DEBUG_EVENTQUEUE_SIZE = 10;
+#endif
+
+void DeleteIPCEventHelper(DebuggerIPCEvent *pDel);
+
+
+// Private interface on CordbProcess that ShimProcess needs to emulate V2 functionality.
+// The fact that we need private hooks means that V3 is not sufficiently finished to allow building
+// a V2 debugger. This interface should shrink over time (and eventually go away) as the functionality gets exposed
+// publicly.
+// CordbProcess calls back into ShimProcess too, so the public surface of code:ShimProcess plus
+// the spots in CordbProcess that call them are additional surface area that may need to addressed
+// to make the shim public.
+class IProcessShimHooks
+{
+public:
+ // Get the OS Process ID of the target.
+ virtual DWORD GetPid() = 0;
+
+ // Request a synchronization for attach.
+ // This essentially just sends an AsyncBreak to the left-side. Once the target is
+ // synchronized, the Shim can use inspection to send all the various fake-attach events.
+ //
+ // Once the shim has a way of requesting a synchronization from out-of-process for an
+ // arbitrary running target that's not stopped at a managed debug event, we can
+ // remove this.
+ virtual void QueueManagedAttachIfNeeded() = 0;
+
+ // Hijack a thread at an unhandled exception to allow us to resume executing the target so
+ // that the helper thread can run and service IPC requests. This is also needed to allow
+ // func-eval at a 2nd-chance exception
+ //
+ // This will require an architectural change to remove. Either:
+ // - actions like func-eval / synchronization may call this directly themselves.
+ // - the CLR's managed Unhandled-exception event is moved out of the native
+ // unhandled-exception event, thus making native unhandled exceptions uninteresting to ICorDebug.
+ // - everything is out-of-process, and so the CLR doesn't need to continue after an unhandled
+ // native exception.
+ virtual BOOL HijackThreadForUnhandledExceptionIfNeeded(DWORD dwThreadId) = 0;
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // Private hook to do the bulk of the interop-debugging goo. This includes hijacking inband
+ // events and queueing them so that the helper-thread can run.
+ //
+ // We can remove this once we kill the helper-thread, or after enough functionality is
+ // out-of-process that the debugger doesn't need the helper thread when stopped at an event.
+ virtual void HandleDebugEventForInteropDebugging(const DEBUG_EVENT * pEvent) = 0;
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ // Get the modules in the order that they were loaded. This is needed to send the fake-attach events
+ // for module load in the right order.
+ //
+ // This can be removed once ICorDebug's enumerations are ordered.
+ virtual void GetModulesInLoadOrder(
+ ICorDebugAssembly * pAssembly,
+ RSExtSmartPtr<ICorDebugModule>* pModules,
+ ULONG countModules) = 0;
+
+ // Get the assemblies in the order that they were loaded. This is needed to send the fake-attach events
+ // for assembly load in the right order.
+ //
+ // This can be removed once ICorDebug's enumerations are ordered.
+ virtual void GetAssembliesInLoadOrder(
+ ICorDebugAppDomain * pAppDomain,
+ RSExtSmartPtr<ICorDebugAssembly>* pAssemblies,
+ ULONG countAssemblies) = 0;
+
+ // Queue up fake connection events for attach.
+ // ICorDebug doesn't expose any enumeration for connections, so the shim needs to call into a
+ // private hook to enumerate them for attach.
+ virtual void QueueFakeConnectionEvents() = 0;
+
+ // This finishes initializing the IPC channel between the LS + RS, which includes duplicating
+ // some handles and events.
+ //
+ // This can be removed once the IPC channel is completely gone and all communication goes
+ // soley through the data-target.
+ virtual void FinishInitializeIPCChannel() = 0;
+
+ // Called when stopped at a managed debug event to request a synchronization.
+ // This can be replaced when we expose synchronization from ICorDebug.
+ // The fact that the debuggee is at a managed debug event greatly simplifies the request here
+ // (in contrast to QueueManagedAttachIfNeeded). It means that we can just flip a flag from
+ // out-of-process, and when the debuggee thread resumes, it can check that flag and do the
+ // synchronization from in-process.
+ virtual void RequestSyncAtEvent()= 0;
+
+ virtual bool IsThreadSuspendedOrHijacked(ICorDebugThread * pThread) = 0;
+};
+
+
+// entry for the array of connections in EnumerateConnectionsData
+struct EnumerateConnectionsEntry
+{
+public:
+ StringCopyHolder m_pName; // name of the connection
+ DWORD m_dwID; // ID of the connection
+};
+
+// data structure used in the callback for enumerating connections (code:CordbProcess::QueueFakeConnectionEvents)
+struct EnumerateConnectionsData
+{
+public:
+ ~EnumerateConnectionsData()
+ {
+ if (m_pEntryArray != NULL)
+ {
+ delete [] m_pEntryArray;
+ m_pEntryArray = NULL;
+ }
+ }
+
+ CordbProcess * m_pThis; // the "this" process
+ EnumerateConnectionsEntry * m_pEntryArray; // an array of connections to be filled in
+ UINT32 m_uIndex; // the next entry in the array to be filled
+};
+
+// data structure used in the callback for asserting that an appdomain has been deleted
+// (code:CordbProcess::DbgAssertAppDomainDeleted)
+struct DbgAssertAppDomainDeletedData
+{
+public:
+ CordbProcess * m_pThis;
+ VMPTR_AppDomain m_vmAppDomainDeleted;
+};
+
+class CordbProcess :
+ public CordbBase,
+ public ICorDebugProcess,
+ public ICorDebugProcess2,
+ public ICorDebugProcess3,
+ public ICorDebugProcess4,
+ public ICorDebugProcess5,
+ public ICorDebugProcess7,
+ public ICorDebugProcess8,
+ public IDacDbiInterface::IAllocator,
+ public IDacDbiInterface::IMetaDataLookup,
+ public IProcessShimHooks
+#ifdef FEATURE_LEGACYNETCF_DBG_HOST_CONTROL
+ , public ICorDebugLegacyNetCFHostCallbackInvoker_PrivateWindowsPhoneOnly
+#endif
+{
+ // Ctor is private. Use OpenVirtualProcess instead.
+ CordbProcess(ULONG64 clrInstanceId, IUnknown * pDataTarget, HMODULE hDacModule, Cordb * pCordb, DWORD dwProcessID, ShimProcess * pShim);
+
+public:
+
+ virtual ~CordbProcess();
+ virtual void Neuter();
+
+ // Neuter left-side resources for all children
+ void NeuterChildrenLeftSideResources();
+
+ // Neuter all of all children, but not the actual process object.
+ void NeuterChildren();
+
+
+ // The way to instantiate a new CordbProcess object.
+ // @dbgtodo managed pipeline - this is not fully active in all scenarios yet.
+ static HRESULT OpenVirtualProcess(ULONG64 clrInstanceId,
+ IUnknown * pDataTarget,
+ HMODULE hDacModule,
+ Cordb * pCordb,
+ DWORD dwProcessID,
+ ShimProcess * pShim,
+ CordbProcess ** ppProcess);
+
+ // Helper function to determine whether this ICorDebug is compatibile with a debugger
+ // designed for the specified major version
+ static bool IsCompatibleWith(DWORD clrMajorVersion);
+
+ //-----------------------------------------------------------
+ // IMetaDataLookup
+ // -----------------------------------------------------------
+ IMDInternalImport * LookupMetaData(VMPTR_PEFile vmPEFile, bool &isILMetaDataForNGENImage);
+
+ // Helper functions for LookupMetaData implementation
+ IMDInternalImport * LookupMetaDataFromDebugger(VMPTR_PEFile vmPEFile,
+ bool &isILMetaDataForNGENImage,
+ CordbModule * pModule);
+
+ IMDInternalImport * LookupMetaDataFromDebuggerForSingleFile(CordbModule * pModule,
+ LPCWSTR pwszImagePath,
+ DWORD dwTimeStamp,
+ DWORD dwImageSize);
+
+
+ //-----------------------------------------------------------
+ // IDacDbiInterface::IAllocator
+ //-----------------------------------------------------------
+
+ void * Alloc(SIZE_T lenBytes);
+ void Free(void * p);
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbProcess"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return BaseAddRefEnforceExternal();
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return BaseReleaseEnforceExternal();
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugController
+ //-----------------------------------------------------------
+
+ COM_METHOD Stop(DWORD dwTimeout);
+ COM_METHOD Deprecated_Continue();
+ COM_METHOD IsRunning(BOOL *pbRunning);
+ COM_METHOD HasQueuedCallbacks(ICorDebugThread *pThread, BOOL *pbQueued);
+ COM_METHOD EnumerateThreads(ICorDebugThreadEnum **ppThreads);
+ COM_METHOD SetAllThreadsDebugState(CorDebugThreadState state,
+ ICorDebugThread *pExceptThisThread);
+ COM_METHOD Detach();
+ COM_METHOD Terminate(unsigned int exitCode);
+
+ COM_METHOD CanCommitChanges(
+ ULONG cSnapshots,
+ ICorDebugEditAndContinueSnapshot *pSnapshots[],
+ ICorDebugErrorInfoEnum **pError);
+
+ COM_METHOD CommitChanges(
+ ULONG cSnapshots,
+ ICorDebugEditAndContinueSnapshot *pSnapshots[],
+ ICorDebugErrorInfoEnum **pError);
+
+ COM_METHOD Continue(BOOL fIsOutOfBand);
+ COM_METHOD ThreadForFiberCookie(DWORD fiberCookie,
+ ICorDebugThread **ppThread);
+ COM_METHOD GetHelperThreadID(DWORD *pThreadID);
+
+ //-----------------------------------------------------------
+ // ICorDebugProcess
+ //-----------------------------------------------------------
+
+ COM_METHOD GetID(DWORD *pdwProcessId);
+ COM_METHOD GetHandle(HANDLE *phProcessHandle);
+ COM_METHOD EnableSynchronization(BOOL bEnableSynchronization);
+ COM_METHOD GetThread(DWORD dwThreadId, ICorDebugThread **ppThread);
+ COM_METHOD EnumerateBreakpoints(ICorDebugBreakpointEnum **ppBreakpoints);
+ COM_METHOD EnumerateSteppers(ICorDebugStepperEnum **ppSteppers);
+ COM_METHOD EnumerateObjects(ICorDebugObjectEnum **ppObjects);
+ COM_METHOD IsTransitionStub(CORDB_ADDRESS address, BOOL *pbTransitionStub);
+ COM_METHOD EnumerateModules(ICorDebugModuleEnum **ppModules);
+ COM_METHOD GetModuleFromMetaDataInterface(IUnknown *pIMetaData,
+ ICorDebugModule **ppModule);
+ COM_METHOD SetStopState(DWORD threadID, CorDebugThreadState state);
+ COM_METHOD IsOSSuspended(DWORD threadID, BOOL *pbSuspended);
+ COM_METHOD GetThreadContext(DWORD threadID, ULONG32 contextSize,
+ BYTE context[]);
+ COM_METHOD SetThreadContext(DWORD threadID, ULONG32 contextSize,
+ BYTE context[]);
+ COM_METHOD ReadMemory(CORDB_ADDRESS address, DWORD size, BYTE buffer[],
+ SIZE_T *read);
+ COM_METHOD WriteMemory(CORDB_ADDRESS address, DWORD size, BYTE buffer[],
+ SIZE_T *written);
+
+ COM_METHOD ClearCurrentException(DWORD threadID);
+
+ /*
+ * EnableLogMessages enables/disables sending of log messages to the
+ * debugger for logging.
+ */
+ COM_METHOD EnableLogMessages(BOOL fOnOff);
+
+ /*
+ * ModifyLogSwitch modifies the specified switch's severity level.
+ */
+ COM_METHOD ModifyLogSwitch(__in_z WCHAR *pLogSwitchName, LONG lLevel);
+
+ COM_METHOD EnumerateAppDomains(ICorDebugAppDomainEnum **ppAppDomains);
+ COM_METHOD GetObject(ICorDebugValue **ppObject);
+
+ //-----------------------------------------------------------
+ // ICorDebugProcess2
+ //-----------------------------------------------------------
+
+ COM_METHOD GetThreadForTaskID(TASKID taskId, ICorDebugThread2 ** ppThread);
+ COM_METHOD GetVersion(COR_VERSION* pInfo);
+
+ COM_METHOD SetUnmanagedBreakpoint(CORDB_ADDRESS address, ULONG32 bufsize, BYTE buffer[], ULONG32 * bufLen);
+ COM_METHOD ClearUnmanagedBreakpoint(CORDB_ADDRESS address);
+ COM_METHOD GetCodeAtAddress(CORDB_ADDRESS address, ICorDebugCode ** pCode, ULONG32 * offset);
+
+ COM_METHOD SetDesiredNGENCompilerFlags(DWORD pdwFlags);
+ COM_METHOD GetDesiredNGENCompilerFlags(DWORD *pdwFlags );
+
+ COM_METHOD GetReferenceValueFromGCHandle(UINT_PTR handle, ICorDebugReferenceValue **pOutValue);
+
+ //-----------------------------------------------------------
+ // ICorDebugProcess3
+ //-----------------------------------------------------------
+
+ // enables or disables CustomNotifications of a given type
+ COM_METHOD SetEnableCustomNotification(ICorDebugClass * pClass, BOOL fEnable);
+
+ //-----------------------------------------------------------
+ // ICorDebugProcess4
+ //-----------------------------------------------------------
+ COM_METHOD Filter(
+ const BYTE pRecord[],
+ DWORD countBytes,
+ CorDebugRecordFormat format,
+ DWORD dwFlags,
+ DWORD dwThreadId,
+ ICorDebugManagedCallback *pCallback,
+ DWORD * pContinueStatus);
+
+ COM_METHOD ProcessStateChanged(CorDebugStateChange eChange);
+
+ //-----------------------------------------------------------
+ // ICorDebugProcess5
+ //-----------------------------------------------------------
+ COM_METHOD GetGCHeapInformation(COR_HEAPINFO *pHeapInfo);
+ COM_METHOD EnumerateHeap(ICorDebugHeapEnum **ppObjects);
+ COM_METHOD EnumerateHeapRegions(ICorDebugHeapSegmentEnum **ppRegions);
+ COM_METHOD GetObject(CORDB_ADDRESS addr, ICorDebugObjectValue **pObject);
+ COM_METHOD EnableNGENPolicy(CorDebugNGENPolicy ePolicy);
+ COM_METHOD EnumerateGCReferences(BOOL enumerateWeakReferences, ICorDebugGCReferenceEnum **ppEnum);
+ COM_METHOD EnumerateHandles(CorGCReferenceType types, ICorDebugGCReferenceEnum **ppEnum);
+ COM_METHOD GetTypeID(CORDB_ADDRESS obj, COR_TYPEID *pId);
+ COM_METHOD GetTypeForTypeID(COR_TYPEID id, ICorDebugType **ppType);
+ COM_METHOD GetArrayLayout(COR_TYPEID id, COR_ARRAY_LAYOUT *pLayout);
+ COM_METHOD GetTypeLayout(COR_TYPEID id, COR_TYPE_LAYOUT *pLayout);
+ COM_METHOD GetTypeFields(COR_TYPEID id, ULONG32 celt, COR_FIELD fields[], ULONG32 *pceltNeeded);
+
+ //-----------------------------------------------------------
+ // ICorDebugProcess7
+ //-----------------------------------------------------------
+ COM_METHOD SetWriteableMetadataUpdateMode(WriteableMetadataUpdateMode flags);
+
+ //-----------------------------------------------------------
+ // ICorDebugProcess8
+ //-----------------------------------------------------------
+ COM_METHOD EnableExceptionCallbacksOutsideOfMyCode(BOOL enableExceptionsOutsideOfJMC);
+
+#ifdef FEATURE_LEGACYNETCF_DBG_HOST_CONTROL
+ // ---------------------------------------------------------------
+ // ICorDebugLegacyNetCFHostCallbackInvoker_PrivateWindowsPhoneOnly
+ // ---------------------------------------------------------------
+
+ COM_METHOD InvokePauseCallback();
+ COM_METHOD InvokeResumeCallback();
+
+#endif
+
+ //-----------------------------------------------------------
+ // Methods not exposed via a COM interface.
+ //-----------------------------------------------------------
+
+ HRESULT ContinueInternal(BOOL fIsOutOfBand);
+ HRESULT StopInternal(DWORD dwTimeout, VMPTR_AppDomain pAppDomainToken);
+
+ // Sets an unmanaged breakpoint at the target address
+ HRESULT SetUnmanagedBreakpointInternal(CORDB_ADDRESS address, ULONG32 bufsize, BYTE buffer[], ULONG32 * bufLen);
+
+ // Allocate a buffer within the target and return the range. Throws on error.
+ TargetBuffer GetRemoteBuffer(ULONG cbBuffer); // throws
+
+ // Same as above except also copy-in the contents of a RS buffer using WriteProcessMemory
+ HRESULT GetAndWriteRemoteBuffer(CordbAppDomain *pDomain, unsigned int bufferSize, const void *bufferFrom, void **ppBuffer);
+
+ /*
+ * This will release a previously allocated left side buffer.
+ * Often they are deallocated by the LS itself.
+ */
+ HRESULT ReleaseRemoteBuffer(void **ppBuffer);
+
+
+ void TargetConsistencyCheck(bool fExpression);
+
+ // Activate interop-debugging, after the process has initially been Init()
+ void EnableInteropDebugging();
+
+ HRESULT Init();
+ void DeleteQueuedEvents();
+ void CleanupHalfBakedLeftSide();
+ void Terminating(BOOL fDetach);
+
+ CordbThread * TryLookupThread(VMPTR_Thread vmThread);
+ CordbThread * TryLookupOrCreateThreadByVolatileOSId(DWORD dwThreadId);
+ CordbThread * TryLookupThreadByVolatileOSId(DWORD dwThreadId);
+ CordbThread * LookupOrCreateThread(VMPTR_Thread vmThread);
+
+ void QueueManagedAttachIfNeeded();
+ void QueueManagedAttachIfNeededWorker();
+ HRESULT QueueManagedAttach();
+
+ void DetachShim();
+
+ // Flush for when the process is running.
+ void FlushProcessRunning();
+
+ // Flush all state.
+ void FlushAll();
+
+ BOOL HijackThreadForUnhandledExceptionIfNeeded(DWORD dwThreadId);
+
+ // Filter a CLR notification (subset of exceptions).
+ void FilterClrNotification(
+ DebuggerIPCEvent * pManagedEvent,
+ RSLockHolder * pLockHolder,
+ ICorDebugManagedCallback * pCallback);
+
+ // Wrapper to invoke IClrDataTarget4::ContinueStatusChanged
+ void ContinueStatusChanged(DWORD dwThreadId, CORDB_CONTINUE_STATUS dwContinueStatus);
+
+
+ // Request a synchronization to occur after a debug event is dispatched.
+ void RequestSyncAtEvent();
+
+ //
+ // Basic managed event plumbing
+ //
+
+ // This is called on the first IPC event from the debuggee. It initializes state.
+ void FinishInitializeIPCChannel();
+ void FinishInitializeIPCChannelWorker();
+
+ // This is called on each IPC event from the debuggee.
+ void HandleRCEvent(DebuggerIPCEvent * pManagedEvent, RSLockHolder * pLockHolder, ICorDebugManagedCallback * pCallback);
+
+ // Queue the RC event.
+ void QueueRCEvent(DebuggerIPCEvent * pManagedEvent);
+
+ // This marshals a managed debug event from the
+ void MarshalManagedEvent(DebuggerIPCEvent * pManagedEvent);
+
+ // This copies a managed debug event from the IPC block and to pManagedEvent.
+ // The event still needs to be marshalled.
+ void CopyRCEventFromIPCBlock(DebuggerIPCEvent * pManagedEvent);
+
+ // This copies a managed debug event out of the Native-Debug event envelope.
+ // The event still needs to be marshalled.
+ bool CopyManagedEventFromTarget(const EXCEPTION_RECORD * pRecord, DebuggerIPCEvent * pLocalManagedEvent);
+
+ // Helper for Filter() to verify parameters and return a type-safe exception record.
+ const EXCEPTION_RECORD * ValidateExceptionRecord(
+ const BYTE pRawRecord[],
+ DWORD countBytes,
+ CorDebugRecordFormat format);
+
+ // Helper to read a structure from the target.
+ template<typename T>
+ HRESULT SafeReadStruct(CORDB_ADDRESS pRemotePtr, T* pLocalBuffer);
+
+ // Helper to write a structure into the target.
+ template<typename T>
+ HRESULT SafeWriteStruct(CORDB_ADDRESS pRemotePtr, const T* pLocalBuffer);
+
+ // Reads a buffer from the target
+ HRESULT SafeReadBuffer(TargetBuffer tb, BYTE * pLocalBuffer, BOOL throwOnError = TRUE);
+
+ // Writes a buffer to the target
+ void SafeWriteBuffer(TargetBuffer tb, const BYTE * pLocalBuffer);
+
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ void DuplicateHandleToLocalProcess(HANDLE * pLocalHandle, RemoteHANDLE * pRemoteHandle);
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ bool IsThreadSuspendedOrHijacked(ICorDebugThread * pICorDebugThread);
+
+ // Helper to get PID internally.
+ DWORD GetPid();
+
+ HRESULT GetRuntimeOffsets();
+
+ // Are we blocked waiting fo ran OOB event to be continue?
+ bool IsWaitingForOOBEvent()
+ {
+#ifdef FEATURE_INTEROP_DEBUGGING
+ return m_outOfBandEventQueue != NULL;
+#else
+ // If no interop, then we're never waiting for an OOB event.
+ return false;
+#endif
+ }
+
+ //
+ // Shim callbacks to simulate fake attach events.
+ //
+
+
+ // Callback for Shim to get the assemblies in load order
+ void GetAssembliesInLoadOrder(
+ ICorDebugAppDomain * pAppDomain,
+ RSExtSmartPtr<ICorDebugAssembly>* pAssemblies,
+ ULONG countAssemblies);
+
+ // Callback for Shim to get the modules in load order
+ void GetModulesInLoadOrder(
+ ICorDebugAssembly * pAssembly,
+ RSExtSmartPtr<ICorDebugModule>* pModules,
+ ULONG countModules);
+
+ // Functions to queue fake Connection events on attach.
+ static void CountConnectionsCallback(DWORD id, LPCWSTR pName, void * pUserData);
+ static void EnumerateConnectionsCallback(DWORD id, LPCWSTR pName, void * pUserData);
+ void QueueFakeConnectionEvents();
+
+
+
+ void DispatchRCEvent();
+
+ // Dispatch a single event via the callbacks.
+ void RawDispatchEvent(
+ DebuggerIPCEvent * pEvent,
+ RSLockHolder * pLockHolder,
+ ICorDebugManagedCallback * pCallback1,
+ ICorDebugManagedCallback2 * pCallback2,
+ ICorDebugManagedCallback3 * pCallback3);
+
+ void MarkAllThreadsDirty();
+
+ bool CheckIfLSExited();
+
+ void Lock()
+ {
+ // Lock Hierarchy - shouldn't have List lock when taking/release the process lock.
+
+ m_processMutex.Lock();
+ LOG((LF_CORDB, LL_EVERYTHING, "P::Lock enter, this=0x%p\n", this));
+ }
+
+ void Unlock()
+ {
+ // Lock Hierarchy - shouldn't have List lock when taking/releasing the process lock.
+
+ LOG((LF_CORDB, LL_EVERYTHING, "P::Lock leave, this=0x%p\n", this));
+ m_processMutex.Unlock();
+ }
+
+#ifdef _DEBUG
+ bool ThreadHoldsProcessLock()
+ {
+ return m_processMutex.HasLock();
+ }
+#endif
+
+ // Expose the process lock.
+ // This is the main lock in V3.
+ RSLock * GetProcessLock()
+ {
+ return &m_processMutex;
+ }
+
+
+ // @dbgtodo synchronization - the SG lock goes away in V3.
+ // Expose the stop-go lock b/c varios Cordb objects in our process tree may need to take it.
+ RSLock * GetStopGoLock()
+ {
+ return &m_StopGoLock;
+ }
+
+
+ void UnrecoverableError(HRESULT errorHR,
+ unsigned int errorCode,
+ const char *errorFile,
+ unsigned int errorLine);
+ HRESULT CheckForUnrecoverableError();
+ void VerifyControlBlock();
+
+ // The implementation of EnumerateThreads without the public API error checks
+ VOID InternalEnumerateThreads(RSInitHolder<CordbHashTableEnum> * ppThreads);
+
+ //-----------------------------------------------------------
+ // Convenience routines
+ //-----------------------------------------------------------
+
+ // Is it safe to send events to the LS?
+ bool IsSafeToSendEvents() { return !m_unrecoverableError && !m_terminated && !m_detached; }
+
+ bool IsWin32EventThread();
+
+ void HandleSyncCompleteRecieved();
+
+ // Send a truly asynchronous IPC event.
+ void SendAsyncIPCEvent(DebuggerIPCEventType t);
+
+ HRESULT SendIPCEvent(DebuggerIPCEvent *event, SIZE_T eventSize)
+ {
+ // @dbgtodo - eventually remove this when all IPC events are gone.
+ // In V3 paths, we can't send IPC events.
+ if (GetShim() == NULL)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "!! Can't send IPC event in V3. %s", IPCENames::GetName(event->type));
+ return E_NOTIMPL;
+ }
+ _ASSERTE(m_cordb != NULL);
+ return (m_cordb->SendIPCEvent(this, event, eventSize));
+ }
+
+ void InitAsyncIPCEvent(DebuggerIPCEvent *ipce,
+ DebuggerIPCEventType type,
+ VMPTR_AppDomain vmAppDomain)
+ {
+ // Async events only allowed for the following:
+ _ASSERTE(type == DB_IPCE_ATTACHING);
+
+ InitIPCEvent(ipce, type, false, vmAppDomain);
+ ipce->asyncSend = true;
+ }
+
+ void InitIPCEvent(DebuggerIPCEvent *ipce,
+ DebuggerIPCEventType type,
+ bool twoWay,
+ VMPTR_AppDomain vmAppDomain
+ )
+ {
+ // zero out the event in case we try and use any uninitialized fields
+ memset( ipce, 0, sizeof(DebuggerIPCEvent) );
+
+ _ASSERTE((!vmAppDomain.IsNull()) ||
+ type == DB_IPCE_GET_GCHANDLE_INFO ||
+ type == DB_IPCE_ENABLE_LOG_MESSAGES ||
+ type == DB_IPCE_MODIFY_LOGSWITCH ||
+ type == DB_IPCE_ASYNC_BREAK ||
+ type == DB_IPCE_CONTINUE ||
+ type == DB_IPCE_GET_BUFFER ||
+ type == DB_IPCE_RELEASE_BUFFER ||
+ type == DB_IPCE_IS_TRANSITION_STUB ||
+ type == DB_IPCE_ATTACHING ||
+ type == DB_IPCE_APPLY_CHANGES ||
+ type == DB_IPCE_CONTROL_C_EVENT_RESULT ||
+ type == DB_IPCE_SET_REFERENCE ||
+ type == DB_IPCE_SET_ALL_DEBUG_STATE ||
+ type == DB_IPCE_GET_THREAD_FOR_TASKID ||
+ type == DB_IPCE_DETACH_FROM_PROCESS ||
+ type == DB_IPCE_INTERCEPT_EXCEPTION ||
+ type == DB_IPCE_GET_NGEN_COMPILER_FLAGS ||
+ type == DB_IPCE_SET_NGEN_COMPILER_FLAGS ||
+ type == DB_IPCE_SET_VALUE_CLASS);
+
+ ipce->type = type;
+ ipce->hr = S_OK;
+ ipce->processId = 0;
+ ipce->vmAppDomain = vmAppDomain;
+ ipce->vmThread = VMPTR_Thread::NullPtr();
+ ipce->replyRequired = twoWay;
+ ipce->asyncSend = false;
+ ipce->next = NULL;
+ }
+
+ // Looks up a previously constructed CordbClass instance without creating. May return NULL if the
+ // CordbClass instance doesn't exist.
+ CordbClass * LookupClass(ICorDebugAppDomain * pAppDomain, VMPTR_DomainFile vmDomainFile, mdTypeDef classToken);
+
+ CordbModule * LookupOrCreateModule(VMPTR_DomainFile vmDomainFile);
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ CordbUnmanagedThread *GetUnmanagedThread(DWORD dwThreadId)
+ {
+ _ASSERTE(ThreadHoldsProcessLock());
+ return m_unmanagedThreads.GetBase(dwThreadId);
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ /*
+ * This will cleanup the patch table, releasing memory,etc.
+ */
+ void ClearPatchTable();
+
+ /*
+ * This will grab the patch table from the left side & go through
+ * it to gather info needed for faster access. If address,size,buffer
+ * are passed in, while going through the table we'll undo patches
+ * in buffer at the same time
+ */
+ HRESULT RefreshPatchTable(CORDB_ADDRESS address = NULL, SIZE_T size = NULL, BYTE buffer[] = NULL);
+
+ // Find if a patch exists at a given address.
+ HRESULT FindPatchByAddress(CORDB_ADDRESS address, bool *patchFound, bool *patchIsUnmanaged);
+
+ enum AB_MODE
+ {
+ AB_READ,
+ AB_WRITE
+ };
+
+ /*
+ * Once we've called RefreshPatchTable to get the patch table,
+ * this routine will iterate through the patches & either apply
+ * or unapply the patches to buffer. AB_READ => Replaces patches
+ * in buffer with the original opcode, AB_WRTE => replace opcode
+ * with breakpoint instruction, caller is responsible for
+ * updating the patchtable back to the left side.
+ *
+ * <TODO>@todo Perf Instead of a copy, undo the changes
+ * Since the 'buffer' arg is an [in] param, we're not supposed to
+ * change it. If we do, we'll allocate & copy it to bufferCopy
+ * (we'll also set *pbUpdatePatchTable to true), otherwise we
+ * don't manipuldate bufferCopy (so passing a NULL in for
+ * reading is fine).</TODO>
+ */
+ HRESULT AdjustBuffer(CORDB_ADDRESS address,
+ SIZE_T size,
+ BYTE buffer[],
+ BYTE **bufferCopy,
+ AB_MODE mode,
+ BOOL *pbUpdatePatchTable = NULL);
+
+ /*
+ * AdjustBuffer, above, doesn't actually update the local patch table
+ * if asked to do a write. It stores the changes alongside the table,
+ * and this will cause the changes to be written to the table (for
+ * a range of left-side addresses
+ */
+ void CommitBufferAdjustments(CORDB_ADDRESS start,
+ CORDB_ADDRESS end);
+
+ /*
+ * Clear the stored changes, or they'll sit there until we
+ * accidentally commit them
+ */
+ void ClearBufferAdjustments();
+
+
+
+
+ //-----------------------------------------------------------
+ // Accessors for key synchronization fields.
+ //-----------------------------------------------------------
+
+ // If CAD is NULL, returns true if all appdomains (ie, the entire process)
+ // is synchronized. Otherwise, returns true if the specified appdomain is
+ // synch'd.
+ bool GetSynchronized();
+ void SetSynchronized(bool fSynch);
+
+ void IncStopCount();
+ void DecStopCount();
+
+ // Gets the exact stop count. You need the Proecss lock for this.
+ int GetStopCount();
+
+ // Just gets whether we're stopped or not (m_stopped > 0).
+ // You only need the StopGo lock for this.
+ // This is biases towards returning false.
+ bool IsStopped();
+
+ bool GetSyncCompleteRecv();
+ void SetSyncCompleteRecv(bool fSyncRecv);
+
+
+ // Cordbg may not always continue during a callback; but we really shouldn't do meaningful
+ // work after a callback has returned yet before they've called continue. Thus we may need
+ // to remember some state at the time of dispatch so that we do stuff at continue.
+ // Only example here is neutering... we'd like to Neuter an object X after the ExitX callback,
+ // but we can't neuter it until Continue. So remember X when we dispatch, and neuter this at continue.
+ // Use a smart ptr to keep it alive until we neuter it.
+
+ // Add objects to various neuter lists.
+ // NeuterOnContinue is for all objects that can be neutered once we continue.
+ // NeuterOnExit is for all objects that can survive continues (but are neutered on process shutdown).
+ // If an object's external ref count goes to 0, it gets promoted to the NeuterOnContinue list.
+ void AddToNeuterOnExitList(CordbBase *pObject);
+ void AddToNeuterOnContinueList(CordbBase *pObject);
+
+ NeuterList * GetContinueNeuterList() { return &m_ContinueNeuterList; }
+ NeuterList * GetExitNeuterList() { return &m_ExitNeuterList; }
+
+ void AddToLeftSideResourceCleanupList(CordbBase * pObject);
+
+ // Routines to read and write thread context records between the processes safely.
+ HRESULT SafeReadThreadContext(LSPTR_CONTEXT pRemoteContext, DT_CONTEXT * pCtx);
+ HRESULT SafeWriteThreadContext(LSPTR_CONTEXT pRemoteContext, const DT_CONTEXT * pCtx);
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // Record a win32 event for debugging purposes.
+ void DebugRecordWin32Event(const DEBUG_EVENT * pEvent, CordbUnmanagedThread * pUThread);
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ //-----------------------------------------------------------
+ // Interop Helpers
+ //-----------------------------------------------------------
+
+ // Get the DAC interface.
+ IDacDbiInterface * GetDAC();
+
+ // Get the data-target, which provides access to the debuggee.
+ ICorDebugDataTarget * GetDataTarget();
+
+ BOOL IsDacInitialized();
+
+ void ForceDacFlush();
+
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // Deal with native debug events for the interop-debugging scenario.
+ void HandleDebugEventForInteropDebugging(const DEBUG_EVENT * pEvent);
+
+ void ResumeHijackedThreads();
+
+ //@todo - We should try to make these all private
+ CordbUnmanagedThread *HandleUnmanagedCreateThread(DWORD dwThreadId, HANDLE hThread, void *lpThreadLocalBase);
+
+ HRESULT ContinueOOB();
+ void QueueUnmanagedEvent(CordbUnmanagedThread *pUThread, const DEBUG_EVENT *pEvent);
+ void DequeueUnmanagedEvent(CordbUnmanagedThread *pUThread);
+ void QueueOOBUnmanagedEvent(CordbUnmanagedThread *pUThread, const DEBUG_EVENT *pEvent);
+ void DequeueOOBUnmanagedEvent(CordbUnmanagedThread *pUThread);
+ void DispatchUnmanagedInBandEvent();
+ void DispatchUnmanagedOOBEvent();
+ bool ExceptionIsFlare(DWORD exceptionCode, const void *exceptionAddress);
+
+ bool IsSpecialStackOverflowCase(CordbUnmanagedThread *pUThread, const DEBUG_EVENT *pEvent);
+
+ HRESULT SuspendUnmanagedThreads();
+ HRESULT ResumeUnmanagedThreads();
+
+ HRESULT HijackIBEvent(CordbUnmanagedEvent * pUnmanagedEvent);
+
+ BOOL HasUndispatchedNativeEvents();
+ BOOL HasUserUncontinuedNativeEvents();
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ HRESULT StartSyncFromWin32Stop(BOOL * pfAsyncBreakSent);
+
+
+ // For interop attach, we first do native, and then once Cordbg continues from
+ // the loader-bp, we kick off the managed attach. This field remembers that
+ // whether we need the managed attach.
+ // @dbgtodo managed pipeline - hoist to shim.
+ bool m_fDoDelayedManagedAttached;
+
+
+
+ // Table of CordbEval objects that we've sent over to the LS.
+ // This is synced via the process lock.
+ RsPtrTable<CordbEval> m_EvalTable;
+
+ void PrepopulateThreadsOrThrow();
+
+ // Lookup or create an appdomain.
+ CordbAppDomain * LookupOrCreateAppDomain(VMPTR_AppDomain vmAppDomain);
+
+ // Get the shared app domain.
+ CordbAppDomain * GetSharedAppDomain();
+
+ // Get metadata dispenser.
+ IMetaDataDispenserEx * GetDispenser();
+
+ // Sets a bitfield reflecting the managed debugging state at the time of
+ // the jit attach.
+ HRESULT GetAttachStateFlags(CLR_DEBUGGING_PROCESS_FLAGS *pFlags);
+
+ HRESULT GetTypeForObject(CORDB_ADDRESS obj, CordbType **ppType, CordbAppDomain **pAppDomain = NULL);
+
+ WriteableMetadataUpdateMode GetWriteableMetadataUpdateMode() { return m_writableMetadataUpdateMode; }
+private:
+
+#ifdef _DEBUG
+ // Assert that vmAppDomainDeleted doesn't show up in dac enumerations
+ void DbgAssertAppDomainDeleted(VMPTR_AppDomain vmAppDomainDeleted);
+
+ // Callback helper for DbgAssertAppDomainDeleted.
+ static void DbgAssertAppDomainDeletedCallback(VMPTR_AppDomain vmAppDomain, void * pUserData);
+#endif // _DEBUG
+
+ static void ThreadEnumerationCallback(VMPTR_Thread vmThread, void * pUserData);
+
+
+ // Callback for AppDomain enumeration
+ static void AppDomainEnumerationCallback(VMPTR_AppDomain vmAppDomain, void * pUserData);
+
+ // Helper to create a new CordbAppDomain around the vmptr and cache it
+ CordbAppDomain * CacheAppDomain(VMPTR_AppDomain vmAppDomain);
+
+ // Helper to traverse Appdomains in target and build up our cache.
+ void PrepopulateAppDomainsOrThrow();
+
+
+ void ProcessFirstLogMessage (DebuggerIPCEvent *event);
+ void ProcessContinuedLogMessage (DebuggerIPCEvent *event);
+
+ void CloseIPCHandles();
+ void UpdateThreadsForAdUnload( CordbAppDomain* pAppDomain );
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // Each win32 debug event needs to be triaged to get a Reaction.
+ Reaction TriageBreakpoint(CordbUnmanagedThread * pUnmanagedThread, const DEBUG_EVENT * pEvent);
+ Reaction TriageSyncComplete();
+ Reaction Triage1stChanceNonSpecial(CordbUnmanagedThread * pUnmanagedThread, const DEBUG_EVENT * pEvent);
+ Reaction TriageExcep1stChanceAndInit(CordbUnmanagedThread * pUnmanagedThread, const DEBUG_EVENT * pEvent);
+ Reaction TriageExcep2ndChanceAndInit(CordbUnmanagedThread * pUnmanagedThread, const DEBUG_EVENT * pEvent);
+ Reaction TriageWin32DebugEvent(CordbUnmanagedThread * pUnmanagedThread, const DEBUG_EVENT * pEvent);
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+public:
+ RSSmartPtr<Cordb> m_cordb;
+
+private:
+ // OS process handle to live process.
+ // @dbgtodo - , Move this into the Shim. This should only be needed in the live-process
+ // case. Get rid of this since it breaks the data-target abstraction.
+ // For Mac debugging, this handle is of course not the real process handle. This is just a handle to
+ // wait on for process termination.
+ HANDLE m_handle;
+
+public:
+ // Wrapper to get the OS process handle. This is unsafe because it breaks the data-target abstraction.
+ // The only things that need this should be calls to DuplicateHandle, and some shimming work.
+ HANDLE UnsafeGetProcessHandle()
+ {
+ return m_handle;
+ }
+
+ // Set when code:CordbProcess::Detach is called.
+ // Public APIs can check this and return CORDBG_E_PROCESS_DETACHED.
+ // @dbgtodo managed pipeline - really could merge this with neuter.
+ bool m_detached;
+
+ // True if we code:CordbProcess::Stop is called before the managed CreateProcess event.
+ // In this case, m_initialized is false, and we can't send an AsyncBreak event to the LS.
+ // (since the LS isn't going to send a SyncComplete event back since the CLR isn't loaded/ready).
+ // @dbgtodo managed pipeline - move into shim, along with Stop/Continue.
+ bool m_uninitializedStop;
+
+
+ // m_exiting is true if we know the LS is starting to exit (if the
+ // RS is telling the LS to exit) or if we know the LS has already exited.
+ bool m_exiting;
+
+
+ // m_terminated can only be set to true if we know 100% the LS has exited (ie, somebody
+ // waited on the LS process handle).
+ bool m_terminated;
+
+ bool m_unrecoverableError;
+
+ bool m_specialDeferment;
+ bool m_helperThreadDead; // flag used for interop
+
+ // This tracks if the loader breakpoint has been received during interop-debugging.
+ // The Loader Breakpoint is an breakpoint event raised by the OS once the debugger is attached.
+ // It comes in both Attach and Launch scenarios.
+ // This is also used in fake-native debugging scenarios.
+ bool m_loaderBPReceived;
+
+
+private:
+
+ // MetaData dispenser.
+ RSExtSmartPtr<IMetaDataDispenserEx> m_pMetaDispenser;
+
+ //
+ // Count of the number of outstanding CordbEvals in the process.
+ //
+ LONG m_cOutstandingEvals;
+
+ // Number of oustanding code:CordbHandleValue objects containing
+ // Left-side resources. This can be used to tell if ICorDebug needs to
+ // cleanup gc handles.
+ LONG m_cOutstandingHandles;
+
+ // Pointer to the CordbModule instance that can currently change the Jit flags.
+ // There can be at most one of these. It will represent a module that has just been loaded, before the
+ // Continue is sent. See code:CordbProcess::RawDispatchEvent and code:CordbProcess::ContinueInternal.
+ CordbModule * m_pModuleThatCanChangeJitFlags;
+
+public:
+ LONG OutstandingEvalCount()
+ {
+ return m_cOutstandingEvals;
+ }
+
+ void IncrementOutstandingEvalCount()
+ {
+ InterlockedIncrement(&m_cOutstandingEvals);
+ }
+
+ void DecrementOutstandingEvalCount()
+ {
+ InterlockedDecrement(&m_cOutstandingEvals);
+ }
+
+ LONG OutstandingHandles();
+ void IncrementOutstandingHandles();
+ void DecrementOutstandingHandles();
+
+ //
+ // Is it OK to detach at this time
+ //
+ HRESULT IsReadyForDetach();
+
+
+private:
+ // This is a target pointer that uniquely identifies the runtime in the target.
+ // This lets ICD discriminate between multiple CLRs within a single process.
+ // On windows, this is the base-address of mscorwks.dll in the target.
+ // If this is 0, then we have V2 semantics where there was only 1 CLR in the target.
+ // In that case, we can lazily initialize it in code:CordbProcess::CopyManagedEventFromTarget.
+ // This is just used for backwards compat.
+ CORDB_ADDRESS m_clrInstanceId;
+
+ // List of things that get neutered on process exit and Continue respectively.
+ NeuterList m_ExitNeuterList;
+ NeuterList m_ContinueNeuterList;
+
+ // List of objects that hold resources into the left-side.
+ // This is currently for funceval, which cleans up resources in code:CordbEval::SendCleanup.
+ // @dbgtodo - , (func-eval feature crew): we can get rid of this
+ // list if we make func-eval not hold resources after it's complete.
+ LeftSideResourceCleanupList m_LeftSideResourceCleanupList;
+
+ // m_stopCount, m_synchronized, & m_syncCompleteReceived are key fields describing
+ // the processes' sync status.
+ DWORD m_stopCount;
+
+ // m_synchronized is the Debugger's view of SyncStatus. It will go high & low for each
+ // callback. Continue() will set this to false.
+ // This flag is true roughly from the time that we've dispatched a managed callback
+ // until the time that it's continued.
+ bool m_synchronized;
+
+ // m_syncCompleteReceived tells us if the runtime is _actually_ sychronized. It goes
+ // high once we get a SyncComplete, and it goes low once we actually send the continue.
+ // This is always set by the thread that receives the sync-complete. In interop, that's the w32et.
+ // Thus this is the most accurate indication of wether the Debuggee is _actually_ synchronized or not.
+ bool m_syncCompleteReceived;
+
+
+ // Back pointer to Shim process. This is used for hooks back into the shim.
+ // If this is Non-null, then we're emulating the V2 case. If this is NULL, then it's the real V3 pipeline.
+ RSExtSmartPtr<ShimProcess> m_pShim;
+
+ CordbSafeHashTable<CordbThread> m_userThreads;
+
+public:
+ ShimProcess* GetShim();
+
+ bool m_oddSync;
+
+
+ void BuildThreadEnum(CordbBase * pOwnerObj, NeuterList * pOwnerList, RSInitHolder<CordbHashTableEnum> * pHolder);
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // List of unmanaged threads. This is only populated for interop-debugging.
+ CordbSafeHashTable<CordbUnmanagedThread> m_unmanagedThreads;
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ CordbSafeHashTable<CordbAppDomain> m_appDomains;
+
+ CordbAppDomain * m_sharedAppDomain;
+
+ // Since a stepper can begin in one appdomain, and complete in another,
+ // we put the hashtable here, rather than on specific appdomains.
+ CordbSafeHashTable<CordbStepper> m_steppers;
+
+ // Used to figure out if we have to refresh any reference objects
+ // on the left side. Gets incremented each time a continue is called, or
+ // global debugee state is modified in some other way.
+ UINT m_continueCounter;
+
+ // Used to track whether the DAC cache has been flushed.
+ // We use this information to determine whether CordbStackWalk instances need to
+ // be refreshed.
+ UINT m_flushCounter;
+
+ // The DCB is essentially a buffer area used to temporarily hold information read from the debugger
+ // control block residing on the LS helper thread. We make no assumptions about the validity of this
+ // information over time, so before using a value from it on the RS, we will always update this buffer
+ // with a call to UpdateRightSideDCB. This uses a ReadProcessMemory to get the current information from
+ // the LS DCB.
+ DebuggerIPCControlBlock * GetDCB() {return ((m_pEventChannel == NULL) ? NULL : m_pEventChannel->GetDCB());}
+
+
+ DebuggerIPCRuntimeOffsets m_runtimeOffsets;
+ HANDLE m_leftSideEventAvailable;
+ HANDLE m_leftSideEventRead;
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ HANDLE m_leftSideUnmanagedWaitEvent;
+#endif // FEATURE_INTEROP_DEBUGGING
+
+
+ // This becomes true when the RS receives its first managed event.
+ // This goes false in shutdown cases.
+ // If this is true, we can assume:
+ // - the CLR is loaded.
+ // - the IPC block is opened and initialized.
+ // - DAC is initialized (see code:CordbProcess::IsDacInitialized)
+ //
+ // If this is false, we can assume:
+ // - the CLR may not be loaded into the target process.
+ // - We can't send IPC events to the LS (because we can't expect a response)
+ //
+ // Many APIs can check this bit and return CORDBG_E_NOTREADY if it's false.
+ bool m_initialized;
+
+#ifdef _DEBUG
+ void * m_pDBGLastIPCEventType;
+#endif
+
+ bool m_stopRequested;
+ HANDLE m_stopWaitEvent;
+ RSLock m_processMutex;
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // The number of threads which are IsFirstChanceHijacked
+ DWORD m_cFirstChanceHijackedThreads;
+
+ CordbUnmanagedEvent *m_unmanagedEventQueue;
+ CordbUnmanagedEvent *m_lastQueuedUnmanagedEvent;
+ CordbUnmanagedEvent *m_lastQueuedOOBEvent;
+ CordbUnmanagedEvent *m_outOfBandEventQueue;
+
+ CordbUnmanagedEvent *m_lastDispatchedIBEvent;
+ bool m_dispatchingUnmanagedEvent;
+ bool m_dispatchingOOBEvent;
+ bool m_doRealContinueAfterOOBBlock;
+
+ enum
+ {
+ PS_WIN32_STOPPED = 0x0001,
+ PS_HIJACKS_IN_PLACE = 0x0002,
+ PS_SOME_THREADS_SUSPENDED = 0x0004,
+ PS_WIN32_ATTACHED = 0x0008,
+ PS_WIN32_OUTOFBAND_STOPPED = 0x0010,
+ };
+
+ unsigned int m_state;
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ // True if we're interop-debugging, else false.
+ bool IsInteropDebugging();
+
+ DWORD m_helperThreadId; // helper thread ID calculated from sniffing from UM thread-create events.
+
+ // Is the given thread id a helper thread (real or worker?)
+ bool IsHelperThreadWorked(DWORD tid);
+
+ //
+ // We cache the LS patch table on the RS.
+ //
+
+ // The array of entries. (The patchtable is a hash implemented as a single-array)
+ // This array includes empty entries.
+ // There is an auxillary bucket structure used to map hash codes to array indices.
+ // We traverse the array, and we recognize an empty slot
+ // if DebuggerControllerPatch::opcode == 0.
+ // If we haven't gotten the table, then m_pPatchTable is NULL
+ BYTE* m_pPatchTable;
+
+ // The number of entries (both used & unused) in m_pPatchTable.
+ UINT m_cPatch;
+
+ // so we know where to write the changes patchtable back to
+ // This has m_cPatch elements.
+ BYTE *m_rgData;
+
+ // Cached value of iNext entries such that:
+ // m_rgNextPatch[i] = ((DebuggerControllerPatch*)m_pPatchTable)[i]->iNext;
+ // where 0 <= i < m_cPatch
+ // This provides a linked list (via indices) to traverse the used entries of m_pPatchTable.
+ // This has m_cPatch elements.
+ ULONG *m_rgNextPatch;
+
+ // This has m_cPatch elements.
+ PRD_TYPE *m_rgUncommitedOpcode;
+
+ // CORDB_ADDRESS's are UINT_PTR's (64 bit under _WIN64, 32 bit otherwise)
+#if defined(DBG_TARGET_WIN64)
+#define MAX_ADDRESS (_UI64_MAX)
+#else
+#define MAX_ADDRESS (ULONG_MAX)
+#endif
+#define MIN_ADDRESS (0x0)
+ CORDB_ADDRESS m_minPatchAddr; //smallest patch in table
+ CORDB_ADDRESS m_maxPatchAddr;
+
+ // <TODO>@todo port : if slots of CHashTable change, so should these</TODO>
+#define DPT_TERMINATING_INDEX (UINT32_MAX)
+ // Index into m_pPatchTable of the first patch (first used entry).
+ ULONG m_iFirstPatch;
+
+ // Initializes the DAC
+ void InitDac();
+
+ // copy new data from LS DCB to RS buffer
+ void UpdateRightSideDCB();
+
+ // copy new data from RS DCB buffer to LS DCB
+ void UpdateLeftSideDCBField(void * rsFieldAddr, SIZE_T size);
+
+ // allocate and initialize the RS DCB buffer
+ void GetEventBlock(BOOL * pfBlockExists);
+
+ IEventChannel * GetEventChannel();
+
+ bool SupportsVersion(CorDebugInterfaceVersion featureVersion);
+
+ void StartEventDispatch(DebuggerIPCEventType event);
+ void FinishEventDispatch();
+ bool AreDispatchingEvent();
+
+ HANDLE GetHelperThreadHandle() { return m_hHelperThread; }
+
+ CordbAppDomain* GetDefaultAppDomain() { return m_pDefaultAppDomain; }
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // Lookup if there's a native BP at the given address. Return NULL not found.
+ NativePatch * GetNativePatch(const void * pAddress);
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ bool IsBreakOpcodeAtAddress(const void * address);
+
+private:
+ //
+ // handle to helper thread. Used for managed debugging.
+ // Initialized only after we get the tid from the DCB.
+ HANDLE m_hHelperThread;
+
+ DebuggerIPCEventType m_dispatchedEvent; // what event are we currently dispatching?
+
+ RSLock m_StopGoLock;
+
+ // Each process has exactly one Default AppDomain
+ // @dbgtodo appdomain : We should try and simplify things by removing this.
+ // At the moment it's necessary for CordbProcess::UpdateThreadsForAdUnload.
+ CordbAppDomain* m_pDefaultAppDomain; // owned by m_appDomains
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // Helpers
+ CordbUnmanagedThread * GetUnmanagedThreadFromEvent(const DEBUG_EVENT * pEvent);
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ // Ensure we have a CLR Instance ID to debug
+ HRESULT EnsureClrInstanceIdSet();
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // // The full debug event is too large, so we just remember the important stuff.
+ struct MiniDebugEvent
+ {
+ BYTE code; // event code from the debug event
+ CordbUnmanagedThread * pUThread; // unmanaged thread this was on.
+ // @todo - we should have some misc data.
+ union
+ {
+ struct {
+ void * pAddress; // address of an exception
+ DWORD dwCode;
+ } ExceptionData;
+ struct {
+ void * pBaseAddress; // for module load & unload
+ } ModuleData;
+ } u;
+ };
+
+ // Group fields that are just used for debug support here.
+ // Some are included even in retail builds to help debug retail failures.
+ struct DebugSupport
+ {
+ // For debugging, we keep a rolling queue of the last N Win32 debug events.
+ MiniDebugEvent m_DebugEventQueue[DEBUG_EVENTQUEUE_SIZE];
+ int m_DebugEventQueueIdx;
+ int m_TotalNativeEvents;
+
+ // Breakdown of different types of native events
+ int m_TotalIB;
+ int m_TotalOOB;
+ int m_TotalCLR;
+ } m_DbgSupport;
+
+ CUnorderedArray<NativePatch, 10> m_NativePatchList;
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ //
+ // DAC
+ //
+
+ // Try to initalize DAC, may fail
+ BOOL TryInitializeDac();
+
+ // Expect DAC initialize to succeed.
+ void InitializeDac();
+
+
+ void CreateDacDbiInterface();
+
+ // Free DAC.
+ void FreeDac();
+
+
+ HModuleHolder m_hDacModule;
+ RSExtSmartPtr<ICorDebugDataTarget> m_pDACDataTarget;
+
+ // The mutable version of the data target, or null if read-only
+ RSExtSmartPtr<ICorDebugMutableDataTarget> m_pMutableDataTarget;
+
+ RSExtSmartPtr<ICorDebugMetaDataLocator> m_pMetaDataLocator;
+
+ IDacDbiInterface * m_pDacPrimitives;
+
+ IEventChannel * m_pEventChannel;
+
+ // If true, then we'll ASSERT if we detect the target is corrupt or inconsistent
+ // This switch is for diagnostics purposes only and should always be false in retail builds.
+ bool m_fAssertOnTargetInconsistency;
+
+ // When a successful attempt to read runtime offsets from LS occurs, this flag is set.
+ bool m_runtimeOffsetsInitialized;
+
+ // controls how metadata updated in the target is handled
+ WriteableMetadataUpdateMode m_writableMetadataUpdateMode;
+};
+
+// Some IMDArocess APIs are supported as interop-only.
+#define FAIL_IF_MANAGED_ONLY(pProcess) \
+{ CordbProcess * __Proc = pProcess; if (!__Proc->IsInteropDebugging()) return CORDBG_E_MUST_BE_INTEROP_DEBUGGING; }
+
+
+/* ------------------------------------------------------------------------- *
+ * Module class
+ * ------------------------------------------------------------------------- */
+
+class CordbModule : public CordbBase,
+ public ICorDebugModule,
+ public ICorDebugModule2,
+ public ICorDebugModule3
+{
+public:
+ CordbModule(CordbProcess * process,
+ VMPTR_Module vmModule,
+ VMPTR_DomainFile vmDomainFile);
+
+ virtual ~CordbModule();
+ virtual void Neuter();
+
+ using CordbBase::GetProcess;
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbModule"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugModule
+ //-----------------------------------------------------------
+
+ COM_METHOD GetProcess(ICorDebugProcess **ppProcess);
+ COM_METHOD GetBaseAddress(CORDB_ADDRESS *pAddress);
+ COM_METHOD GetAssembly(ICorDebugAssembly **ppAssembly);
+ COM_METHOD GetName(ULONG32 cchName, ULONG32 *pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+ COM_METHOD EnableJITDebugging(BOOL bTrackJITInfo, BOOL bAllowJitOpts);
+ COM_METHOD EnableClassLoadCallbacks(BOOL bClassLoadCallbacks);
+
+ // Gets the latest version of a function given the methodDef token
+ COM_METHOD GetFunctionFromToken(mdMethodDef methodDef,
+ ICorDebugFunction **ppFunction);
+ COM_METHOD GetFunctionFromRVA(CORDB_ADDRESS rva, ICorDebugFunction **ppFunction);
+ COM_METHOD GetClassFromToken(mdTypeDef typeDef,
+ ICorDebugClass **ppClass);
+ COM_METHOD CreateBreakpoint(ICorDebugModuleBreakpoint **ppBreakpoint);
+
+ // Not implemented - legacy
+ COM_METHOD GetEditAndContinueSnapshot(
+ ICorDebugEditAndContinueSnapshot **ppEditAndContinueSnapshot);
+
+ COM_METHOD GetMetaDataInterface(REFIID riid, IUnknown **ppObj);
+ COM_METHOD GetToken(mdModule *pToken);
+ COM_METHOD IsDynamic(BOOL *pDynamic);
+ COM_METHOD GetGlobalVariableValue(mdFieldDef fieldDef,
+ ICorDebugValue **ppValue);
+ COM_METHOD GetSize(ULONG32 *pcBytes);
+ COM_METHOD IsInMemory(BOOL *pInMemory);
+
+ //-----------------------------------------------------------
+ // ICorDebugModule2
+ //-----------------------------------------------------------
+ COM_METHOD SetJMCStatus(
+ BOOL fIsUserCode,
+ ULONG32 cOthers,
+ mdToken others[]);
+
+ // Applies an EnC edit to the module
+ COM_METHOD ApplyChanges(
+ ULONG cbMetaData,
+ BYTE pbMetaData[],
+ ULONG cbIL,
+ BYTE pbIL[]);
+
+ // Resolve an assembly given an AssemblyRef token. Note that
+ // this will not trigger the loading of assembly. If assembly is not yet loaded,
+ // this will return an CORDBG_E_CANNOT_RESOLVE_ASSEMBLY error
+ COM_METHOD ResolveAssembly(mdToken tkAssemblyRef,
+ ICorDebugAssembly **ppAssembly);
+
+ // Sets EnC and optimization flags
+ COM_METHOD SetJITCompilerFlags(DWORD dwFlags);
+
+ // Gets EnC and optimization flags
+ COM_METHOD GetJITCompilerFlags(DWORD *pdwFlags);
+
+ //-----------------------------------------------------------
+ // ICorDebugModule3
+ //-----------------------------------------------------------
+ COM_METHOD CreateReaderForInMemorySymbols(REFIID riid,
+ void** ppObj);
+
+ //-----------------------------------------------------------
+ // Internal members
+ //-----------------------------------------------------------
+
+#ifdef _DEBUG
+ // Debug helper to ensure that module is no longer discoverable
+ void DbgAssertModuleDeleted();
+#endif // _DEBUG
+
+ // Internal help to get the "name" (filename or pretty name) of the module.
+ HRESULT GetNameWorker(ULONG32 cchName, ULONG32 *pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+
+ // Marks that the module's metadata has become invalid and needs to be refetched.
+ void RefreshMetaData();
+
+ // Cache the current continue counter as the one that the LoadEvent is
+ // dispatched in.
+ void SetLoadEventContinueMarker();
+
+ // Return CORDBG_E_MUST_BE_IN_LOAD_MODULE if this module is not in its load callback.
+ HRESULT EnsureModuleIsInLoadCallback();
+
+ BOOL IsDynamic();
+
+ // Gets the latest version of the function for the methodDef, if any
+ CordbFunction * LookupFunctionLatestVersion(mdMethodDef methodToken);
+
+ // Gets the latest version of the function. Creates a new instance if none exists yet.
+ CordbFunction* LookupOrCreateFunctionLatestVersion(mdMethodDef funcMetaDataToken);
+
+ // Finds or creates a function for the first time (not for use on EnC if function doesn't exist yet)
+ CordbFunction * LookupOrCreateFunction(mdMethodDef token, SIZE_T enCVersion);
+
+ // Creates an CordbFunction instances for the first time (not for use on EnC)
+ CordbFunction * CreateFunction(mdMethodDef token, SIZE_T enCVersion);
+
+ // Creates a CordbFunction object to represent the specified EnC version
+ HRESULT UpdateFunction(mdMethodDef token,
+ SIZE_T newEnCVersion,
+ CordbFunction** ppFunction);
+
+ CordbClass* LookupClass(mdTypeDef classToken);
+ HRESULT LookupOrCreateClass(mdTypeDef classToken, CordbClass** ppClass);
+ HRESULT CreateClass(mdTypeDef classToken, CordbClass** ppClass);
+ HRESULT LookupClassByToken(mdTypeDef token, CordbClass **ppClass);
+ HRESULT ResolveTypeRef(mdTypeRef token, CordbClass **ppClass);
+ HRESULT ResolveTypeRefOrDef(mdToken token, CordbClass **ppClass);
+
+ // Sends the event to the left side to apply the changes to the debugee
+ HRESULT ApplyChangesInternal(
+ ULONG cbMetaData,
+ BYTE pbMetaData[],
+ ULONG cbIL,
+ BYTE pbIL[]);
+
+ // Pulls new metadata if needed in order to ensure the availability of
+ // the given token
+ void UpdateMetaDataCacheIfNeeded(mdToken token);
+
+ HRESULT InitPublicMetaDataFromFile(const WCHAR * pszFullPathName, DWORD dwOpenFlags, bool validateFileInfo);
+
+ // Creates a CordbNativeCode (if it's not already created) and adds it to the
+ // hash table of CordbNativeCodes belonging to the module.
+ CordbNativeCode * LookupOrCreateNativeCode(mdMethodDef methodToken,
+ VMPTR_MethodDesc methodDesc,
+ CORDB_ADDRESS startAddress);
+
+private:
+ // Set the metadata (both public and internal) for the module.
+ void InitMetaData(TargetBuffer buffer, BOOL useFileMappingOptimization);
+
+ // Checks if the given token is in the cached metadata
+ BOOL CheckIfTokenInMetaData(mdToken token);
+
+ // Update the public metadata given a buffer in the target.
+ void UpdatePublicMetaDataFromRemote(TargetBuffer bufferRemoteMetaData);
+
+ // Initialize just the public metadata by reading from an on-disk module
+ HRESULT InitPublicMetaDataFromFile();
+ // Initialize just the public metadata by reading new metadata from the buffer
+ void InitPublicMetaData(TargetBuffer buffer);
+
+ // Rebuild the internal metadata given the public one.
+ void UpdateInternalMetaData();
+
+ // Determines whether the on-disk metadata for this module is usable as the
+ // current metadata
+ BOOL IsFileMetaDataValid();
+
+ // Helper to copy metadata buffer from the Target to the host.
+ void CopyRemoteMetaData(TargetBuffer buffer, CoTaskMemHolder<VOID> * pLocalBuffer);
+
+
+ CordbAssembly * ResolveAssemblyInternal(mdToken tkAssemblyRef);
+
+ BOOL IsWinMD();
+
+ //-----------------------------------------------------------
+ // Convenience routines
+ //-----------------------------------------------------------
+
+public:
+ CordbAppDomain *GetAppDomain()
+ {
+ return m_pAppDomain;
+ }
+
+ CordbAssembly * GetCordbAssembly ();
+
+ // Get the module filename, or NULL if none. Throws on error.
+ const WCHAR * GetModulePath();
+
+ const WCHAR * GetNGenImagePath();
+
+ const VMPTR_DomainFile GetRuntimeDomainFile ()
+ {
+ return m_vmDomainFile;
+ }
+
+ const VMPTR_Module GetRuntimeModule()
+ {
+ return m_vmModule;
+ }
+
+ // Get symbol stream for in-memory modules.
+ IDacDbiInterface::SymbolFormat GetInMemorySymbolStream(IStream ** ppStream);
+
+ // accessor for PE file
+ VMPTR_PEFile GetPEFile();
+
+
+ IMetaDataImport * GetMetaDataImporter();
+
+ // accessor for Internal MetaData importer.
+ IMDInternalImport * GetInternalMD();
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+public:
+ CordbAssembly* m_pAssembly;
+ CordbAppDomain* m_pAppDomain;
+ CordbSafeHashTable<CordbClass> m_classes;
+
+ // A collection, indexed by methodDef, of the latest version of functions in this module
+ // The collection is filled lazily by LookupOrCreateFunction
+ CordbSafeHashTable<CordbFunction> m_functions;
+
+ // The real handle into the VM for a module. This is appdomain aware.
+ // This is the primary VM counterpart for the CordbModule.
+ VMPTR_DomainFile m_vmDomainFile;
+
+ VMPTR_Module m_vmModule;
+
+ DWORD m_EnCCount;
+
+private:
+
+ enum ILWinMDState
+ {
+ Uninitialized,
+ False,
+ True
+ };
+
+ // Base Address and size of this module in debuggee's process. Maybe null if unknown.
+ TargetBuffer m_PEBuffer;
+
+ BOOL m_fDynamic; // Dynamic modules can grow (like Reflection Emit)
+ BOOL m_fInMemory; // In memory modules don't have file-backing.
+ ILWinMDState m_isIlWinMD; // WinMD modules don't support all metadata interfaces
+
+ // Indicates that the module must serialize its metadata in process as part of metadata
+ // refresh. This is required for modules updated on the fly by the profiler
+ BOOL m_fForceMetaDataSerialize;
+
+ // Full path to module's image, if any. Empty if none, NULL if not yet set.
+ StringCopyHolder m_strModulePath;
+
+ // Full path to the ngen file. Empty if not ngenned, NULL if not yet set.
+ // This isn't exposed publicly, but we may use it internally for loading metadata.
+ StringCopyHolder m_strNGenImagePath;
+
+ // "Global" class for this module. Global functions + vars exist in this class.
+ RSSmartPtr<CordbClass> m_pClass;
+
+ // Handle to PEFile, useful for metadata lookups.
+ // this should always be non-null.
+ VMPTR_PEFile m_vmPEFile;
+
+
+ // Public metadata importer. This is lazily initialized and accessed from code:GetMetaDataImporter
+ // This is handed out to debugger clients via code:CordbModule::GetMetaDataInterface
+ // This is also tightly coupled to the internal metadata importer, m_pInternalMetaDataImport.
+ RSExtSmartPtr<IMetaDataImport> m_pIMImport;
+
+ // Internal metadata object. This is closely tied to the public metadata object (m_pIMImport).
+ // They share the same backing storage, but expose different interfaces to that storage.
+ // Debugger authors and tools use the public interfaces.
+ // DAC-ized operations in the VM require an IMDInternalImport.
+ // The public and internal must be updated together.
+ // This ultimately gets handed back to DAC via code:CordbProcess::LookupMetaData
+ RSExtSmartPtr<IMDInternalImport> m_pInternalMetaDataImport;
+
+ // Continue counter of when the module was loaded.
+ // See code:CordbModule::SetLoadEventContinueMarker for details
+ UINT m_nLoadEventContinueCounter;
+
+ // This is a table of all NativeCode objects in the module indexed
+ // by start address
+ // The collection is filled lazily by LookupOrCreateNativeCode
+ CordbSafeHashTable<CordbNativeCode> m_nativeCodeTable;
+};
+
+
+//-----------------------------------------------------------------------------
+// Cordb MDA notification
+//-----------------------------------------------------------------------------
+class CordbMDA : public CordbBase, public ICorDebugMDA
+{
+public:
+ CordbMDA(CordbProcess * pProc, DebuggerMDANotification * pData);
+ ~CordbMDA();
+
+ virtual void Neuter();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbMDA"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRefEnforceExternal());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseReleaseEnforceExternal());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugMDA
+ //-----------------------------------------------------------
+
+ // Get the string for the type of the MDA. Never empty.
+ // This is a convenient performant alternative to getting the XML stream and extracting
+ // the type from that based off the schema.
+ COM_METHOD GetName(ULONG32 cchName, ULONG32 * pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+
+ // Get a string description of the MDA. This may be empty (0-length).
+ COM_METHOD GetDescription(ULONG32 cchName, ULONG32 * pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+
+ // Get the full associated XML for the MDA. This may be empty.
+ // This could be a potentially expensive operation if the xml stream is large.
+ // See the MDA documentation for the schema for this XML stream.
+ COM_METHOD GetXML(ULONG32 cchName, ULONG32 * pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+
+ COM_METHOD GetFlags(CorDebugMDAFlags * pFlags);
+
+ // Thread that the MDA is fired on. We use the os tid instead of an ICDThread in case an MDA is fired on a
+ // native thread (or a managed thread that hasn't yet entered managed code and so we don't have a ICDThread
+ // object for it yet)
+ COM_METHOD GetOSThreadId(DWORD * pOsTid);
+
+private:
+ NewArrayHolder<WCHAR> m_szName;
+ NewArrayHolder<WCHAR> m_szDescription;
+ NewArrayHolder<WCHAR> m_szXml;
+
+ DWORD m_dwOSTID;
+ CorDebugMDAFlags m_flags;
+};
+
+
+
+struct CordbHangingField
+{
+ FREEHASHENTRY entry;
+ FieldData data;
+};
+
+// A hashtable for storing EnC hanging field information
+// FieldData.m_fldMetadataToken is the key
+class CordbHangingFieldTable : public CHashTableAndData<CNewDataNoThrow>
+{
+ private:
+
+ BOOL Cmp(SIZE_T k1, const HASHENTRY *pc2)
+ {
+ LIMITED_METHOD_CONTRACT;
+ return (ULONG)(UINT_PTR)(k1) !=
+ (reinterpret_cast<const CordbHangingField *>(pc2))->data.m_fldMetadataToken;
+ }
+
+ ULONG HASH(mdFieldDef fldToken)
+ {
+ LIMITED_METHOD_CONTRACT;
+ return fldToken;
+ }
+
+ SIZE_T KEY(mdFieldDef fldToken)
+ {
+ return (SIZE_T)fldToken;
+ }
+
+ public:
+
+#ifndef DACCESS_COMPILE
+
+ CordbHangingFieldTable() : CHashTableAndData<CNewDataNoThrow>(11)
+ {
+ NewInit(11, sizeof(CordbHangingField), 11);
+ }
+
+ FieldData * AddFieldInfo(FieldData * pInfo)
+ {
+ _ASSERTE(pInfo != NULL);
+
+ CordbHangingField *pEntry = (CordbHangingField *)Add(HASH(pInfo->m_fldMetadataToken));
+ pEntry->data = *pInfo; // copy everything over
+
+ // Return a pointer to the data
+ return &(pEntry->data);
+ }
+
+ void RemoveFieldInfo(mdFieldDef fldToken)
+ {
+ CordbHangingField *entry = (CordbHangingField*)Find(HASH(fldToken), KEY(fldToken));
+ _ASSERTE(entry != NULL);
+ Delete(HASH(fldToken), (HASHENTRY*)entry);
+ }
+
+#endif // #ifndef DACCESS_COMPILE
+
+ FieldData * GetFieldInfo(mdFieldDef fldToken)
+ {
+ CordbHangingField * entry = (CordbHangingField *)Find(HASH(fldToken), KEY(fldToken));
+ return (entry!=NULL?&(entry->data):NULL);
+ }
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Instantiation.
+ *
+ * This struct stores a set of type parameters. It is used in
+ * the heap-allocated data structures CordbType and CordbNativeCode.
+ *
+ * CordbType::m_inst. Stores the class type parameters if any,
+ * or the solitary array type parameter, or the solitary parameter
+ * to a byref type.
+ *
+ * CordbJITILFrame::m_genericArgs. Stores exact generic parameters for the generic method frame if available
+ * Need not be identicial if code is shared between generic instantiations.
+ * May be inexact if real instantiation has been optimized away off
+ * the frame (nb this gets reported by the left side)
+ *
+ * This is conceptually an array of Type-parameters, with the split (m_cClassTyPars) between
+ * where the Type's type-parameters end and the Method's type-parameters begin.
+ * ------------------------------------------------------------------------- */
+class Instantiation
+{
+public:
+ // Empty ctor
+ Instantiation()
+ : m_cInst(0), m_ppInst(NULL), m_cClassTyPars (0)
+ { }
+
+ // Instantiation for Type. 0 Method type-parameters.
+ Instantiation(unsigned int _cClassInst, CordbType **_ppClassInst)
+ : m_cInst(_cClassInst), m_ppInst(_ppClassInst), m_cClassTyPars(_cClassInst)
+ {LIMITED_METHOD_CONTRACT; }
+
+ // Instantiation for Type + Function.
+ Instantiation(unsigned int _cInst, CordbType **_ppInst, unsigned int numClassTyPars)
+ : m_cInst(_cInst), m_ppInst(_ppInst),
+ m_cClassTyPars (numClassTyPars)
+ { }
+
+ // Copy constructor.
+ Instantiation(const Instantiation &inst)
+ : m_cInst(inst.m_cInst), m_ppInst(inst.m_ppInst), m_cClassTyPars (inst.m_cClassTyPars)
+ { }
+
+ // Number of elements in array pointed to by m_ppInst
+ unsigned int m_cInst;
+
+ // Pointer to array of CordbType objects. Length of array is m_cInst.
+ // Array is Class Type parameters followed by Function's Type parameters.
+ // Eg, Instantiation for Class<Foo, Goo>::Func<Bar> would be {Foo, Goo, Bar}.
+ // m_cInst = 3, m_cClassTyPars = 2.
+ // In contrast, Instantiation for Class::Func<Foo, Goo, Bar> would have same
+ // array, but m_cClassTyPars = 0.
+ CordbType **m_ppInst;
+
+ // Track the split between Type vs. Method type-params.
+ unsigned int m_cClassTyPars;
+};
+
+//------------------------------------------------------------------------
+// CordbType: replaces the use of signatures.
+//
+// Left Side & Right Side
+// ---------------------------
+// CordbTypes may come from either the Right Side (via being built up from
+// ICorDebug), or from the Left-Side (being handed back from LS operations
+// like getting the type from an Object the LS handed back).
+// The RightSide CordbType corresponds to a Left-Side TypeHandle.
+// CordbTypes are communicated across the LS/RS boundary by marshalling
+// to BasicTypeData + ExpandedTypeData IPC events.
+//
+//
+// Invariants on CordbType
+// ---------------------------
+//
+// The m_elementType is NEVER ELEMENT_TYPE_VAR or ELEMENT_TYPE_MVAR or ELEMENT_TYPE_GENERICINST
+// CordbTypes are always _ground_ types (fully instantiated generics or non-generic types). If
+// they represent an instantiated type like List<int> then m_inst will be non-empty.
+//
+//
+// !!!! The m_elementType is NEVER ELEMENT_TYPE_VALUETYPE !!!!
+// !!!! To find out if it is a value type call CordbType::IsValueType() !!!!
+//
+// Where CordbTypes are stored
+// ---------------------------
+//
+// Because we could have a significant number of different instantiations for a given templated type,
+// we need an efficient way to store and retrieve the CordbType instances for these instantiations.
+// For this reason, we use a tree-like scheme to hash-cons types. To implement this we use the following
+// scheme:
+// - CordbTypes are created for "partially instantiated" types,
+// e.g. CordbTypes exist for "Dict" and "Dict<int>" even if the real
+// type being manipulated by the user is "Dict<int,string>"
+// - Subordinate types (E.g. Dict<int,string> is subordinate to Dict<int>,
+// which is itself subordinate to the type for Dict) get stored
+// in the m_spinetypes hash table of the parent type.
+// - In m_spinetypes the pointers of the CordbTypes themselves
+// are used for the unique ids for entries in the table.
+// Note that CordbType instances that are created for "partially instantiated" types
+// are never used for any purpose other than efficient hashing. Specifically, the debugger will
+// never have reason to expose a partially instantiated type outside of the hashing algorithm.
+//
+// CordbTypes have object identity: if 2 CordbTypes represent the same type (in the same AppDomain),
+// then they will be the same CordbType instance.
+//
+// Thus the representation for "Dict<class String,class Foo, class Foo* >" goes as follows:
+// 1. Assume the type Foo is represented by CordbClass *5678x
+// 1b. Assume the hashtable m_sharedtypes in the AppDomain maps E_T_STRING to the CordbType *0ABCx
+// Assume m_type in class Foo (i.e. CordbClass *5678x) is the CordbType *0DEFx
+// Assume m_type in class Foo maps E_T_PTR to the CordbType *0647x
+// 2. The hash table m_spinetypes in "Dict" maps "0ABCx" to a new CordbType
+// representing Dict<String> (a single type application)
+// 3. The hash table m_spinetypes in this new CordbType maps "0DEFx" to a
+// new CordbType representing Dict<class String,class Foo>
+// 3. The hash table m_spinetypes in this new CordbType maps "0647" to a
+// new CordbType representing Dict<class String,class Foo, class Foo*>
+//
+// This lets us reuse the existing hash table scheme to build
+// up instantiated types of arbitrary size.
+//
+// Array types are similar, excpet that they start with a head type
+// for the "type constructor", e.g. "_ []" is a type constructor with rank 1
+// and m_elementType = ELEMENT_TYPE_SZARRAY. These head constructors are
+// stored in the m_sharedtypes table in the appdomain. The actual instantiations
+// of the array types are then subordinate types to the array constructor type.
+//
+// Other types are simpler, and have unique objects stored in the m_sharedtypes
+// table in the appdomain. This table is indexed by CORDBTYPE_ID in RsType.cpp
+//
+//
+// Memory Management of CordbTypes
+// ---------------------------
+// All CordbTypes are ultimately stored off the CordbAppDomain object.
+// The most common place is in the AppDomain's neuter-list.
+//
+// See definition of ICorDebugType for further invariants on types.
+//
+
+class CordbType : public CordbBase, public ICorDebugType
+{
+public:
+ CordbType(CordbAppDomain *appdomain, CorElementType ty, unsigned int rank);
+ CordbType(CordbAppDomain *appdomain, CorElementType ty, CordbClass *c);
+ CordbType(CordbType *tycon, CordbType *tyarg);
+ virtual ~CordbType();
+ virtual void Neuter();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbType"; }
+#endif
+
+ // If you want to force the init to happen even if we think the class
+ // is up to date, set fForceInit to TRUE
+ HRESULT Init(BOOL fForceInit);
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef();
+ ULONG STDMETHODCALLTYPE Release();
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugType
+ //-----------------------------------------------------------
+
+ COM_METHOD GetType(CorElementType *ty);
+ COM_METHOD GetClass(ICorDebugClass **ppClass);
+ COM_METHOD EnumerateTypeParameters(ICorDebugTypeEnum **ppTyParEnum);
+ COM_METHOD GetFirstTypeParameter(ICorDebugType **ppType);
+ COM_METHOD GetBase(ICorDebugType **ppType);
+ COM_METHOD GetStaticFieldValue(mdFieldDef fieldDef,
+ ICorDebugFrame * pFrame,
+ ICorDebugValue ** ppValue);
+ COM_METHOD GetRank(ULONG32 *pnRank);
+
+ //-----------------------------------------------------------
+ // Non-COM members
+ //-----------------------------------------------------------
+
+ //-----------------------------------------------------------
+ // Basic constructor operations for the algebra of types.
+ // These all create unique objects within an AppDomain.
+ //-----------------------------------------------------------
+
+ // This one is used to create simple types, e.g. int32, int64, typedbyref etc.
+ static HRESULT MkType(CordbAppDomain * pAppDomain,
+ CorElementType elementType,
+ CordbType ** ppResultType);
+
+ // This one is used to create array, pointer and byref types
+ static HRESULT MkType(CordbAppDomain * pAppDomain,
+ CorElementType elementType,
+ ULONG rank,
+ CordbType * pType,
+ CordbType ** ppResultType);
+
+ // This one is used to create function pointer types. et must be ELEMENT_TYPE_FNPTR
+ static HRESULT MkType(CordbAppDomain * pAppDomain,
+ CorElementType elementType,
+ const Instantiation * pInst,
+ CordbType ** ppResultType);
+
+ // This one is used to class and value class types, e.g. "class MyClass" or "class ArrayList<int>"
+ static HRESULT MkType(CordbAppDomain * pAppDomain,
+ CorElementType elementType,
+ CordbClass * pClass,
+ const Instantiation * pInst,
+ CordbType ** ppResultType);
+
+ // Some derived constructors... Use this one if the type is definitely not
+ // a paramterized type, e.g. to implement functions on the API where types cannot
+ // be parameterized.
+ static HRESULT MkUnparameterizedType(CordbAppDomain *appdomain, CorElementType et, CordbClass *cl, CordbType **ppType);
+
+ //-----------------------------------------------------------
+ // Basic destructor operations over the algebra
+ //-----------------------------------------------------------
+ void DestUnaryType(CordbType **pRes) ;
+ void DestConstructedType(CordbClass **pClass, Instantiation *pInst);
+ void DestNaryType(Instantiation *pInst);
+
+ CorElementType GetElementType() { return m_elementType; }
+ VMPTR_DomainFile GetDomainFile();
+ VMPTR_Module GetModule();
+
+ // If this is a ptr type, get the CordbType that it points to.
+ // Eg, for CordbType("Int*"), returns CordbType("Int").
+ // If not a ptr type, returns null.
+ // Since it's all internal, no reference counting.
+ // This is effectively a specialized version of DestUnaryType.
+ CordbType * GetPointerElementType();
+
+
+ // Create a type from metadata
+ static HRESULT SigToType(CordbModule * pModule, SigParser * pSigParser, const Instantiation * pInst, CordbType ** ppResultType);
+
+ // Create a type from from the data received from the left-side
+ static HRESULT TypeDataToType(CordbAppDomain *appdomain, DebuggerIPCE_ExpandedTypeData *data, CordbType **pRes);
+ static HRESULT TypeDataToType(CordbAppDomain *appdomain, DebuggerIPCE_BasicTypeData *data, CordbType **pRes);
+ static HRESULT InstantiateFromTypeHandle(CordbAppDomain * appdomain,
+ VMPTR_TypeHandle vmTypeHandle,
+ CorElementType et,
+ CordbClass * tycon,
+ CordbType ** pRes);
+
+ // Prepare data to send back to left-side during Init() and FuncEval. Fail if the the exact
+ // type data is requested but was not fetched correctly during Init()
+ HRESULT TypeToBasicTypeData(DebuggerIPCE_BasicTypeData *data);
+ void TypeToExpandedTypeData(DebuggerIPCE_ExpandedTypeData *data);
+ void TypeToTypeArgData(DebuggerIPCE_TypeArgData *data);
+
+ void CountTypeDataNodes(unsigned int *count);
+ static void CountTypeDataNodesForInstantiation(unsigned int genericArgsCount, ICorDebugType *genericArgs[], unsigned int *count);
+ static void GatherTypeData(CordbType *type, DebuggerIPCE_TypeArgData **curr_tyargData);
+ static void GatherTypeDataForInstantiation(unsigned int genericArgsCount, ICorDebugType *genericArgs[], DebuggerIPCE_TypeArgData **curr_tyargData);
+
+ HRESULT GetParentType(CordbClass * baseClass, CordbType ** ppRes);
+
+ // These are available after Init() has been called....
+ HRESULT GetUnboxedObjectSize(ULONG32 *res);
+ HRESULT GetFieldInfo(mdFieldDef fldToken, FieldData ** ppFieldData);
+
+ CordbAppDomain *GetAppDomain() { return m_appdomain; }
+
+ bool IsValueType();
+
+ // Is this type a GC-root.
+ bool IsGCRoot();
+
+#ifdef FEATURE_64BIT_ALIGNMENT
+ // checks if the type requires 8-byte alignment.
+ // this is not exposed via ICorDebug at present.
+ HRESULT CordbType::RequiresAlign8(BOOL* isRequired);
+#endif
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+public:
+ // Internal representation of the element type. This may not map exactly to the public element type.
+ // Specifically, m_elementType is NEVER:
+ // ELEMENT_TYPE_VAR, ELEMENT_TYPE_MVAR, ELEMENT_TYPE_GENERICINST,
+ // or ELEMENT_TYPE_VALUETYPE.
+ // To find out if this CordbType corresponds to a value type (instead of Reference type) call CordbType::IsValueType()
+ CorElementType m_elementType;
+
+ // The appdomain that this type lives in. Types (and their type-parameters) are all contained in a single appdomain.
+ // (alhtough the types may be from different modules).
+ // This is valid for all CordbType objects, regardless of m_elementType;
+ CordbAppDomain * m_appdomain;
+
+ // The matching class for this type.
+ // Initially only set for E_T_CLASS, lazily computed for E_T_STRING and E_T_OBJECT if needed
+ CordbClass * m_pClass;
+
+ ULONG m_rank; // Only set for E_T_ARRAY etc.
+
+ // Array of Type Parameters for this Type.
+ Instantiation m_inst;
+
+ // A unique mapping from CordbType objects that are type parameters to CordbType objects. Each mapping
+ // represents the use of the containing type as type constructor. e.g. If the containing type
+ // is CordbType(CordbClass "List") then the table here will map parameters such as (CordbType(CordbClass "String")) to
+ // the constructed type CordbType(CordbClass "List", <CordbType(CordbClass "String")>)
+ // @dbgtodo synchronization - this is currently protected by the Stop-Go lock. Transition to process-lock.
+ CordbSafeHashTable<CordbType> m_spinetypes;
+
+ // Valid after Init(), only for E_T_ARRAY etc.and E_T_CLASS when m_pClass->m_classInfo.m_genericArgsCount > 0.
+ // m_typeHandleExact is the precise Runtime type handle for this type.
+ VMPTR_TypeHandle m_typeHandleExact;
+
+ // Valid after Init(), only for E_T_CLASS, and when m_pClass->m_classInfo.m_genericArgsCount > 0.
+ // May not be set correctly if m_fieldInfoNeedsInit.
+ SIZE_T m_objectSize;
+
+ // DON'T KEEP POINTERS TO ELEMENTS OF m_pFields AROUND!!
+ // This may be deleted if the class gets EnC'd.
+ //
+ // Valid after Init(), only for E_T_CLASS, and when m_pClass->m_classInfo.m_genericArgsCount > 0
+ // All fields will be valid if we have m_typeHandleExact.
+ //
+ // Only some fields will be valid if we have called Init() but still have m_fieldInfoNeedsInit.
+ DacDbiArrayList<FieldData> m_fieldList;
+
+ HRESULT ReturnedByValue();
+
+private:
+ static HRESULT MkTyAppType(CordbAppDomain * pAddDomain,
+ CordbType * pType,
+ const Instantiation * pInst,
+ CordbType ** pResultType);
+
+ BOOL m_fieldInfoNeedsInit;
+
+private:
+ HRESULT InitInstantiationTypeHandle(BOOL fForceInit);
+ HRESULT InitInstantiationFieldInfo(BOOL fForceInit);
+ HRESULT InitStringOrObjectClass(BOOL fForceInit);
+};
+
+/* ------------------------------------------------------------------------- *
+ * Class class
+ * ------------------------------------------------------------------------- */
+
+class CordbClass : public CordbBase, public ICorDebugClass, public ICorDebugClass2
+{
+public:
+ CordbClass(CordbModule* m, mdTypeDef token);
+ virtual ~CordbClass();
+ virtual void Neuter();
+
+ using CordbBase::GetProcess;
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbClass"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugClass
+ //-----------------------------------------------------------
+
+ COM_METHOD GetStaticFieldValue(mdFieldDef fieldDef,
+ ICorDebugFrame *pFrame,
+ ICorDebugValue **ppValue);
+ COM_METHOD GetModule(ICorDebugModule **pModule);
+ COM_METHOD GetToken(mdTypeDef *pTypeDef);
+ //-----------------------------------------------------------
+ // ICorDebugClass2
+ //-----------------------------------------------------------
+ COM_METHOD GetParameterizedType(CorElementType elementType,
+ ULONG32 cTypeArgs,
+ ICorDebugType * rgpTypeArgs[],
+ ICorDebugType ** ppType);
+
+ COM_METHOD SetJMCStatus(BOOL fIsUserCode);
+
+ //-----------------------------------------------------------
+ // Convenience routines and Accessors
+ //-----------------------------------------------------------
+
+ // Helper to get containing module
+ CordbModule * GetModule()
+ {
+ return m_pModule;
+ }
+
+ // get the metadata token for this class
+ mdTypeDef GetToken() { return m_token; }
+
+ // Helper to get the AppDomain the class lives in.
+ CordbAppDomain * GetAppDomain()
+ {
+ return m_pModule->GetAppDomain();
+ }
+
+ // This only very roughly resembles the CLASS_LOAD_LEVEL concept in the VM.
+ // because DBI's needs are far more coarse grained. Also DBI
+ // may contain more, equal, or less information than what is available in
+ // native runtime data structures. We can have less when we are being lazy
+ // and haven't yet fetched it. We can have more if use an independent data
+ // source such as the metadata blob and then compute some type data ourselves
+ typedef enum
+ {
+ // At this state the constructor has been run.
+ // m_module and m_token will be valid
+ Constructed,
+
+ // At this state we have additionally certain to have initialized
+ // m_fIsValueClass and m_fHasTypeParams
+ // Calls to IsValueClass() and HasTypeParams() are valid
+ // This stage should be achievable as long as a runtime type handle
+ // exists, even if it is unrestored
+ BasicInfo,
+
+ //Everything is loaded, or at least anything created lazily from this
+ //point on should be certain to succeed (ie m_type)
+ FullInfo
+ }
+ ClassLoadLevel;
+
+ ClassLoadLevel GetLoadLevel()
+ {
+ return m_loadLevel;
+ }
+
+ // determine if a load event has been sent for this class
+ BOOL LoadEventSent() { return m_fLoadEventSent; }
+
+ // set value of m_fLoadEventSent
+ void SetLoadEventSent(BOOL fEventSent) { m_fLoadEventSent = fEventSent; }
+
+ // determine if the class has been unloaded
+ BOOL HasBeenUnloaded() { return m_fHasBeenUnloaded; }
+
+ // set value of m_fHasBeenUnloaded
+ void SetHasBeenUnloaded(BOOL fUnloaded) { m_fHasBeenUnloaded = (fUnloaded == TRUE); }
+
+ // determine if this is a value class
+ BOOL IsValueClassNoInit() { return m_fIsValueClass; }
+
+ // set value of m_fIsValueClass
+ void SetIsValueClass(BOOL fIsValueClass) { m_fIsValueClass = (fIsValueClass == TRUE); }
+
+ // determine if the value class is known
+ BOOL IsValueClassKnown() { return m_fIsValueClassKnown; }
+
+ // set value of m_fIsValueClassKnown
+ void SetIsValueClassKnown(BOOL fIsValueClassKnown) { m_fIsValueClassKnown = (fIsValueClassKnown == TRUE); }
+
+ // get value of m_type
+ CordbType * GetType() { return m_type; }
+
+ void SetType(CordbType * pType) { m_type.Assign(pType); }
+
+ // get the type parameter count
+ bool HasTypeParams() { _ASSERTE(m_loadLevel >= BasicInfo); return m_fHasTypeParams; }
+
+ // get the object size
+ SIZE_T ObjectSize() { return m_classInfo.m_objectSize; }
+
+ // get the metadata token for this class
+ mdTypeDef MDToken() { return m_token; }
+
+ // get the number of fields
+ unsigned int FieldCount() { return m_classInfo.m_fieldList.Count(); }
+
+ //-----------------------------------------------------------
+ // Functionality shared for CordbType and CordbClass
+ //-----------------------------------------------------------
+
+ static HRESULT SearchFieldInfo(CordbModule * module,
+ DacDbiArrayList<FieldData> * pFieldList,
+ mdTypeDef classToken,
+ mdFieldDef fldToken,
+ FieldData ** ppFieldData);
+
+ static HRESULT GetStaticFieldValue2(CordbModule * pModule,
+ FieldData * pFieldData,
+ BOOL fEnCHangingField,
+ const Instantiation * pInst,
+ ICorDebugFrame * pFrame,
+ ICorDebugValue ** ppValue);
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+
+ // Get information about a field that was added by EnC
+ HRESULT GetEnCHangingField(mdFieldDef fldToken,
+ FieldData ** ppFieldData,
+ CordbObjectValue * pObject);
+
+private:
+ // Get information via the DAC about a field added with Edit and Continue.
+ FieldData * GetEnCFieldFromDac(BOOL fStatic,
+ CordbObjectValue * pObject,
+ mdFieldDef fieldToken);
+
+ // Initialize an instance of EnCHangingFieldInfo.
+ void InitEnCFieldInfo(EnCHangingFieldInfo * pEncField,
+ BOOL fStatic,
+ CordbObjectValue * pObject,
+ mdFieldDef fieldToken,
+ mdTypeDef classToken);
+
+
+public:
+
+ // set or clear the custom notifications flag to control whether we ignore custom debugger notifications
+ void SetCustomNotifications(BOOL fEnable) { m_fCustomNotificationsEnabled = fEnable; }
+ BOOL CustomNotificationsEnabled () { return m_fCustomNotificationsEnabled; }
+
+ HRESULT GetFieldInfo(mdFieldDef fldToken, FieldData ** ppFieldData);
+
+ // If you want to force the init to happen even if we think the class
+ // is up to date, set fForceInit to TRUE
+ void Init(ClassLoadLevel desiredLoadLevel = FullInfo);
+
+ // determine if any fields for a type are unallocated statics
+ BOOL GotUnallocatedStatic(DacDbiArrayList<FieldData> * pFieldList);
+
+ bool IsValueClass();
+ HRESULT GetThisType(const Instantiation * pInst, CordbType ** ppResultType);
+ static HRESULT PostProcessUnavailableHRESULT(HRESULT hr,
+ IMetaDataImport *pImport,
+ mdFieldDef fieldDef);
+ mdTypeDef GetTypeDef() { return (mdTypeDef)m_id; }
+
+#ifdef EnC_SUPPORTED
+ // when we get an added field or method, mark the class to force re-init when we access it
+ void MakeOld()
+ {
+ m_loadLevel = Constructed;
+ }
+#endif // EnC_SUPPORTED
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+private:
+ // contains information about the type: size and
+ // field information
+ ClassInfo m_classInfo;
+
+ ClassLoadLevel m_loadLevel;
+
+ // @dbgtodo managed pipeline - can we get rid of both of these fields?
+ BOOL m_fLoadEventSent;
+ bool m_fHasBeenUnloaded;
+
+ // [m_type] is the type object for when this class is used
+ // as a type. If the class is a value class then it can represent
+ // either the boxed or unboxed type - it depends on the context where the
+ // type is used. For example on a CordbBoxValue it represents the type of the
+ // boxed VC, on a CordbVCObjectValue it represents the type of the unboxed VC.
+ //
+ // The type field starts of NULL as there
+ // is no need to create the type object until it is needed.
+ RSSmartPtr<CordbType> m_type;
+
+ // Module that this Class lives in. Valid at the Constructed type level.
+ CordbModule * m_pModule;
+
+ // the token for the type constructor - m_id cannot be used for constructed types
+ // valid at the Constructed type level
+ mdTypeDef m_token;
+
+ // Whether the class is a VC or not is discovered either by
+ // seeing the class used in a signature after ELEMENT_TYPE_VALUETYPE
+ // or ELEMENT_TYPE_CLASS or by going and asking the EE.
+ bool m_fIsValueClassKnown;
+
+ // Whether the class is a VC or not
+ bool m_fIsValueClass;
+
+ // Whether the class has generic type parameters in its definition
+ bool m_fHasTypeParams;
+
+ // Timestamp from GetProcess()->m_continueCounter, which we can use to tell if
+ // the process has been continued since we last took a snapshot.
+ UINT m_continueCounterLastSync;
+
+ // if we add static fields with EnC after this class is loaded (in the debuggee),
+ // their value will be hung off the FieldDesc. Hold information about such fields here.
+ CordbHangingFieldTable m_hangingFieldsStatic;
+
+ // this indicates whether we should send custom debugger notifications
+ BOOL m_fCustomNotificationsEnabled;
+
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * TypeParameter enumerator class
+ * ------------------------------------------------------------------------- */
+
+class CordbTypeEnum : public CordbBase, public ICorDebugTypeEnum
+{
+public:
+ // Factory method: Create a new instance of this class. Returns NULL on out-of-memory.
+ // On success, returns a new initialized instance of CordbTypeEnum with ref-count 0 (just like a ctor).
+ // the life expectancy of the enumerator varies by caller so we require them to specify the applicable neuter list here.
+ static CordbTypeEnum* Build(CordbAppDomain * pAppDomain, NeuterList * pNeuterList, unsigned int cTypars, CordbType **ppTypars);
+ static CordbTypeEnum* Build(CordbAppDomain * pAppDomain, NeuterList * pNeuterList, unsigned int cTypars, RSSmartPtr<CordbType>*ppTypars);
+
+ virtual ~CordbTypeEnum() ;
+
+ virtual void Neuter();
+
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbTypeEnum"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Skip(ULONG celt);
+ COM_METHOD Reset();
+ COM_METHOD Clone(ICorDebugEnum **ppEnum);
+ COM_METHOD GetCount(ULONG *pcelt);
+
+ //-----------------------------------------------------------
+ // ICorDebugTypeEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Next(ULONG celt, ICorDebugType *Types[], ULONG *pceltFetched);
+
+private:
+ // Private constructor, only partially initializes the object.
+ // Clients should use the 'Build' factory method to create an instance of this class.
+ CordbTypeEnum( CordbAppDomain * pAppDomain, NeuterList * pNeuterList );
+ template<class T> static CordbTypeEnum* BuildImpl(CordbAppDomain * pAppDomain, NeuterList * pNeuterList, unsigned int cTypars, T* ppTypars );
+
+ // Owning object.
+ CordbAppDomain * m_pAppDomain;
+
+ // Array of Types. We own the array, and share refs to the types.
+ // @todo- since these are guaranteed to be kept alive as long as we're not neutered,
+ // we don't need to keep refs to them.
+ RSSmartPtr<CordbType> * m_ppTypars;
+ UINT m_iCurrent;
+ UINT m_iMax;
+};
+
+/* ------------------------------------------------------------------------- *
+ * Code enumerator class
+ * ------------------------------------------------------------------------- */
+
+class CordbCodeEnum : public CordbBase, public ICorDebugCodeEnum
+{
+public:
+ CordbCodeEnum(unsigned int cCode, RSSmartPtr<CordbCode> * ppCode);
+ virtual ~CordbCodeEnum() ;
+
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbCodeEnum"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Skip(ULONG celt);
+ COM_METHOD Reset();
+ COM_METHOD Clone(ICorDebugEnum **ppEnum);
+ COM_METHOD GetCount(ULONG *pcelt);
+
+ //-----------------------------------------------------------
+ // ICorDebugCodeEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Next(ULONG celt, ICorDebugCode *Codes[], ULONG *pceltFetched);
+
+private:
+ // Ptr to an array of CordbCode*
+ // We own the array.
+ RSSmartPtr<CordbCode> * m_ppCodes;
+ UINT m_iCurrent;
+ UINT m_iMax;
+};
+
+
+
+
+
+typedef CUnorderedArray<CordbCode*,11> UnorderedCodeArray;
+//<TODO>@todo port: different SIZE_T size/</TODO>
+const int DMI_VERSION_INVALID = 0;
+const int DMI_VERSION_MOST_RECENTLY_JITTED = 1;
+const int DMI_VERSION_MOST_RECENTLY_EnCED = 2;
+
+
+/* ------------------------------------------------------------------------- *
+ * Function class
+ *
+ * @review . The CordbFunction class now keeps a multiple MethodDescInfo
+ * structures in a hash table indexed by tokens provided by the left-side.
+ * In 99.9% of cases this hash table will only contain one entry - we only
+ * use a hashtable to cover the case where we have multiple JITtings of
+ * a single version of a function, in particular multiple JITtings of generic
+ * code under different instantiations. This will increase space usage.
+ * The way around it is to store one CordbNativeCode in-line in the CordbFunction
+ * class, or at least store one such pointer so no hash table will normally
+ * be needed. This is similar to other cases, e.g. the hash table in
+ * CordbClass used to indicate different CordbTypes made from that class -
+ * again in the normal case these tables will only contain one element.
+ *
+ * However, for the moment I've focused on correctness and we can minimize
+ * this space usage in due course.
+ * ------------------------------------------------------------------------- */
+
+const BOOL bNativeCode = FALSE;
+const BOOL bILCode = TRUE;
+
+//
+// Each E&C version gets its own function object. So the IL that a function
+// is associated w/ does not change.
+// B/C of generics, a single IL function may get jitted multiple times and
+// be associated w/ multiple native code blobs (CordbNativeCode).
+//
+class CordbFunction : public CordbBase, public ICorDebugFunction, public ICorDebugFunction2, public ICorDebugFunction3
+{
+public:
+ //-----------------------------------------------------------
+ // Create from scope and member objects.
+ //-----------------------------------------------------------
+ CordbFunction(CordbModule * m,
+ mdMethodDef token,
+ SIZE_T enCVersion);
+ virtual ~CordbFunction();
+ virtual void Neuter();
+
+
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbFunction"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugFunction
+ //-----------------------------------------------------------
+ COM_METHOD GetModule(ICorDebugModule **pModule);
+ COM_METHOD GetClass(ICorDebugClass **ppClass);
+ COM_METHOD GetToken(mdMethodDef *pMemberDef);
+ COM_METHOD GetILCode(ICorDebugCode **ppCode);
+ COM_METHOD GetNativeCode(ICorDebugCode **ppCode);
+ COM_METHOD CreateBreakpoint(ICorDebugFunctionBreakpoint **ppBreakpoint);
+ COM_METHOD GetLocalVarSigToken(mdSignature *pmdSig);
+ COM_METHOD GetCurrentVersionNumber(ULONG32 *pnCurrentVersion);
+
+ //-----------------------------------------------------------
+ // ICorDebugFunction2
+ //-----------------------------------------------------------
+ COM_METHOD SetJMCStatus(BOOL fIsUserCode);
+ COM_METHOD GetJMCStatus(BOOL * pfIsUserCode);
+ COM_METHOD EnumerateNativeCode(ICorDebugCodeEnum **ppCodeEnum) { return E_NOTIMPL; }
+ COM_METHOD GetVersionNumber(ULONG32 *pnCurrentVersion);
+
+ //-----------------------------------------------------------
+ // ICorDebugFunction3
+ //-----------------------------------------------------------
+ COM_METHOD GetActiveReJitRequestILCode(ICorDebugILCode **ppReJitedILCode);
+
+ //-----------------------------------------------------------
+ // Internal members
+ //-----------------------------------------------------------
+protected:
+ // Returns the function's ILCode and SigToken
+ HRESULT GetILCodeAndSigToken();
+
+ // Get the metadata token for the class to which a function belongs.
+ mdTypeDef InitParentClassOfFunctionHelper(mdToken funcMetaDataToken);
+
+ // Get information about one of the native code blobs for this function
+ HRESULT InitNativeCodeInfo();
+
+public:
+
+ // Get the class to which a given function belongs
+ HRESULT InitParentClassOfFunction();
+
+ void NotifyCodeCreated(CordbNativeCode* nativeCode);
+
+ HRESULT GetSig(SigParser *pMethodSigParser,
+ ULONG *pFunctionArgCount,
+ BOOL *pFunctionIsStatic);
+
+ HRESULT GetArgumentType(DWORD dwIndex, const Instantiation * pInst, CordbType ** ppResultType);
+
+
+ //-----------------------------------------------------------
+ // Internal routines
+ //-----------------------------------------------------------
+
+ // Get the existing IL code object
+ HRESULT GetILCode(CordbILCode ** ppCode);
+
+ // Finds or creates an ILCode for a given rejit request
+ HRESULT LookupOrCreateReJitILCode(VMPTR_SharedReJitInfo vmSharedRejitInfo,
+ CordbReJitILCode** ppILCode);
+
+
+#ifdef EnC_SUPPORTED
+ void MakeOld();
+#endif
+
+ //-----------------------------------------------------------
+ // Accessors
+ //-----------------------------------------------------------
+
+ // Get the AppDomain that this function lives in.
+ CordbAppDomain * GetAppDomain()
+ {
+ return (m_pModule->GetAppDomain());
+ }
+
+ // Get the CordbModule that this Function lives in.
+ CordbModule * GetModule()
+ {
+ return m_pModule;
+ }
+
+ // Get the CordbClass this of which this function is a member
+ CordbClass * GetClass()
+ {
+ return m_pClass;
+ }
+
+ // Get the IL code blob corresponding to this function
+ CordbILCode * GetILCode()
+ {
+ return m_pILCode;
+ }
+
+ // Get metadata token for this function
+ mdMethodDef GetMetadataToken()
+ {
+ return m_MDToken;
+ }
+
+ SIZE_T GetEnCVersionNumber()
+ {
+ return m_dwEnCVersionNumber;
+ }
+
+ CordbFunction * GetPrevVersion()
+ {
+ return m_pPrevVersion;
+ }
+
+ void SetPrevVersion(CordbFunction * prevVersion)
+ {
+ m_pPrevVersion.Assign(prevVersion);
+ }
+
+ typedef enum {kNativeOnly, kHasIL, kUnknownImpl} ImplementationKind;
+ ImplementationKind IsNativeImpl()
+ {
+ return (m_fIsNativeImpl);
+ }
+
+ // determine whether we have a native-only implementation
+ void InitNativeImpl();
+
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+private:
+ // The module that this Function is contained in. It maintains a strong reference to this object
+ // and will neuter this object.
+ CordbModule * m_pModule;
+
+ // The Class that this function is contained in.
+ CordbClass * m_pClass;
+
+ // We only have 1 IL blob associated with a given Function object.
+ RSSmartPtr<CordbILCode> m_pILCode;
+
+
+ // Generics allow a single IL method to be instantiated to multiple native
+ // code blobs. So CordbFunction : CordbNativeCode is 1:n.
+ // This pointer is to arbitrary one of those n code bodies.
+ // Someday we may need to get access to all N of them but not today
+ RSSmartPtr<CordbNativeCode> m_nativeCode;
+
+ // Metadata Token for the IL function. Scoped to m_module.
+ mdMethodDef m_MDToken;
+
+ // EnC version number of this instance
+ SIZE_T m_dwEnCVersionNumber;
+
+ // link to previous version of this function
+ RSSmartPtr<CordbFunction> m_pPrevVersion;
+
+ // Is the function implemented natively in the runtime?? (eg, it has no IL, may be an Ecall/fcall)
+ ImplementationKind m_fIsNativeImpl;
+
+ // True if method signature (argument) values are cached.
+ BOOL m_fCachedMethodValuesValid;
+
+ // Cached SigParser for this Function's argument signature.
+ // Only valid if m_fCachedMethodValuesValid is set.
+ SigParser m_methodSigParserCached;
+
+ // Cached Count of arguments in the argument signature.
+ // Only valid if m_fCachedMethodValuesValid is set.
+ ULONG m_argCountCached;
+
+ // Cached boolean if method is static or instance (part of the argument signature).
+ // Only valid if m_fCachedMethodValuesValid is set.
+ BOOL m_fIsStaticCached;
+
+ // A collection, indexed by VMPTR_SharedReJitInfo, of IL code for rejit requests
+ // The collection is filled lazily by LookupOrCreateReJitILCode
+ CordbSafeHashTable<CordbReJitILCode> m_reJitILCodes;
+};
+
+//-----------------------------------------------------------------------------
+// class CordbCode
+// Represents either IL or Native code blobs associated with a function.
+//
+// See the comments at the ICorDebugCode definition for invariants about Code objects.
+//
+//-----------------------------------------------------------------------------
+class CordbCode : public CordbBase, public ICorDebugCode
+{
+protected:
+ CordbCode(CordbFunction * pFunction, UINT_PTR id, SIZE_T encVersion, BOOL fIsIL);
+
+public:
+ virtual ~CordbCode();
+ virtual void Neuter();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() = 0;
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugCode
+ //-----------------------------------------------------------
+
+ COM_METHOD IsIL(BOOL * pbIL);
+ COM_METHOD GetFunction(ICorDebugFunction ** ppFunction);
+ COM_METHOD GetAddress(CORDB_ADDRESS * pStart) = 0;
+ COM_METHOD GetSize(ULONG32 * pcBytes);
+ COM_METHOD CreateBreakpoint(ULONG32 offset,
+ ICorDebugFunctionBreakpoint ** ppBreakpoint);
+ COM_METHOD GetCode(ULONG32 startOffset, ULONG32 endOffset,
+ ULONG32 cBufferAlloc,
+ BYTE buffer[],
+ ULONG32 * pcBufferSize);
+ COM_METHOD GetVersionNumber( ULONG32 * nVersion);
+ COM_METHOD GetILToNativeMapping(ULONG32 cMap,
+ ULONG32 * pcMap,
+ COR_DEBUG_IL_TO_NATIVE_MAP map[]) = 0;
+ COM_METHOD GetEnCRemapSequencePoints(ULONG32 cMap,
+ ULONG32 * pcMap,
+ ULONG32 offsets[]);
+
+ //-----------------------------------------------------------
+ // Accessors and convenience routines
+ //-----------------------------------------------------------
+
+ // get the CordbFunction instance for this code object
+ CordbFunction * GetFunction();
+
+ // get the actual code bytes for this function
+ virtual HRESULT ReadCodeBytes() = 0;
+
+ // get the size in bytes of this function
+ virtual ULONG32 GetSize() = 0;
+
+
+ // get the metadata token for this code object
+ mdMethodDef GetMetadataToken()
+ {
+ _ASSERTE(m_pFunction != NULL);
+ return (m_pFunction->GetMetadataToken());
+ }
+
+ // get the module this code object belongs to
+ CordbModule * GetModule()
+ {
+ _ASSERTE(m_pFunction != NULL);
+ return (m_pFunction->GetModule());
+ }
+
+ // get the function signature for this code blob or throw on failure
+ void GetSig(SigParser *pMethodSigParser,
+ ULONG *pFunctionArgCount,
+ BOOL *pFunctionIsStatic)
+ {
+ _ASSERTE(m_pFunction != NULL);
+ IfFailThrow(m_pFunction->GetSig(pMethodSigParser, pFunctionArgCount, pFunctionIsStatic));
+ }
+
+ // get the class to which this code blob belongs
+ CordbClass * GetClass()
+ {
+ _ASSERTE(m_pFunction != NULL);
+ return (m_pFunction->GetClass());
+ }
+
+ // Quick helper to get the AppDomain that this code object lives in.
+ CordbAppDomain *GetAppDomain()
+ {
+ _ASSERTE(m_pFunction != NULL);
+ return (m_pFunction->GetAppDomain());
+ }
+
+ // Get the EnC version of this blob
+ SIZE_T GetVersion() { return m_nVersion; };
+
+ // Return true if this is an IL code blob. Else return false.
+ BOOL IsIL() { return m_fIsIL; }
+
+ // convert to CordbNativeCode as long as m_fIsIl is false.
+ CordbNativeCode * AsNativeCode()
+ {
+ _ASSERTE(m_fIsIL == FALSE);
+ return reinterpret_cast<CordbNativeCode *>(this);
+ }
+
+ // convert to CordbILCode as long as m_fIsIl is true.
+ CordbILCode * AsILCode()
+ {
+ _ASSERTE(m_fIsIL == TRUE);
+ return reinterpret_cast<CordbILCode *>(this);
+ }
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+private:
+ UINT m_fIsIL : 1;
+
+ // EnC version number.
+ SIZE_T m_nVersion;
+
+protected:
+ // Our local copy of the code. It will be GetSize() bytes long.
+ BYTE * m_rgbCode; // will be NULL if we can't fit it into memory
+
+ UINT m_continueCounterLastSync;
+
+ // Owning Function associated with this code.
+ CordbFunction * m_pFunction;
+}; //class CordbCode
+
+
+
+
+
+/* ------------------------------------------------------------------------- *
+* CordbILCode class
+* This class represents an IL code blob for a particular EnC version. Thus it is
+* 1:1 with a given instantiation of CordbFunction. Provided functionality includes
+* methods to get the starting address and size of an IL code blob and to read
+* the actual bytes of IL into a buffer.
+ * ------------------------------------------------------------------------- */
+
+class CordbILCode : public CordbCode
+{
+public:
+ // Initialize a new CordbILCode instance
+ CordbILCode(CordbFunction *pFunction, TargetBuffer codeRegionInfo, SIZE_T nVersion, mdSignature localVarSigToken, UINT_PTR id = 0);
+
+#ifdef _DEBUG
+ const char * DbgGetName() { return "CordbILCode"; };
+#endif // _DEBUG
+
+ COM_METHOD GetAddress(CORDB_ADDRESS * pStart);
+ COM_METHOD GetILToNativeMapping(ULONG32 cMap,
+ ULONG32 * pcMap,
+ COR_DEBUG_IL_TO_NATIVE_MAP map[]);
+ // Quick helper for internal access to: GetAddress(CORDB_ADDRESS *pStart);
+ CORDB_ADDRESS GetAddress() { return m_codeRegionInfo.pAddress; }
+
+ // get total size of the IL code
+ ULONG32 GetSize() { return m_codeRegionInfo.cbSize; }
+
+#ifdef EnC_SUPPORTED
+ void MakeOld();
+#endif // EnC_SUPPORTED
+
+ HRESULT GetLocalVarSig(SigParser *pLocalsSigParser, ULONG *pLocalVarCount);
+ HRESULT GetLocalVariableType(DWORD dwIndex, const Instantiation * pInst, CordbType ** ppResultType);
+ mdSignature GetLocalVarSigToken();
+
+private:
+ // Read the actual bytes of IL code into the data member m_rgbCode.
+ // Helper routine for GetCode
+ HRESULT ReadCodeBytes();
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+private:
+#ifdef EnC_SUPPORTED
+ UINT m_fIsOld : 1; // marks this instance as an old EnC version
+ bool m_encBreakpointsApplied;
+#endif
+
+ // derived types can init this
+protected:
+ TargetBuffer m_codeRegionInfo; // stores the starting address and size of the
+ // IL code blob
+
+ // Metadata token for local's signature.
+ mdSignature m_localVarSigToken;
+
+}; // class CordbILCode
+
+/* ------------------------------------------------------------------------- *
+* CordbReJitILCode class
+* This class represents an IL code blob for a particular EnC version and
+* rejitID. Thus it is 1:N with a given instantiation of CordbFunction.
+* ------------------------------------------------------------------------- */
+
+class CordbReJitILCode : public CordbILCode, public ICorDebugILCode, public ICorDebugILCode2
+{
+public:
+ // Initialize a new CordbILCode instance
+ CordbReJitILCode(CordbFunction *pFunction, SIZE_T encVersion, VMPTR_SharedReJitInfo vmSharedReJitInfo);
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+ ULONG STDMETHODCALLTYPE AddRef();
+ ULONG STDMETHODCALLTYPE Release();
+ COM_METHOD QueryInterface(REFIID riid, void** ppInterface);
+
+
+ //-----------------------------------------------------------
+ // ICorDebugILCode
+ //-----------------------------------------------------------
+ COM_METHOD GetEHClauses(ULONG32 cClauses, ULONG32 * pcClauses, CorDebugEHClause clauses[]);
+
+
+ //-----------------------------------------------------------
+ // ICorDebugILCode2
+ //-----------------------------------------------------------
+ COM_METHOD GetLocalVarSigToken(mdSignature *pmdSig);
+ COM_METHOD GetInstrumentedILMap(ULONG32 cMap, ULONG32 *pcMap, COR_IL_MAP map[]);
+
+private:
+ HRESULT Init(DacSharedReJitInfo* pSharedReJitInfo);
+
+private:
+ ULONG32 m_cClauses;
+ NewArrayHolder<CorDebugEHClause> m_pClauses;
+ ULONG32 m_cbLocalIL;
+ NewArrayHolder<BYTE> m_pLocalIL;
+ ULONG32 m_cILMap;
+ NewArrayHolder<COR_IL_MAP> m_pILMap;
+};
+
+/* ------------------------------------------------------------------------- *
+ * CordbNativeCode class. These correspond to MethodDesc's on the left-side.
+ * There may or may not be a DebuggerJitInfo associated with the MethodDesc.
+ * At most one CordbNativeCode is created for each native code compilation of each method
+ * that is seen by the right-side. Note that if each method were JITted only once
+ * then this information could go in CordbFunction, however generics allow
+ * methods to be compiled more than once.
+ *
+ * The purpose of this class is to encapsulate details about a blob of jitted/ngen'ed
+ * code, including an optional set of mappings from IL to offsets in the native Code.
+ * ------------------------------------------------------------------------- */
+
+class CordbNativeCode : public CordbCode, public ICorDebugCode2, public ICorDebugCode3
+{
+public:
+ CordbNativeCode(CordbFunction * pFunction,
+ const NativeCodeFunctionData * pJitData,
+ BOOL fIsInstantiatedGeneric);
+#ifdef _DEBUG
+ const char * DbgGetName() { return "CordbNativeCode"; };
+#endif // _DEBUG
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugCode
+ //-----------------------------------------------------------
+ COM_METHOD GetAddress(CORDB_ADDRESS * pStart);
+ COM_METHOD GetILToNativeMapping(ULONG32 cMap,
+ ULONG32 * pcMap,
+ COR_DEBUG_IL_TO_NATIVE_MAP map[]);
+ //-----------------------------------------------------------
+ // ICorDebugCode2
+ //-----------------------------------------------------------
+ COM_METHOD GetCodeChunks(ULONG32 cbufSize, ULONG32 * pcnumChunks, CodeChunkInfo chunks[]);
+
+ COM_METHOD GetCompilerFlags(DWORD * pdwFlags);
+
+ //-----------------------------------------------------------
+ // ICorDebugCode3
+ //-----------------------------------------------------------
+ COM_METHOD GetReturnValueLiveOffset(ULONG32 ILoffset, ULONG32 bufferSize, ULONG32 *pFetched, ULONG32 *pOffsets);
+
+
+ //-----------------------------------------------------------
+ // Internal members
+ //-----------------------------------------------------------
+
+ HRESULT ILVariableToNative(DWORD dwIndex,
+ SIZE_T ip,
+ const ICorDebugInfo::NativeVarInfo ** ppNativeInfo);
+ void LoadNativeInfo();
+
+ //-----------------------------------------------------------
+ // Accessors and convenience routines
+ //-----------------------------------------------------------
+
+ // get the argument type for a generic
+ void GetArgumentType(DWORD dwIndex,
+ const Instantiation * pInst,
+ CordbType ** ppResultType)
+ {
+ CordbFunction * pFunction = GetFunction();
+ _ASSERTE(pFunction != NULL);
+ IfFailThrow(pFunction->GetArgumentType(dwIndex, pInst, ppResultType));
+ }
+
+ // Quick helper for internall access to: GetAddress(CORDB_ADDRESS *pStart);
+ CORDB_ADDRESS GetAddress() { return m_rgCodeRegions[kHot].pAddress; };
+
+ VMPTR_MethodDesc GetVMNativeCodeMethodDescToken() { return m_vmNativeCodeMethodDescToken; };
+
+ // Worker function for GetReturnValueLiveOffset.
+ HRESULT GetReturnValueLiveOffsetImpl(Instantiation *currentInstantiation, ULONG32 ILoffset, ULONG32 bufferSize, ULONG32 *pFetched, ULONG32 *pOffsets);
+
+ // get total size of the code including both hot and cold regions
+ ULONG32 GetSize();
+
+ // get the size of the cold region(s) only
+ ULONG32 GetColdSize();
+
+ // Return true if the Code is split into hot + cold regions.
+ bool HasColdRegion() { return m_rgCodeRegions[kCold].pAddress != NULL; }
+
+ // Get the number of fixed arguments for this function (the "this"
+ // but not varargs)
+ unsigned int GetFixedArgCount()
+ {
+ return m_nativeVarData.GetFixedArgCount();
+ }
+
+ // Get the number of all arguments for this function
+ // ("this" pointer, fixed args and varargs)
+ ULONG32 GetAllArgsCount()
+ {
+ return m_nativeVarData.GetAllArgsCount();
+ }
+
+ void SetAllArgsCount(ULONG32 count)
+ {
+ m_nativeVarData.SetAllArgsCount(count);
+ }
+
+ // Determine whether this is an instantiation of a generic function
+ BOOL IsInstantiatedGeneric()
+ {
+ return m_fIsInstantiatedGeneric != 0;
+ }
+
+ // Determine whether we have initialized the native variable and
+ // sequence point offsets
+ BOOL IsNativeCodeValid ()
+ {
+ return ((m_nativeVarData.IsInitialized() != 0) &&
+ (m_sequencePoints.IsInitialized() != 0));
+ }
+
+ SequencePoints * GetSequencePoints()
+ {
+ return &m_sequencePoints;
+ }
+
+
+ // Given an ILOffset in the current function, return the class token and function token of the IL call target at that
+ // location. Also fill "methodSig" with the method's signature and "genericSig" with the method's generic signature.
+ HRESULT GetCallSignature(ULONG32 ILOffset, mdToken *pClass, mdToken *pMDFunction, SigParser &methodSig, SigParser &genericSig);
+
+ // Moves a method signature from the start of the signature to the location of the return value (passing out the
+ // number of generic parameters in the method).
+ static HRESULT SkipToReturn(SigParser &parser, ULONG *genArgCount = 0);
+
+private:
+ // Read the actual bytes of native code into the data member m_rgbCode.
+ // Helper routine for GetCode
+ HRESULT ReadCodeBytes();
+
+ // Returns a failure HRESULT if we cannot handle the return value of the given
+ // methodref, methoddef, or methodspec token, otherwise S_OK. Does NOT return S_FALSE;
+ HRESULT EnsureReturnValueAllowed(Instantiation *currentInstantiation, mdToken targetClass, SigParser &parser, SigParser &methodGenerics);
+ HRESULT EnsureReturnValueAllowedWorker(Instantiation *currentInstantiation, mdToken targetClass, SigParser &parser, SigParser &methodGenerics, ULONG genCount);
+
+ // Grabs the appropriate signature parser for a methodref, methoddef, methodspec.
+ HRESULT GetSigParserFromFunction(mdToken mdFunction, mdToken *pClass, SigParser &methodSig, SigParser &genericSig);
+
+ int GetCallInstructionLength(BYTE *buffer, ULONG32 len);
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+private:
+ // offset of the beginning of the last sequence point in the sequence point map
+ SIZE_T m_lastIL;
+
+ // start address(es) and size(s) of hot and cold regions
+ TargetBuffer m_rgCodeRegions[MAX_REGIONS];
+
+ // LS data structure--method desc for this instantiation.
+ VMPTR_MethodDesc m_vmNativeCodeMethodDescToken;
+
+ bool m_fCodeAvailable; // true iff the code has been jitted but not pitched
+
+ bool m_fIsInstantiatedGeneric; // true iff this is an instantiated generic
+
+ // information in the following two classes tracks native offsets and is initialized on demand.
+
+ // location and ID information for local variables. See code:NativeVarData for details.
+ NativeVarData m_nativeVarData;
+
+ // mapping between IL and native code sequence points.
+ SequencePoints m_sequencePoints;
+
+}; //class CordbNativeCode
+
+//---------------------------------------------------------------------------------------
+//
+// GetActiveInternalFramesData is used to enumerate internal frames on a specific thread.
+// It is used in conjunction with code:CordbThread::GetActiveInternalFramesCallback.
+// We store each internal frame in ppInternalFrames as we enumerate them.
+//
+
+struct GetActiveInternalFramesData
+{
+public:
+ // the thread we are walking
+ CordbThread * pThis;
+
+ // an array to store the internal frames
+ RSPtrArray<CordbInternalFrame> pInternalFrames;
+
+ // next element in the array to be filled
+ ULONG32 uIndex;
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Thread classes
+ * ------------------------------------------------------------------------- */
+
+class CordbThread : public CordbBase, public ICorDebugThread,
+ public ICorDebugThread2,
+ public ICorDebugThread3,
+ public ICorDebugThread4
+{
+public:
+ CordbThread(CordbProcess * pProcess, VMPTR_Thread);
+
+ virtual ~CordbThread();
+ virtual void Neuter();
+
+ using CordbBase::GetProcess;
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbThread"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ // there's an external add ref from within RS in CordbEnumFilter
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugThread
+ //-----------------------------------------------------------
+
+ COM_METHOD GetProcess(ICorDebugProcess **ppProcess);
+ COM_METHOD GetID(DWORD *pdwThreadId);
+ COM_METHOD GetHandle(HANDLE * phThreadHandle);
+ COM_METHOD GetAppDomain(ICorDebugAppDomain **ppAppDomain);
+ COM_METHOD SetDebugState(CorDebugThreadState state);
+ COM_METHOD GetDebugState(CorDebugThreadState *pState);
+ COM_METHOD GetUserState(CorDebugUserState *pState);
+ COM_METHOD GetCurrentException(ICorDebugValue ** ppExceptionObject);
+ COM_METHOD ClearCurrentException();
+ COM_METHOD CreateStepper(ICorDebugStepper **ppStepper);
+ COM_METHOD EnumerateChains(ICorDebugChainEnum **ppChains);
+ COM_METHOD GetActiveChain(ICorDebugChain **ppChain);
+ COM_METHOD GetActiveFrame(ICorDebugFrame **ppFrame);
+ COM_METHOD GetRegisterSet(ICorDebugRegisterSet **ppRegisters);
+ COM_METHOD CreateEval(ICorDebugEval **ppEval);
+ COM_METHOD GetObject(ICorDebugValue ** ppObject);
+
+ // ICorDebugThread2
+ COM_METHOD GetConnectionID(CONNID * pConnectionID);
+ COM_METHOD GetTaskID(TASKID * pTaskID);
+ COM_METHOD GetVolatileOSThreadID(DWORD * pdwTID);
+ COM_METHOD GetActiveFunctions(ULONG32 cFunctions, ULONG32 * pcFunctions, COR_ACTIVE_FUNCTION pFunctions[]);
+ // Intercept the current exception at the specified frame. pFrame must be a valid ICDFrame, possibly from
+ // a previous stackwalk.
+ COM_METHOD InterceptCurrentException(ICorDebugFrame * pFrame);
+
+
+
+ // ICorDebugThread3
+ COM_METHOD CreateStackWalk(ICorDebugStackWalk **ppStackWalk);
+
+ COM_METHOD GetActiveInternalFrames(ULONG32 cInternalFrames,
+ ULONG32 * pcInternalFrames,
+ ICorDebugInternalFrame2 * ppInternalFrames[]);
+
+ // ICorDebugThread4
+ COM_METHOD HasUnhandledException();
+
+ COM_METHOD GetBlockingObjects(ICorDebugBlockingObjectEnum **ppBlockingObjectEnum);
+
+ // Gets the current CustomNotification object from the thread or NULL if no such object exists
+ COM_METHOD GetCurrentCustomDebuggerNotification(ICorDebugValue ** ppNotificationObject);
+ //-----------------------------------------------------------
+ // Internal members
+ //-----------------------------------------------------------
+
+ // callback used to enumerate the internal frames on a thread
+ static void GetActiveInternalFramesCallback(const DebuggerIPCE_STRData * pFrameData,
+ void * pUserData);
+
+ CorDebugUserState GetUserState();
+
+ // Given a FramePointer, find the matching CordbFrame.
+ HRESULT FindFrame(ICorDebugFrame ** ppFrame, FramePointer fp);
+
+ // Get the task ID for this thread.
+ TASKID GetTaskID();
+
+ void RefreshStack();
+ void CleanupStack();
+ void MarkStackFramesDirty();
+
+#if !defined(DBG_TARGET_ARM) // @ARMTODO
+
+#if defined(DBG_TARGET_X86)
+ // Converts the values in the floating point register area of the context to real number values.
+ void Get32bitFPRegisters(CONTEXT * pContext);
+
+#elif defined(DBG_TARGET_AMD64) || defined(DBG_TARGET_ARM64)
+ // Converts the values in the floating point register area of the context to real number values.
+ void Get64bitFPRegisters(FPRegister64 * rgContextFPRegisters, int start, int nRegisters);
+#endif // DBG_TARGET_X86
+
+ // Initializes the float state members of this instance of CordbThread. This function gets the context and
+ // converts the floating point values from their context representation to real number values.
+ void LoadFloatState();
+
+#endif //!DBG_TARGET_ARM @ARMTODO
+
+ HRESULT SetIP( bool fCanSetIPOnly,
+ CordbNativeCode * pNativeCode,
+ SIZE_T offset,
+ bool fIsIL );
+
+ // Tells the LS to remap to the latest version of the function
+ HRESULT SetRemapIP(SIZE_T offset);
+
+ // Ask the left-side for the current (up-to-date) AppDomain of this thread's IP.
+ // This should be preferred over using the cached value from GetAppDomain.
+ HRESULT GetCurrentAppDomain(CordbAppDomain ** ppAppDomain);
+
+ //-----------------------------------------------------------
+ // Convenience routines
+ //-----------------------------------------------------------
+
+ // The last domain from which a debug event for this thread was sent.
+ // This usually (but not always) the domain the thread is currently executing in.
+ // Since this is a cache, it may sometimes be out-of-date. I believe all current
+ // usage of this is OK (we pass AppDomains around a lot without really using them),
+ // but no new code should rely on this value.
+ // TODO: eliminate this and the m_pAppDomain field entirely
+ CordbAppDomain *GetAppDomain()
+ {
+ return (m_pAppDomain);
+ }
+
+ DWORD GetVolatileOSThreadID();
+
+ //////////////////////////////////////////////////////////////////////////
+ //
+ // Get Context
+ //
+ // <TODO>TODO: Since Thread will share the memory with RegisterSets, how
+ // do we know that the RegisterSets have relinquished all pointers
+ // to the m_pContext structure?</TODO>
+ //
+ // Returns: NULL if the thread's CONTEXT structure couldn't be obtained
+ // A pointer to the CONTEXT otherwise.
+ //
+ //
+ //////////////////////////////////////////////////////////////////////////
+ HRESULT GetManagedContext( DT_CONTEXT ** ppContext );
+ HRESULT SetManagedContext( DT_CONTEXT * pContext );
+
+ // API to retrieve the thread handle from the LS.
+ void InternalGetHandle(HANDLE * phThread);
+ void RefreshHandle(HANDLE * phThread);
+
+ // NeuterList that's executed when this Thread's stack is refreshed.
+ // Chain + Frame + some Value enums can be held on this.
+ NeuterList * GetRefreshStackNeuterList()
+ {
+ return &m_RefreshStackNeuterList;
+ }
+
+ DWORD GetUniqueId();
+
+
+ // Hijack a thread at a 2nd-chance exception so that it can execute the CLR's UEF
+ void HijackForUnhandledException();
+
+ // check whether the specified frame lives on the stack of the current thread
+ bool OwnsFrame(CordbFrame *pFrame);
+
+ // Specify that there's an outstanding exception on this thread.
+ void SetExInfo(VMPTR_OBJECTHANDLE vmExcepObjHandle);
+
+ VMPTR_OBJECTHANDLE GetThreadExceptionRawObjectHandle() { return m_vmExcepObjHandle; }
+ bool HasException() { return m_fException; }
+
+ void SetUnhandledNativeException(const EXCEPTION_RECORD * pExceptionRecord);
+ bool HasUnhandledNativeException();
+
+#ifdef _DEBUG
+ // Helper to assert that this thread no longer appears in dac-dbi enumerations
+ void DbgAssertThreadDeleted();
+
+ // Callback for DbgAssertThreadDeleted
+ static void DbgAssertThreadDeletedCallback(VMPTR_Thread vmThread, void * pUserData);
+#endif // _DEBUG
+
+ // Determine if the thread's current exception is managed or unmanaged.
+ BOOL IsThreadExceptionManaged();
+
+ // This is a private hook for the shim to create a CordbRegisterSet for a ShimChain.
+ void CreateCordbRegisterSet(DT_CONTEXT * pContext,
+ BOOL fActive,
+ CorDebugChainReason reason,
+ ICorDebugRegisterSet ** ppRegSet);
+
+ // This is a private hook for the shim to convert an ICDFrame into an ICDInternalFrame for a dynamic
+ // method. Refer to the function header for more information.
+ BOOL ConvertFrameForILMethodWithoutMetadata(ICorDebugFrame * pFrame,
+ ICorDebugInternalFrame2 ** ppInternalFrame2);
+
+ // Gets/sets m_fCreationEventQueued
+ bool CreateEventWasQueued();
+ void SetCreateEventQueued();
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+public:
+ // RS Cache for LS context.
+ // NULL if we haven't allocated memory for a Right side context
+ DT_CONTEXT * m_pContext;
+
+ // Set to the CONTEXT pointer in the LS if this LS thread is
+ // stopped in managed code. This may be either stopped for execution control
+ // (breakpoint / single-step exception) or hijacked w/ a redirected frame because
+ // another thread synced the LS.
+ // This context is used by the RS to set enregistered vars.
+ VMPTR_CONTEXT m_vmLeftSideContext;
+
+ // indicates whether m_pContext is up-to-date
+ bool m_fContextFresh;
+
+ // last domain we've seen this thread.
+ // If the appdomain exits, it will clear out this value.
+ CordbAppDomain *m_pAppDomain;
+
+ // Handle to VM's Thread* object. This is the primary key for a CordbThread object
+ // @dbgtodo ICDThread - merge with m_id;
+ VMPTR_Thread m_vmThreadToken;
+
+ // Unique ID for this thread. See code:CordbThread::GetID for semantics of this field.
+ DWORD m_dwUniqueID;
+
+ CorDebugThreadState m_debugState; // Note that this is for resume
+ // purposes, NOT the current state of
+ // the thread.
+
+ // The frames are all protected under the Stop-Go lock.
+ // This field indicates whether the stack is valid (i.e. no update is necessary).
+ bool m_fFramesFresh;
+
+ // This is a cache of V3 ICDFrames. The cache is only used by two functions:
+ // - code:CordbThread::GetActiveFunctions
+ // - code:CordbThread::InterceptCurrentException.
+ //
+ // We don't clear the cache in CleanupStack() because we don't refresh the cache every time we stop.
+ // Instead, we mark m_fFramesFresh in CleanupStack() and clear the cache only when it is used next time.
+ CDynArray<CordbFrame *> m_stackFrames;
+
+#if !defined(DBG_TARGET_ARM) // @ARMTODO
+ bool m_fFloatStateValid;
+ unsigned int m_floatStackTop;
+ double m_floatValues[DebuggerIPCE_FloatCount];
+#endif // !DBG_TARGET_ARM @ARMTODO
+
+private:
+ // True for the window after an Exception callback, but before it's been continued.
+ // We dispatch two exception events in a row (ICDManagedCallback::Exception and ICDManagedCallback2::Exception),
+ // and a debugger may normally just skip the first one knowing it can stop on the 2nd once.
+ // Both events will set this bit high. Be careful not to reset this bit inbetween them.
+ bool m_fException;
+
+ // True if a creation event has been queued for this thread
+ // The event may or may not have been dispatched yet
+ // Bugfix DevDiv2\DevDiv 77523 - this is only being set from ShimProcess::QueueFakeThreadAttachEventsNativeOrder
+ bool m_fCreationEventQueued;
+
+ // Object handle for Exception object in debuggee.
+ VMPTR_OBJECTHANDLE m_vmExcepObjHandle;
+
+public:
+
+ //Returns true if current user state of a thread is USER_WAIT_SLEEP_JOIN
+ bool IsThreadWaitingOrSleeping();
+
+ // Returns true if the thread is dead. See function header for definition.
+ bool IsThreadDead();
+
+ // Return CORDBG_E_BAD_THREAD_STATE if the thread is dead.
+ HRESULT EnsureThreadIsAlive();
+
+ // On a RemapBreakpoint, the debugger will eventually call RemapFunction and
+ // we need to communicate the IP back to LS. So we stash the address of where
+ // to store the IP here and stuff it in on RemapFunction.
+ // If we're not at an outstanding RemapOpportunity, this will be NULL
+ REMOTE_PTR m_EnCRemapFunctionIP;
+
+private:
+ void ClearStackFrameCache();
+
+ // True iff this thread has an unhandled exception on it.
+ // Set high when Filter() gets noitifed of an unhandled exception.
+ // Set Low if the thread is hijacked.
+ bool m_fHasUnhandledException;
+
+ // Exception record for last unhandled exception on this thread.
+ // Lazily initialized.
+ EXCEPTION_RECORD * m_pExceptionRecord;
+
+ static const CorDebugUserState kInvalidUserState = CorDebugUserState(-1);
+ CorDebugUserState m_userState; // This is the current state of the
+ // thread, at the time that the
+ // left side synchronized
+
+ // NeuterList that's executed when this Thread's stack is refreshed.
+ // This list is for everything related to stackwalking, i.e. everything which is invalidated
+ // if the stack changes in any way. This list is cleared when any of the following is called:
+ // 1) Continue()
+ // 2) SetIP()
+ // 3) RemapFunction()
+ // 4) ICDProcess::SetThreadContext()
+ NeuterList m_RefreshStackNeuterList;
+
+ // The following two data members are used for caching thread handles.
+ // @dbgtodo - Remove in V3 (can't have local handles with data-target abstraction);
+ // offload to the shim to support V2 scenarios.
+ HANDLE m_hCachedThread;
+ HANDLE m_hCachedOutOfProcThread;
+};
+
+/* ------------------------------------------------------------------------- *
+ * StackWalk class
+ * ------------------------------------------------------------------------- */
+
+class CordbStackWalk : public CordbBase, public ICorDebugStackWalk
+{
+public:
+ CordbStackWalk(CordbThread * pCordbThread);
+ virtual ~CordbStackWalk();
+ virtual void Neuter();
+
+ // helper function for Neuter
+ virtual void DeleteAll();
+
+ using CordbBase::GetProcess;
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbStackWalk"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugStackWalk
+ //-----------------------------------------------------------
+
+ COM_METHOD GetContext(ULONG32 contextFlags,
+ ULONG32 contextBufSize,
+ ULONG32 * pContextSize,
+ BYTE pbContextBuf[]);
+ COM_METHOD SetContext(CorDebugSetContextFlag flag, ULONG32 contextSize, BYTE context[]);
+ COM_METHOD Next();
+ COM_METHOD GetFrame(ICorDebugFrame **ppFrame);
+
+ //-----------------------------------------------------------
+ // Internal members
+ //-----------------------------------------------------------
+
+ void SetContextWorker(CorDebugSetContextFlag flag, ULONG32 contextSize, BYTE context[]);
+ HRESULT GetFrameWorker(ICorDebugFrame **ppFrame);
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+public:
+ void Init();
+
+private:
+ // handle legacy V2 hijacking for unhandled hardware exceptions
+ void CheckForLegacyHijackCase();
+
+ // refresh the data for this instance of CordbStackWalk if we have had an IPC event followed by a
+ // continue since we got the information.
+ void RefreshIfNeeded();
+
+ // unwind the frame and update m_context with the new context
+ BOOL UnwindStackFrame();
+
+ // the thread on which this CordbStackWalk is created
+ CordbThread * m_pCordbThread;
+
+ // This is the same iterator used by the runtime itself.
+ IDacDbiInterface::StackWalkHandle m_pSFIHandle;
+
+ // buffers used for stackwalking
+ DT_CONTEXT m_context;
+
+ // Used to figure out if we have to refresh any reference objects
+ // on the left side. We set it to CordbProcess::m_flushCounter on
+ // creation and will check it against that value when we call GetFrame or Next.
+ // If it doesn't match, an IPC event has occurred and the values will need to be
+ // refreshed via the DAC.
+ UINT m_lastSyncFlushCounter;
+
+ // cached flag used for refreshing a CordbStackWalk
+ CorDebugSetContextFlag m_cachedSetContextFlag;
+
+ // We unwind one frame ahead of time to get the FramePointer on x86.
+ // These fields are used for the bookkeeping.
+ RSSmartPtr<CordbFrame> m_pCachedFrame;
+ HRESULT m_cachedHR;
+ bool m_fIsOneFrameAhead;
+};
+
+
+class CordbContext : public CordbBase, public ICorDebugContext
+{
+public:
+
+ CordbContext() : CordbBase(NULL, 0, enumCordbContext) {}
+
+
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbContext"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugContext
+ //-----------------------------------------------------------
+private:
+
+} ;
+
+
+/* ------------------------------------------------------------------------- *
+ * Frame class
+ * ------------------------------------------------------------------------- */
+
+class CordbFrame : public CordbBase, public ICorDebugFrame
+{
+protected:
+ // Ctor to provide dummy frame that just wraps a frame-pointer
+ CordbFrame(CordbProcess * pProcess, FramePointer fp);
+
+public:
+ CordbFrame(CordbThread * pThread,
+ FramePointer fp,
+ SIZE_T ip,
+ CordbAppDomain * pCurrentAppDomain);
+
+ virtual ~CordbFrame();
+ virtual void Neuter();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbFrame"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugFrame
+ //-----------------------------------------------------------
+
+ COM_METHOD GetChain(ICorDebugChain **ppChain);
+
+ // Derived versions of Frame will implement GetCode.
+ COM_METHOD GetCode(ICorDebugCode **ppCode) = 0;
+
+ COM_METHOD GetFunction(ICorDebugFunction **ppFunction);
+ COM_METHOD GetFunctionToken(mdMethodDef *pToken);
+
+ COM_METHOD GetStackRange(CORDB_ADDRESS *pStart, CORDB_ADDRESS *pEnd);
+ COM_METHOD GetCaller(ICorDebugFrame **ppFrame);
+ COM_METHOD GetCallee(ICorDebugFrame **ppFrame);
+ COM_METHOD CreateStepper(ICorDebugStepper **ppStepper);
+
+ //-----------------------------------------------------------
+ // Convenience routines
+ //-----------------------------------------------------------
+
+ CordbAppDomain *GetCurrentAppDomain()
+ {
+ return m_currentAppDomain;
+ }
+
+ // Internal helper to get a CordbFunction for this frame.
+ virtual CordbFunction *GetFunction() = 0;
+
+ FramePointer GetFramePointer()
+ {
+ return m_fp;
+ }
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+ // Accessors to return NULL or typesafe cast to derived frame
+ virtual CordbInternalFrame * GetAsInternalFrame() { return NULL; }
+ virtual CordbNativeFrame * GetAsNativeFrame() { return NULL; }
+
+ // determine if the frame pointer is in the stack range owned by the frame
+ bool IsContainedInFrame(FramePointer fp);
+
+ // This is basically a complicated cast function. We are casting from an ICorDebugFrame to a CordbFrame.
+ static CordbFrame* GetCordbFrameFromInterface(ICorDebugFrame *pFrame);
+
+ virtual const DT_CONTEXT * GetContext() const { return NULL; }
+
+public:
+ // this represents the IL offset for a CordbJITILFrame, the native offset for a CordbNativeFrame,
+ // and 0 for a CordbInternalFrame
+ SIZE_T m_ip;
+
+ CordbThread * m_pThread;
+
+ CordbAppDomain *m_currentAppDomain;
+ FramePointer m_fp;
+
+protected:
+ // indicates whether this frame is the leaf frame; lazily initialized
+ mutable Optional<bool> m_optfIsLeafFrame;
+
+private:
+#ifdef _DEBUG
+ // For tracking down neutering bugs;
+ UINT m_DbgContinueCounter;
+#endif
+};
+
+// Dummy frame that just wraps a frame pointer.
+// This is used to pass a FramePointer back in the Exception2 callback.
+// Currently, the callback passes back an ICorDebugFrame as a way of exposing a cross-platform
+// frame pointer. However passing back an ICDFrame means we need to do a stackwalk, and
+// that may not be possible in V3:
+// - the stackwalk is very chatty, and may be too much work just to give an exception notification.
+// - in 64-bit, we may not even be able to do the stackwalk ourselves.
+//
+// The shim can take the framePointer and do the stackwalk and resolve it to a real frame,
+// so V2 emulation scenarios will continue to work.
+// @dbgtodo exception - resolve this when we iron out exceptions in V3.
+class CordbPlaceholderFrame : public CordbFrame
+{
+public:
+ // Ctor to provide dummy frame that just wraps a frame-pointer
+ CordbPlaceholderFrame(CordbProcess * pProcess, FramePointer fp)
+ : CordbFrame(pProcess, fp)
+ {
+ }
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbFrame"; }
+#endif
+
+ // Provide dummy implementation for some methods. These should never be called.
+ COM_METHOD GetCode(ICorDebugCode **ppCode)
+ {
+ _ASSERTE(!"Don't call this");
+ return E_NOTIMPL;
+ }
+ virtual CordbFunction *GetFunction()
+ {
+ _ASSERTE(!"Don't call this");
+ return NULL;
+ }
+};
+
+class CordbInternalFrame : public CordbFrame, public ICorDebugInternalFrame, public ICorDebugInternalFrame2
+{
+public:
+ CordbInternalFrame(CordbThread * pThread,
+ FramePointer fp,
+ CordbAppDomain * pCurrentAppDomain,
+ const DebuggerIPCE_STRData * pData);
+
+ CordbInternalFrame(CordbThread * pThread,
+ FramePointer fp,
+ CordbAppDomain * pCurrentAppDomain,
+ CorDebugInternalFrameType frameType,
+ mdMethodDef funcMetadataToken,
+ CordbFunction * pFunction,
+ VMPTR_MethodDesc vmMethodDesc);
+
+ virtual void Neuter();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbInternalFrame"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugFrame
+ //-----------------------------------------------------------
+
+ COM_METHOD GetChain(ICorDebugChain **ppChain)
+ {
+ return (CordbFrame::GetChain(ppChain));
+ }
+
+ // We don't expose a code-object for stubs.
+ COM_METHOD GetCode(ICorDebugCode **ppCode)
+ {
+ return CORDBG_E_CODE_NOT_AVAILABLE;
+ }
+
+ COM_METHOD GetFunction(ICorDebugFunction **ppFunction)
+ {
+ return (CordbFrame::GetFunction(ppFunction));
+ }
+ COM_METHOD GetFunctionToken(mdMethodDef *pToken)
+ {
+ return (CordbFrame::GetFunctionToken(pToken));
+ }
+
+ COM_METHOD GetCaller(ICorDebugFrame **ppFrame)
+ {
+ return (CordbFrame::GetCaller(ppFrame));
+ }
+ COM_METHOD GetCallee(ICorDebugFrame **ppFrame)
+ {
+ return (CordbFrame::GetCallee(ppFrame));
+ }
+ COM_METHOD CreateStepper(ICorDebugStepper **ppStepper)
+ {
+ return E_NOTIMPL;
+ }
+
+ COM_METHOD GetStackRange(CORDB_ADDRESS *pStart, CORDB_ADDRESS *pEnd);
+
+ //-----------------------------------------------------------
+ // ICorDebugInternalFrame
+ //-----------------------------------------------------------
+
+ // Get the type of internal frame. This will never be STUBFRAME_NONE.
+ COM_METHOD GetFrameType(CorDebugInternalFrameType * pType)
+ {
+ VALIDATE_POINTER_TO_OBJECT(pType, CorDebugInternalFrameType)
+ *pType = m_eFrameType;
+ return S_OK;
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugInternalFrame2
+ //-----------------------------------------------------------
+
+ COM_METHOD GetAddress(CORDB_ADDRESS * pAddress);
+ COM_METHOD IsCloserToLeaf(ICorDebugFrame * pFrameToCompare,
+ BOOL * pIsCloser);
+
+ BOOL IsCloserToLeafWorker(ICorDebugFrame * pFrameToCompare);
+
+ //-----------------------------------------------------------
+ // Non COM methods
+ //-----------------------------------------------------------
+
+ virtual CordbFunction *GetFunction();
+
+
+ // Accessors to return NULL or typesafe cast to derived frame
+ virtual CordbInternalFrame * GetAsInternalFrame() { return this; }
+
+ // accessor for the shim private hook code:CordbThread::ConvertFrameForILMethodWithoutMetadata
+ BOOL ConvertInternalFrameForILMethodWithoutMetadata(ICorDebugInternalFrame2 ** ppInternalFrame2);
+
+protected:
+ // the frame type
+ CorDebugInternalFrameType m_eFrameType;
+
+ // the method token of the method (if any) associated with the internal frame
+ mdMethodDef m_funcMetadataToken;
+
+ // the method (if any) associated with the internal frame
+ RSSmartPtr<CordbFunction> m_function;
+
+ VMPTR_MethodDesc m_vmMethodDesc;
+};
+
+//---------------------------------------------------------------------------------------
+//
+// This class implements ICorDebugRuntimeUnwindableFrame. It is used to mark a native stack frame
+// which requires special unwinding and which doesn't correspond to any IL code. It is really
+// just a marker to tell the debugger to use the managed unwinder. The debugger is still responsible
+// to do all the inspection and symbol lookup. An example is the hijack stub.
+//
+
+class CordbRuntimeUnwindableFrame : public CordbFrame, public ICorDebugRuntimeUnwindableFrame
+{
+public:
+ CordbRuntimeUnwindableFrame(CordbThread * pThread,
+ FramePointer fp,
+ CordbAppDomain * pCurrentAppDomain,
+ DT_CONTEXT * pContext);
+
+ virtual void Neuter();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbRuntimeUnwindableFrame"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+
+ COM_METHOD QueryInterface(REFIID riid, void ** ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugFrame
+ //-----------------------------------------------------------
+
+ //
+ // Just return E_NOTIMPL for everything.
+ // See the class comment.
+ //
+
+ COM_METHOD GetChain(ICorDebugChain ** ppChain)
+ {
+ return E_NOTIMPL;
+ }
+
+ COM_METHOD GetCode(ICorDebugCode ** ppCode)
+ {
+ return E_NOTIMPL;
+ }
+
+ COM_METHOD GetFunction(ICorDebugFunction ** ppFunction)
+ {
+ return E_NOTIMPL;
+ }
+
+ COM_METHOD GetFunctionToken(mdMethodDef * pToken)
+ {
+ return E_NOTIMPL;
+ }
+
+ COM_METHOD GetCaller(ICorDebugFrame ** ppFrame)
+ {
+ return E_NOTIMPL;
+ }
+
+ COM_METHOD GetCallee(ICorDebugFrame ** ppFrame)
+ {
+ return E_NOTIMPL;
+ }
+
+ COM_METHOD CreateStepper(ICorDebugStepper ** ppStepper)
+ {
+ return E_NOTIMPL;
+ }
+
+ COM_METHOD GetStackRange(CORDB_ADDRESS * pStart, CORDB_ADDRESS * pEnd)
+ {
+ return E_NOTIMPL;
+ }
+
+ //-----------------------------------------------------------
+ // Non COM methods
+ //-----------------------------------------------------------
+
+ virtual CordbFunction * GetFunction()
+ {
+ return NULL;
+ }
+
+ virtual const DT_CONTEXT * GetContext() const;
+
+private:
+ DT_CONTEXT m_context;
+};
+
+
+class CordbValueEnum : public CordbBase, public ICorDebugValueEnum
+{
+public:
+ enum ValueEnumMode {
+ LOCAL_VARS_ORIGINAL_IL,
+ LOCAL_VARS_REJIT_IL,
+ ARGS,
+ } ;
+
+ CordbValueEnum(CordbNativeFrame *frame, ValueEnumMode mode);
+ HRESULT Init();
+ ~CordbValueEnum();
+ virtual void Neuter();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbValueEnum"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Skip(ULONG celt);
+ COM_METHOD Reset();
+ COM_METHOD Clone(ICorDebugEnum **ppEnum);
+ COM_METHOD GetCount(ULONG *pcelt);
+
+ //-----------------------------------------------------------
+ // ICorDebugValueEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Next(ULONG celt, ICorDebugValue *values[], ULONG *pceltFetched);
+
+private:
+ CordbNativeFrame* m_frame;
+ ValueEnumMode m_mode;
+ UINT m_iCurrent;
+ UINT m_iMax;
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Misc Info for the Native Frame class
+ * ------------------------------------------------------------------------- */
+
+struct CordbMiscFrame
+{
+public:
+ CordbMiscFrame();
+
+ // new-style constructor
+ CordbMiscFrame(DebuggerIPCE_JITFuncData * pJITFuncData);
+
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+ SIZE_T parentIP;
+ FramePointer fpParentOrSelf;
+ bool fIsFilterFunclet;
+#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Native Frame class
+ * ------------------------------------------------------------------------- */
+
+class CordbNativeFrame : public CordbFrame, public ICorDebugNativeFrame, public ICorDebugNativeFrame2
+{
+public:
+ CordbNativeFrame(CordbThread * pThread,
+ FramePointer fp,
+ CordbNativeCode * pNativeCode,
+ SIZE_T ip,
+ DebuggerREGDISPLAY * pDRD,
+ TADDR addrAmbientESP,
+ bool fQuicklyUnwound,
+ CordbAppDomain * pCurrentAppDomain,
+ CordbMiscFrame * pMisc = NULL,
+ DT_CONTEXT * pContext = NULL);
+ virtual ~CordbNativeFrame();
+ virtual void Neuter();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbNativeFrame"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugFrame
+ //-----------------------------------------------------------
+
+ COM_METHOD GetChain(ICorDebugChain **ppChain)
+ {
+ return (CordbFrame::GetChain(ppChain));
+ }
+ COM_METHOD GetCode(ICorDebugCode **ppCode);
+ COM_METHOD GetFunction(ICorDebugFunction **ppFunction)
+ {
+ return (CordbFrame::GetFunction(ppFunction));
+ }
+ COM_METHOD GetFunctionToken(mdMethodDef *pToken)
+ {
+ return (CordbFrame::GetFunctionToken(pToken));
+ }
+ COM_METHOD GetCaller(ICorDebugFrame **ppFrame)
+ {
+ return (CordbFrame::GetCaller(ppFrame));
+ }
+ COM_METHOD GetCallee(ICorDebugFrame **ppFrame)
+ {
+ return (CordbFrame::GetCallee(ppFrame));
+ }
+ COM_METHOD CreateStepper(ICorDebugStepper **ppStepper)
+ {
+ return (CordbFrame::CreateStepper(ppStepper));
+ }
+
+ COM_METHOD GetStackRange(CORDB_ADDRESS *pStart, CORDB_ADDRESS *pEnd);
+
+ //-----------------------------------------------------------
+ // ICorDebugNativeFrame
+ //-----------------------------------------------------------
+
+ COM_METHOD GetIP(ULONG32* pnOffset);
+ COM_METHOD SetIP(ULONG32 nOffset);
+ COM_METHOD GetRegisterSet(ICorDebugRegisterSet **ppRegisters);
+ COM_METHOD GetLocalRegisterValue(CorDebugRegister reg,
+ ULONG cbSigBlob,
+ PCCOR_SIGNATURE pvSigBlob,
+ ICorDebugValue ** ppValue);
+
+ COM_METHOD GetLocalDoubleRegisterValue(CorDebugRegister highWordReg,
+ CorDebugRegister lowWordReg,
+ ULONG cbSigBlob,
+ PCCOR_SIGNATURE pvSigBlob,
+ ICorDebugValue ** ppValue);
+
+ COM_METHOD GetLocalMemoryValue(CORDB_ADDRESS address,
+ ULONG cbSigBlob,
+ PCCOR_SIGNATURE pvSigBlob,
+ ICorDebugValue ** ppValue);
+
+ COM_METHOD GetLocalRegisterMemoryValue(CorDebugRegister highWordReg,
+ CORDB_ADDRESS lowWordAddress,
+ ULONG cbSigBlob,
+ PCCOR_SIGNATURE pvSigBlob,
+ ICorDebugValue ** ppValue);
+
+ COM_METHOD GetLocalMemoryRegisterValue(CORDB_ADDRESS highWordAddress,
+ CorDebugRegister lowWordRegister,
+ ULONG cbSigBlob,
+ PCCOR_SIGNATURE pvSigBlob,
+ ICorDebugValue ** ppValue);
+
+ COM_METHOD CanSetIP(ULONG32 nOffset);
+
+ //-----------------------------------------------------------
+ // ICorDebugNativeFrame2
+ //-----------------------------------------------------------
+
+ COM_METHOD IsChild(BOOL * pIsChild);
+
+ COM_METHOD IsMatchingParentFrame(ICorDebugNativeFrame2 *pPotentialParentFrame,
+ BOOL * pIsParent);
+
+ COM_METHOD GetStackParameterSize(ULONG32 * pSize);
+
+ //-----------------------------------------------------------
+ // Non-COM members
+ //-----------------------------------------------------------
+
+ // Accessors to return NULL or typesafe cast to derived frame
+ virtual CordbNativeFrame * GetAsNativeFrame() { return this; }
+
+ CordbFunction * GetFunction();
+ CordbNativeCode * GetNativeCode();
+ virtual const DT_CONTEXT * GetContext() const;
+
+ // Given the native variable information of a variable, return its value.
+ // This function assumes that the value is either in a register or on the stack
+ // (i.e. VLT_REG or VLT_STK).
+ SIZE_T GetRegisterOrStackValue(const ICorDebugInfo::NativeVarInfo * pNativeVarInfo);
+
+ HRESULT GetLocalRegisterValue(CorDebugRegister reg,
+ CordbType * pType,
+ ICorDebugValue **ppValue);
+ HRESULT GetLocalDoubleRegisterValue(CorDebugRegister highWordReg,
+ CorDebugRegister lowWordReg,
+ CordbType * pType,
+ ICorDebugValue **ppValue);
+ HRESULT GetLocalMemoryValue(CORDB_ADDRESS address,
+ CordbType * pType,
+ ICorDebugValue **ppValue);
+ HRESULT GetLocalByRefMemoryValue(CORDB_ADDRESS address,
+ CordbType * pType,
+ ICorDebugValue **ppValue);
+ HRESULT GetLocalRegisterMemoryValue(CorDebugRegister highWordReg,
+ CORDB_ADDRESS lowWordAddress,
+ CordbType * pType,
+ ICorDebugValue **ppValue);
+ HRESULT GetLocalMemoryRegisterValue(CORDB_ADDRESS highWordAddress,
+ CorDebugRegister lowWordRegister,
+ CordbType * pType,
+ ICorDebugValue **ppValue);
+ UINT_PTR * GetAddressOfRegister(CorDebugRegister regNum) const;
+ CORDB_ADDRESS GetLeftSideAddressOfRegister(CorDebugRegister regNum) const;
+#if !defined(DBG_TARGET_ARM) // @ARMTODO
+ HRESULT GetLocalFloatingPointValue(DWORD index,
+ CordbType * pType,
+ ICorDebugValue **ppValue);
+#endif // !DBG_TARGET_ARM @ARMTODO
+
+
+ CORDB_ADDRESS GetLSStackAddress(ICorDebugInfo::RegNum regNum, signed offset);
+
+ bool IsLeafFrame() const;
+
+ // Return the offset used for inspection purposes.
+ // Refer to the comment at the beginning of the function definition in RsThread.cpp for more information.
+ SIZE_T GetInspectionIP();
+
+ ULONG32 GetIPOffset();
+
+ // whether this is a funclet frame
+ bool IsFunclet();
+ bool IsFilterFunclet();
+
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+ // return the offset of the parent method frame at which an exception occurs
+ SIZE_T GetParentIP();
+#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM
+
+ TADDR GetAmbientESP() { return m_taAmbientESP; }
+ TADDR GetReturnRegisterValue();
+
+ // accessor for the shim private hook code:CordbThread::ConvertFrameForILMethodWithoutMetadata
+ BOOL ConvertNativeFrameForILMethodWithoutMetadata(ICorDebugInternalFrame2 ** ppInternalFrame2);
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+public:
+ // the register set
+ DebuggerREGDISPLAY m_rd;
+
+ // This field is only true for Enter-Managed chain. It means that the register set is invalid.
+ bool m_quicklyUnwound;
+
+ // each CordbNativeFrame corresponds to exactly one CordbJITILFrame and one CordbNativeCode
+ RSSmartPtr<CordbJITILFrame> m_JITILFrame;
+ RSSmartPtr<CordbNativeCode> m_nativeCode;
+
+ // auxiliary information only used on 64-bit to find the parent stack pointer and offset for funclets
+ CordbMiscFrame m_misc;
+
+private:
+ // the ambient SP value only used on x86 to retrieve sp-relative local variables
+ // (most likely in a frameless method)
+ TADDR m_taAmbientESP;
+
+ // @dbgtodo inspection - When we DACize the various Cordb*Value classes, we should consider getting rid of the
+ // DebuggerREGDISPLAY and just use the CONTEXT. A lot of simplification can be done here.
+ DT_CONTEXT m_context;
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * CordbRegisterSet class
+ *
+ * This can be obtained via GetRegisterSet from
+ * CordbNativeFrame
+ * CordbThread
+ *
+ * ------------------------------------------------------------------------- */
+
+#define SETBITULONG64( x ) ( (ULONG64)1 << (x) )
+#define SET_BIT_MASK(_mask, _reg) (_mask[(_reg) >> 3] |= (1 << ((_reg) & 7)))
+#define RESET_BIT_MASK(_mask, _reg) (_mask[(_reg) >> 3] &= ~(1 << ((_reg) & 7)))
+#define IS_SET_BIT_MASK(_mask, _reg) (_mask[(_reg) >> 3] & (1 << ((_reg) & 7)))
+
+
+class CordbRegisterSet : public CordbBase, public ICorDebugRegisterSet, public ICorDebugRegisterSet2
+{
+public:
+ CordbRegisterSet(DebuggerREGDISPLAY * pRegDisplay,
+ CordbThread * pThread,
+ bool fActive,
+ bool fQuickUnwind,
+ bool fTakeOwnershipOfDRD = false);
+
+
+ ~CordbRegisterSet();
+
+
+
+ virtual void Neuter();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbRegisterSet"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRefEnforceExternal());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseReleaseEnforceExternal());
+ }
+
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+
+
+ //-----------------------------------------------------------
+ // ICorDebugRegisterSet
+ // More extensive explanation are in Src/inc/CorDebug.idl
+ //-----------------------------------------------------------
+ COM_METHOD GetRegistersAvailable(ULONG64 *pAvailable);
+
+ COM_METHOD GetRegisters(ULONG64 mask,
+ ULONG32 regCount,
+ CORDB_REGISTER regBuffer[]);
+ COM_METHOD SetRegisters( ULONG64 mask,
+ ULONG32 regCount,
+ CORDB_REGISTER regBuffer[])
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(regBuffer, CORDB_REGISTER,
+ regCount, true, true);
+
+ return E_NOTIMPL;
+ }
+
+ COM_METHOD GetThreadContext(ULONG32 contextSize, BYTE context[]);
+
+ // SetThreadContexthad a very problematic implementation in v1.1.
+ // We've ripped it out in V2.0 and E_NOTIMPL it. See V1.1 sources for what it used to look like
+ // in case we ever want to re-add it.
+ // If we ever re-implement it consider the following:
+ // - must fail on non-leaf frames (just check m_active).
+ // - must make sure that GetThreadContext() is fully accurate. If we don't have SetThCtx, then
+ // GetThreadCtx bugs are much more benign.
+ // - be sure to update any shared reg displays (what if a frame + chain have the same rd) and
+ // also update any cached contexts (such as CordbThread::m_context).
+ // - be sure to honor the context flags and only setting what we can set.
+ //
+ // Friday, July 16, 2004. (This date will be useful for Source control history)
+ COM_METHOD SetThreadContext(ULONG32 contextSize, BYTE context[])
+ {
+ return E_NOTIMPL;
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugRegisterSet2
+ // More extensive explanation are in Src/inc/CorDebug.idl
+ //-----------------------------------------------------------
+ COM_METHOD GetRegistersAvailable(ULONG32 regCount,
+ BYTE pAvailable[]);
+
+ COM_METHOD GetRegisters(ULONG32 maskCount,
+ BYTE mask[],
+ ULONG32 regCount,
+ CORDB_REGISTER regBuffer[]);
+
+ COM_METHOD SetRegisters(ULONG32 maskCount,
+ BYTE mask[],
+ ULONG32 regCount,
+ CORDB_REGISTER regBuffer[])
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(regBuffer, CORDB_REGISTER,
+ regCount, true, true);
+
+ return E_NOTIMPL;
+ }
+
+protected:
+ // Platform specific helper for GetThreadContext.
+ void InternalCopyRDToContext(DT_CONTEXT * pContext);
+
+ // Adapters to impl v2.0 interfaces on top of v1.0 interfaces.
+ HRESULT GetRegistersAvailableAdapter(ULONG32 regCount, BYTE pAvailable[]);
+ HRESULT GetRegistersAdapter(ULONG32 maskCount, BYTE mask[], ULONG32 regCount, CORDB_REGISTER regBuffer[]);
+
+
+ // This CordbRegisterSet is responsible to free this memory if m_fTakeOwnershipOfDRD is true. Otherwise,
+ // this memory is freed by the CordbNativeFrame or CordbThread which creates this CordbRegisterSet.
+ DebuggerREGDISPLAY *m_rd;
+ CordbThread *m_thread;
+ bool m_active; // true if we're the leafmost register set.
+ bool m_quickUnwind;
+
+ // true if the CordbRegisterSet owns the DebuggerREGDISPLAY pointer and needs to free the memory
+ bool m_fTakeOwnershipOfDRD;
+} ;
+
+
+
+
+/* ------------------------------------------------------------------------- *
+ * JIT-IL Frame class
+ * ------------------------------------------------------------------------- */
+
+class CordbJITILFrame : public CordbBase, public ICorDebugILFrame, public ICorDebugILFrame2, public ICorDebugILFrame3, public ICorDebugILFrame4
+{
+public:
+ CordbJITILFrame(CordbNativeFrame * pNativeFrame,
+ CordbILCode * pCode,
+ UINT_PTR ip,
+ CorDebugMappingResult mapping,
+ GENERICS_TYPE_TOKEN exactGenericArgsToken,
+ DWORD dwExactGenericArgsTokenIndex,
+ bool fVarArgFnx,
+ CordbReJitILCode * pReJitCode);
+ HRESULT Init();
+ virtual ~CordbJITILFrame();
+ virtual void Neuter();
+
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbJITILFrame"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugFrame
+ //-----------------------------------------------------------
+
+ COM_METHOD GetChain(ICorDebugChain **ppChain);
+ COM_METHOD GetCode(ICorDebugCode **ppCode);
+ COM_METHOD GetFunction(ICorDebugFunction **ppFunction);
+ COM_METHOD GetFunctionToken(mdMethodDef *pToken);
+ COM_METHOD GetStackRange(CORDB_ADDRESS *pStart, CORDB_ADDRESS *pEnd);
+ COM_METHOD CreateStepper(ICorDebugStepper **ppStepper);
+ COM_METHOD GetCaller(ICorDebugFrame **ppFrame);
+ COM_METHOD GetCallee(ICorDebugFrame **ppFrame);
+
+ //-----------------------------------------------------------
+ // ICorDebugILFrame
+ //-----------------------------------------------------------
+
+ COM_METHOD GetIP(ULONG32* pnOffset, CorDebugMappingResult *pMappingResult);
+ COM_METHOD SetIP(ULONG32 nOffset);
+ COM_METHOD EnumerateLocalVariables(ICorDebugValueEnum **ppValueEnum);
+ COM_METHOD GetLocalVariable(DWORD dwIndex, ICorDebugValue **ppValue);
+ COM_METHOD EnumerateArguments(ICorDebugValueEnum **ppValueEnum);
+ COM_METHOD GetArgument(DWORD dwIndex, ICorDebugValue ** ppValue);
+ COM_METHOD GetStackDepth(ULONG32 *pDepth);
+ COM_METHOD GetStackValue(DWORD dwIndex, ICorDebugValue **ppValue);
+ COM_METHOD CanSetIP(ULONG32 nOffset);
+
+ //-----------------------------------------------------------
+ // ICorDebugILFrame2
+ //-----------------------------------------------------------
+
+ // Called at an EnC remap opportunity to remap to the latest version of a function
+ COM_METHOD RemapFunction(ULONG32 nOffset);
+
+ COM_METHOD EnumerateTypeParameters(ICorDebugTypeEnum **ppTyParEnum);
+
+ //-----------------------------------------------------------
+ // ICorDebugILFrame3
+ //-----------------------------------------------------------
+
+ COM_METHOD GetReturnValueForILOffset(ULONG32 ILoffset, ICorDebugValue** ppReturnValue);
+
+ //-----------------------------------------------------------
+ // ICorDebugILFrame4
+ //-----------------------------------------------------------
+
+ COM_METHOD EnumerateLocalVariablesEx(ILCodeKind flags, ICorDebugValueEnum **ppValueEnum);
+ COM_METHOD GetLocalVariableEx(ILCodeKind flags, DWORD dwIndex, ICorDebugValue **ppValue);
+ COM_METHOD GetCodeEx(ILCodeKind flags, ICorDebugCode **ppCode);
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+
+ CordbModule *GetModule();
+
+ HRESULT GetNativeVariable(CordbType *type,
+ const ICorDebugInfo::NativeVarInfo *pNativeVarInfo,
+ ICorDebugValue **ppValue);
+
+ CordbAppDomain *GetCurrentAppDomain();
+
+ CordbFunction *GetFunction();
+
+ // ILVariableToNative serves to let the frame intercept accesses
+ // to var args variables.
+ HRESULT ILVariableToNative(DWORD dwIndex,
+ const ICorDebugInfo::NativeVarInfo ** ppNativeInfo);
+
+ // Fills in our array of var args variables
+ HRESULT FabricateNativeInfo(DWORD dwIndex,
+ const ICorDebugInfo::NativeVarInfo ** ppNativeInfo);
+
+ HRESULT GetArgumentType(DWORD dwIndex,
+ CordbType ** ppResultType);
+
+ // load the generics type and method arguments into a cache
+ void LoadGenericArgs();
+
+ HRESULT QueryInterfaceInternal(REFIID id, void** pInterface);
+
+ // Builds an generic Instaniation object from the mdClass and generic signature
+ // for what we are calling into.
+ static HRESULT BuildInstantiationForCallsite(CordbModule *pModule, NewArrayHolder<CordbType*> &types, Instantiation &inst, Instantiation *currentInstantiation, mdToken targetClass, SigParser funcGenerics);
+
+ CordbILCode* GetOriginalILCode();
+ CordbReJitILCode* GetReJitILCode();
+
+private:
+ void RefreshCachedVarArgSigParserIfNeeded();
+
+ // Worker function for GetReturnValueForILOffset.
+ HRESULT GetReturnValueForILOffsetImpl(ULONG32 ILoffset, ICorDebugValue** ppReturnValue);
+
+ // Given pType, fills ppReturnValue with the correct value.
+ HRESULT GetReturnValueForType(CordbType *pType, ICorDebugValue **ppReturnValue);
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+public:
+ // each CordbJITILFrame corresponds to exactly one CordbNativeFrame and one CordbILCode
+ CordbNativeFrame * m_nativeFrame;
+ CordbILCode * m_ilCode;
+
+ // the IL offset and the mapping result for the offset
+ UINT_PTR m_ip;
+ CorDebugMappingResult m_mapping;
+
+ // <vararg-specific fields>
+
+ // whether this is a vararg function
+ bool m_fVarArgFnx;
+
+ // the number of arguments, including the var args
+ ULONG m_allArgsCount;
+
+ // This byte array is used to store the signature for vararg methods.
+ // It points to the underlying memory used by m_sigParserCached, and it enables us to easily delete
+ // the underlying memory when the CordbJITILFrame is neutered.
+ BYTE * m_rgbSigParserBuf;
+
+ // Do not mutate this, instead make copies of it and use the copies, that way we are guaranteed to
+ // start at the correct position in the signature each time.
+ // The underlying memory used for the signature in the SigParser must not be in the DAC cache.
+ // Otherwise it may be flushed underneath us, and we would AV when we try to access it.
+ SigParser m_sigParserCached;
+
+ // the address of the first arg; only used for vararg functions
+ CORDB_ADDRESS m_FirstArgAddr;
+
+ // This is an array of variable information for the arguments.
+ // The variable information is fabricated by the RS.
+ ICorDebugInfo::NativeVarInfo * m_rgNVI;
+
+ // </vararg-specific fields>
+
+ Instantiation m_genericArgs; // the generics type arguments
+ BOOL m_genericArgsLoaded; // whether we have loaded and cached the generics type arguments
+
+ // An extra token to help fetch information about any generic
+ // parameters passed to the method, perhaps dynamically.
+ // This is the so-called generics type context/token.
+ //
+ // This token comes from the stackwalker and it may be NULL, in which case we need to retrieve the token
+ // ourselves using m_dwFrameParamsTokenIndex and the variable lifetime information.
+ GENERICS_TYPE_TOKEN m_frameParamsToken;
+
+ // IL Variable index of the Generics Arg Token.
+ DWORD m_dwFrameParamsTokenIndex;
+
+ // if this frame is instrumented with rejit, this will point to the instrumented IL code
+ RSSmartPtr<CordbReJitILCode> m_pReJitCode;
+};
+
+/* ------------------------------------------------------------------------- *
+ * Breakpoint class
+ * ------------------------------------------------------------------------- */
+
+enum CordbBreakpointType
+{
+ CBT_FUNCTION,
+ CBT_MODULE,
+ CBT_VALUE
+};
+
+class CordbBreakpoint : public CordbBase, public ICorDebugBreakpoint
+{
+public:
+ CordbBreakpoint(CordbProcess * pProcess, CordbBreakpointType bpType);
+ virtual void Neuter();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbBreakpoint"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugBreakpoint
+ //-----------------------------------------------------------
+
+ COM_METHOD BaseIsActive(BOOL *pbActive);
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+ CordbBreakpointType GetBPType()
+ {
+ return m_type;
+ }
+
+ virtual void Disconnect() {}
+
+ CordbAppDomain *GetAppDomain()
+ {
+ return m_pAppDomain;
+ }
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+public:
+ bool m_active;
+ CordbAppDomain *m_pAppDomain;
+ CordbBreakpointType m_type;
+};
+
+/* ------------------------------------------------------------------------- *
+ * Function Breakpoint class
+ * ------------------------------------------------------------------------- */
+
+class CordbFunctionBreakpoint : public CordbBreakpoint,
+ public ICorDebugFunctionBreakpoint
+{
+public:
+ CordbFunctionBreakpoint(CordbCode *code, SIZE_T offset);
+ ~CordbFunctionBreakpoint();
+
+ virtual void Neuter();
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbFunctionBreakpoint"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugBreakpoint
+ //-----------------------------------------------------------
+
+ COM_METHOD GetFunction(ICorDebugFunction **ppFunction);
+ COM_METHOD GetOffset(ULONG32 *pnOffset);
+ COM_METHOD Activate(BOOL bActive);
+ COM_METHOD IsActive(BOOL *pbActive)
+ {
+ VALIDATE_POINTER_TO_OBJECT(pbActive, BOOL *);
+
+ return BaseIsActive(pbActive);
+ }
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+
+ void Disconnect();
+
+ //-----------------------------------------------------------
+ // Convenience routines
+ //-----------------------------------------------------------
+
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+ // Get a point to the LS BP object.
+ LSPTR_BREAKPOINT GetLsPtrBP();
+public:
+
+ // We need to have a strong pointer because we may access the m_code object after we're neutered.
+ // @todo - use external pointer b/c Breakpoints aren't yet rooted, and so this reference could be
+ // leaked.
+ RSExtSmartPtr<CordbCode> m_code;
+ SIZE_T m_offset;
+};
+
+/* ------------------------------------------------------------------------- *
+ * Module Breakpoint class
+ * ------------------------------------------------------------------------- */
+
+class CordbModuleBreakpoint : public CordbBreakpoint,
+ public ICorDebugModuleBreakpoint
+{
+public:
+ CordbModuleBreakpoint(CordbModule *pModule);
+
+
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbModuleBreakpoint"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugModuleBreakpoint
+ //-----------------------------------------------------------
+
+ COM_METHOD GetModule(ICorDebugModule **ppModule);
+ COM_METHOD Activate(BOOL bActive);
+ COM_METHOD IsActive(BOOL *pbActive)
+ {
+ VALIDATE_POINTER_TO_OBJECT(pbActive, BOOL *);
+
+ return BaseIsActive(pbActive);
+ }
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+
+ void Disconnect();
+
+public:
+ CordbModule *m_module;
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Stepper class
+ * ------------------------------------------------------------------------- */
+
+class CordbStepper : public CordbBase, public ICorDebugStepper, public ICorDebugStepper2
+{
+public:
+ CordbStepper(CordbThread *thread, CordbFrame *frame = NULL);
+
+
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbStepper"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugStepper
+ //-----------------------------------------------------------
+
+ COM_METHOD IsActive(BOOL *pbActive);
+ COM_METHOD Deactivate();
+ COM_METHOD SetInterceptMask(CorDebugIntercept mask);
+ COM_METHOD SetUnmappedStopMask(CorDebugUnmappedStop mask);
+ COM_METHOD Step(BOOL bStepIn);
+ COM_METHOD StepRange(BOOL bStepIn,
+ COR_DEBUG_STEP_RANGE ranges[],
+ ULONG32 cRangeCount);
+ COM_METHOD StepOut();
+ COM_METHOD SetRangeIL(BOOL bIL);
+
+ //-----------------------------------------------------------
+ // ICorDebugStepper2
+ //-----------------------------------------------------------
+ COM_METHOD SetJMC(BOOL fIsJMCStepper);
+
+ //-----------------------------------------------------------
+ // Convenience routines
+ //-----------------------------------------------------------
+
+ CordbAppDomain *GetAppDomain()
+ {
+ return (m_thread->GetAppDomain());
+ }
+
+ LSPTR_STEPPER GetLsPtrStepper();
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+ CordbThread *m_thread;
+ CordbFrame *m_frame;
+ REMOTE_PTR m_stepperToken;
+ bool m_active;
+ bool m_rangeIL;
+ bool m_fIsJMCStepper;
+ CorDebugUnmappedStop m_rgfMappingStop;
+ CorDebugIntercept m_rgfInterceptStop;
+};
+
+#define REG_SIZE sizeof(SIZE_T)
+
+// class RegisterInfo: encapsulates information necessary to identify and access a specific register in a
+// register display
+class RegisterInfo
+{
+public:
+ // constructor for an instance of RegisterInfo
+ // Arguments:
+ // input: kNumber - value from CorDebugRegister to identify the register
+ // addr - address in remote register display that holds the value
+ // output: no out parameters, but this instance of RegisterInfo has been initialized
+ RegisterInfo(const CorDebugRegister kNumber, CORDB_ADDRESS addr, SIZE_T value):
+ m_kRegNumber((CorDebugRegister)kNumber),
+ m_regAddr(addr),
+ m_regValue(value)
+ {};
+
+
+ // copy constructor
+ // Arguments:
+ // input: regInfo - register info from which the values for this instance will come
+ // output: no out parameters, but this instance of RegisterInfo has been initialized
+ RegisterInfo(const RegisterInfo * pRegInfo):
+ m_kRegNumber(pRegInfo->m_kRegNumber),
+ m_regAddr(pRegInfo->m_regAddr),
+ m_regValue(pRegInfo->m_regValue)
+ {};
+
+
+ //-------------------------------------
+ // data members
+ //-------------------------------------
+
+ // enumeration value to identify the register, e.g., REGISTER_X86_EAX, or REGISTER_AMD64_XMM0
+ CorDebugRegister m_kRegNumber;
+
+ // address in a context or frame register display of the register value
+ CORDB_ADDRESS m_regAddr;
+
+ // the actual value of the register
+ SIZE_T m_regValue;
+}; // class RegisterInfo
+
+// class EnregisteredValueHome: abstract class to encapsulate basic information for a register value, and
+// serve as a base class for values residing in register-based locations, such as a single register, a
+// register pair, or a register and memory location.
+class EnregisteredValueHome
+{
+public:
+
+ // constructor to initialize an instance of EnregisteredValueHome
+ EnregisteredValueHome(const CordbNativeFrame * pFrame);
+
+ virtual ~EnregisteredValueHome() {}
+
+ // virtual "copy constructor" to make a copy of "this" to be owned by a different instance of
+ // Cordb*Value. If an instance of CordbVCObjectValue represents an enregistered value class, it means
+ // there is a single field. This implies that the register for the CordbVCObject instance is the same as
+ // the register for its field. When we create a Cordb*Value to represent this field, we need to make a
+ // copy of the EnregisteredValueHome belonging to the CordbVCObject instance to become the
+ // EnregisteredValueHome of the Cord*Value representing the field.
+ // returns:
+ // a new cloned copy of this object, allocated on the heap.
+ // Caller is responsible for deleting the memory (using the standard delete operator).
+ // note:
+ // C++ allows derived implementations to differ on return type, thus allowing
+ // derived impls to return the cloned copy as its actual derived type, and not just as a base type.
+
+
+ virtual
+ EnregisteredValueHome * Clone() const = 0;
+
+ // set a remote enregistered location to a new value
+ // Arguments:
+ // input: pNewValue - buffer containing the new value along with its size
+ // pContext - context from which the value comes
+ // fIsSigned - indicates whether the value is signed or not. The value provided may be smaller than
+ // a register, in which case we'll need to extend it to a full register width. To do this
+ // correctly, we need to know whether to sign extend or zero extend. Currently, only
+ // the RegValueHome virtual function uses this, but we may need it if we introduce
+ // types that don't completely occupy the size of two registers.
+ // output: updates the remote enregistered value on success
+ // Note: Throws E_FAIL for invalid input or various HRESULTs from an
+ // unsuccessful call to WriteProcessMemory
+ virtual
+ void SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pContext, bool fIsSigned) = 0;
+
+ // Gets an enregistered value and returns it to the caller
+ // Arguments:
+ // input: pValueOutBuffer - buffer in which to return the value, along with its size
+ // output: pValueOutBuffer - filled with the value
+ // Note: Throws E_NOTIMPL for attempts to get an enregistered value for a float register
+ // (implementation for derived class FloatRegValueHome)
+ virtual
+ void GetEnregisteredValue(MemoryRange valueOutBuffer) = 0;
+
+ // initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+ // instance of a derived class of EnregisteredValueHome
+ // Arguments: input: none--uses fields of "this"
+ // output: pRegAddr - address of an instance of RemoteAddress with field values set to corresponding
+ // field values of "this"
+ virtual
+ void CopyToIPCEType(RemoteAddress * pRegAddr) = 0;
+
+ // accessor
+ const CordbNativeFrame * GetFrame() const { return m_pFrame; };
+
+ //-------------------------------------
+ // data members
+ //-------------------------------------
+protected:
+ // The frame on which the value resides
+ const CordbNativeFrame * m_pFrame;
+
+}; // class EnregisteredValueHome
+
+typedef NewHolder<EnregisteredValueHome> EnregisteredValueHomeHolder;
+
+// class RegValueHome: encapsulates basic information for a value that resides in a single register
+// and serves as a base class for values residing in a register pair.
+class RegValueHome: public EnregisteredValueHome
+{
+public:
+
+ // initializing constructor
+ // Arguments:
+ // input: pFrame - frame to which the value belongs
+ // regNum - enumeration value corresponding to the particular hardware register in
+ // which the value resides
+ // regAddr - remote address within a register display (in a context or frame) of the
+ // register value
+ // output: no out parameters, but the instance has been initialized
+ RegValueHome(const CordbNativeFrame * pFrame,
+ CorDebugRegister regNum):
+ EnregisteredValueHome(pFrame),
+ m_reg1Info(regNum,
+ pFrame->GetLeftSideAddressOfRegister(regNum),
+ *(pFrame->GetAddressOfRegister(regNum)))
+ {};
+
+ // copy constructor
+ // Arguments:
+ // input: pRemoteRegAddr - instance of a remote register address from which the values for this
+ // instance will come
+ // output: no out parameters, but the instance has been initialized
+ RegValueHome(const RegValueHome * pRemoteRegAddr):
+ EnregisteredValueHome(pRemoteRegAddr->m_pFrame),
+ m_reg1Info(pRemoteRegAddr->m_reg1Info)
+ {};
+
+ // make a copy of this instance of RegValueHome
+ virtual
+ RegValueHome * Clone() const { return new RegValueHome(*this); };
+
+ // updates a register in a given context, and in the regdisplay of a given frame.
+ void SetContextRegister(DT_CONTEXT * pContext,
+ CorDebugRegister regNum,
+ SIZE_T newVal);
+
+ // set the value of a remote enregistered value
+ virtual
+ void SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pContext, bool fIsSigned);
+
+ // Gets an enregistered value and returns it to the caller
+ virtual
+ void GetEnregisteredValue(MemoryRange valueOutBuffer);
+ // initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+ // instance of a derived class of RegValueHome
+ virtual
+ void CopyToIPCEType(RemoteAddress * pRegAddr);
+
+ //-------------------------------------
+ // data members
+ //-------------------------------------
+protected:
+ // The information for the register in which the value resides.
+ const RegisterInfo m_reg1Info;
+}; // class RegValueHome
+
+// class RegRegValueHome
+// derived class to add a second register for values that live in a pair of registers
+class RegRegValueHome: public RegValueHome
+{
+public:
+ // initializing constructor
+ // Arguments:
+ // input: pFrame - frame to which the value belongs
+ // reg1Num - enumeration value corresponding to the first particular hardware register in
+ // which the value resides
+ // reg1Addr - remote address within a register display (in a context or frame) of the
+ // first register
+ // reg2Num - enumeration value corresponding to the second particular hardware register in
+ // which the value resides
+ // reg2Addr - remote address within a register display (in a context or frame) of the
+ // second register
+ // output: no out parameters, but the instance has been initialized
+ RegRegValueHome(const CordbNativeFrame * pFrame,
+ CorDebugRegister reg1Num,
+ CorDebugRegister reg2Num):
+ RegValueHome(pFrame, reg1Num),
+ m_reg2Info(reg2Num,
+ pFrame->GetLeftSideAddressOfRegister(reg2Num),
+ *(pFrame->GetAddressOfRegister(reg2Num)))
+ {};
+
+ // copy constructor
+ // Arguments:
+ // input: pRemoteRegAddr - instance of a remote register address from which the values for this
+ // instance will come
+ // output: no out parameters, but the instance has been initialized
+ RegRegValueHome(const RegRegValueHome * pRemoteRegAddr):
+ RegValueHome(pRemoteRegAddr),
+ m_reg2Info(pRemoteRegAddr->m_reg2Info)
+ {};
+
+ // make a copy of this instance of RegRegValueHome
+ virtual
+ RegRegValueHome * Clone() const { return new RegRegValueHome(*this); };
+
+ // set the value of a remote enregistered value
+ virtual
+ void SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pContext, bool fIsSigned);
+
+ // Gets an enregistered value and returns it to the caller
+ virtual
+ void GetEnregisteredValue(MemoryRange valueOutBuffer);
+
+ // initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+ // instance of a derived class of EnregisteredValueHome
+ void CopyToIPCEType(RemoteAddress * pRegAddr);
+
+ //-------------------------------------
+ // data members
+ //-------------------------------------
+
+protected:
+ // The information for the second of two registers in which the value resides.
+ const RegisterInfo m_reg2Info;
+}; // class RegRegValueHome
+
+// class RegAndMemBaseValueHome
+// derived from RegValueHome, this class is also a base class for RegMemValueHome
+// and MemRegValueHome, which add a memory location for reg-mem or mem-reg values
+class RegAndMemBaseValueHome: public RegValueHome
+{
+public:
+ // initializing constructor
+ // Arguments:
+ // input: pFrame - frame to which the value belongs
+ // reg1Num - enumeration value corresponding to the first particular hardware register in
+ // which the value resides
+ // reg1Addr - remote address within a register display (in a context or frame) of the
+ // register component of the value
+ // memAddr - remote address for the memory component of the value
+ // output: no out parameters, but the instance has been initialized
+ RegAndMemBaseValueHome(const CordbNativeFrame * pFrame,
+ CorDebugRegister reg1Num,
+ CORDB_ADDRESS memAddr):
+ RegValueHome(pFrame, reg1Num),
+ m_memAddr(memAddr)
+ {};
+
+ // copy constructor
+ // Arguments:
+ // input: pRemoteRegAddr - instance of a remote register address from which the values for this
+ // instance will come
+ // output: no out parameters, but the instance has been initialized
+ RegAndMemBaseValueHome(const RegAndMemBaseValueHome * pRemoteRegAddr):
+ RegValueHome(pRemoteRegAddr),
+ m_memAddr()
+ {};
+
+ // make a copy of this instance of RegRegValueHome
+ virtual
+ RegAndMemBaseValueHome * Clone() const = 0;
+
+ // set the value of a remote enregistered value
+ virtual
+ void SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * DT_pContext, bool fIsSigned) = 0;
+
+ // Gets an enregistered value and returns it to the caller
+ virtual
+ void GetEnregisteredValue(MemoryRange valueOutBuffer) = 0;
+
+ // initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+ // instance of a derived class of EnregisteredValueHome
+ virtual
+ void CopyToIPCEType(RemoteAddress * pRegAddr) = 0;
+
+ //-------------------------------------
+ // data members
+ //-------------------------------------
+
+protected:
+ // remote address for the memory component of the value
+ CORDB_ADDRESS m_memAddr;
+
+}; // class RegAndMemBaseValueHome;
+
+// class RegMemValueHome
+// type derived from abstract class RegAndMemBaseValueHome to represent a Register/Memory location where the
+// high order part of the value is kept in a register, and the low order part is kept in memory
+class RegMemValueHome: public RegAndMemBaseValueHome
+{
+public:
+
+ // initializing constructor
+ // Arguments:
+ // input: pFrame - frame to which the value belongs
+ // reg1Num - enumeration value corresponding to the first particular hardware register in
+ // which the value resides
+ // reg1Addr - remote address within a register display (in a context or frame) of the
+ // register component of the value
+ // memAddr - remote address for the memory component of the value
+ // output: no out parameters, but the instance has been initialized
+ RegMemValueHome(const CordbNativeFrame * pFrame,
+ CorDebugRegister reg1Num,
+ CORDB_ADDRESS memAddr):
+ RegAndMemBaseValueHome(pFrame, reg1Num, memAddr)
+ {};
+
+ // copy constructor
+ // Arguments:
+ // input: pRemoteRegAddr - instance of a remote register address from which the values for this
+ // instance will come
+ // output: no out parameters, but the instance has been initialized
+ RegMemValueHome(const RegMemValueHome * pRemoteRegAddr):
+ RegAndMemBaseValueHome(pRemoteRegAddr)
+ {};
+
+ // make a copy of this instance of RegMemValueHome
+ virtual
+ RegMemValueHome * Clone() const { return new RegMemValueHome(*this); };
+
+ // set the value of a remote enregistered value
+ virtual
+ void SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pContext, bool fIsSigned);
+
+ // Gets an enregistered value and returns it to the caller
+ virtual
+ void GetEnregisteredValue(MemoryRange valueOutBuffer);
+
+ // initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+ // instance of a derived class of EnregisteredValueHome
+ virtual
+ void CopyToIPCEType(RemoteAddress * pRegAddr);
+
+}; // class RegMemValueHome;
+
+// class MemRegValueHome
+// type derived from abstract class RegAndMemBaseValueHome to represent a Register/Memory location where the
+// low order part of the value is kept in a register, and the high order part is kept in memory
+class MemRegValueHome: public RegAndMemBaseValueHome
+{
+public:
+
+ // initializing constructor
+ // Arguments:
+ // input: pFrame - frame to which the value belongs
+ // reg1Num - enumeration value corresponding to the first particular hardware register in
+ // which the value resides
+ // reg1Addr - remote address within a register display (in a context or frame) of the
+ // register component of the value
+ // memAddr - remote address for the memory component of the value
+ // output: no out parameters, but the instance has been initialized
+ MemRegValueHome(const CordbNativeFrame * pFrame,
+ CorDebugRegister reg1Num,
+ CORDB_ADDRESS memAddr):
+ RegAndMemBaseValueHome(pFrame, reg1Num, memAddr)
+ {};
+
+ // copy constructor
+ // Arguments:
+ // input: pRemoteRegAddr - instance of a remote register address from which the values for this
+ // instance will come
+ // output: no out parameters, but the instance has been initialized
+ MemRegValueHome(const MemRegValueHome * pRemoteRegAddr):
+ RegAndMemBaseValueHome(pRemoteRegAddr)
+ {};
+
+ // make a copy of this instance of MemRegValueHome
+ virtual
+ MemRegValueHome * Clone() const { return new MemRegValueHome(*this); };
+
+ // set the value of a remote enregistered value
+ virtual
+ void SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pContext, bool fIsSigned);
+
+ // Gets an enregistered value and returns it to the caller
+ virtual
+ void GetEnregisteredValue(MemoryRange valueOutBuffer);
+
+ // initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+ // instance of a derived class of EnregisteredValueHome
+ virtual
+ void CopyToIPCEType(RemoteAddress * pRegAddr);
+
+}; // class MemRegValueHome;
+
+// class FloatRegValueHome
+// derived class to add an index into the FP register stack for a floating point value
+class FloatRegValueHome: public EnregisteredValueHome
+{
+public:
+ // initializing constructor
+ // Arguments:
+ // input: pFrame - frame to which the value belongs
+ // index - index into the floating point stack where the value resides
+ // output: no out parameters, but the instance has been initialized
+ FloatRegValueHome(const CordbNativeFrame * pFrame,
+ DWORD index):
+ EnregisteredValueHome(pFrame),
+ m_floatIndex(index)
+ {};
+
+ // copy constructor
+ // Arguments:
+ // input: pRemoteRegAddr - instance of a remote register address from which the values for this
+ // instance will come
+ // output: no out parameters, but the instance has been initialized
+ FloatRegValueHome(const FloatRegValueHome * pRemoteRegAddr):
+ EnregisteredValueHome(pRemoteRegAddr->m_pFrame),
+ m_floatIndex(pRemoteRegAddr->m_floatIndex)
+ {};
+
+ // make a copy of this instance of FloatRegValueHome
+ virtual
+ FloatRegValueHome * Clone() const { return new FloatRegValueHome(*this); };
+
+ // set the value of a remote enregistered value
+ virtual
+ void SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pContext, bool fIsSigned);
+
+ // Gets an enregistered value and returns it to the caller
+ virtual
+ void GetEnregisteredValue(MemoryRange valueOutBuffer);
+
+ // initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+ // instance of a derived class of EnregisteredValueHome
+ virtual
+ void CopyToIPCEType(RemoteAddress * pRegAddr);
+
+ //-------------------------------------
+ // data members
+ //-------------------------------------
+
+protected:
+ // index into the FP registers for the register in which the floating point value resides
+ const DWORD m_floatIndex;
+ }; // class FloatRegValueHome
+
+// ----------------------------------------------------------------------------
+// Type hierarchy for value locations
+// ValueHome
+// | | |
+// ------------------ | -------------------
+// | | |
+// RemoteValueHome RegisterValueHome HandleValueHome
+// | |
+// -------- -------
+// | |
+// VCRemoteValueHome RefRemoteValueHome
+//
+// ValueHome: abstract base class, provides remote read and write utilities
+// RemoteValueHome: used for CordbObjectValue, CordbArrayValue, and CordbBoxValue instances,
+// which have only remote locations, and for other ICDValues with a remote address
+// RegisterValueHome: used for CordbGenericValue and CordbReferenceValue instances with
+// only a register location
+// HandleValueHome: used for CordbReferenceValue instances with only an object handle
+// VCRemoteValueHome: used for CordbVCObjectValue instances to supply special operation CreateInternalValue for
+// value class objects with only a remote location
+// RefRemoteValueHome: used for CordbReferenceValue instances with only a remote location
+//
+// In addition, we have a special type for the ValueHome field for CordbReferenceValue instances:
+// RefValueHome. This will have a field of type ValueHome and will implement extra operations only relevant
+// for object references.
+//
+// ----------------------------------------------------------------------------
+//
+class ValueHome
+{
+public:
+ ValueHome(CordbProcess * pProcess):
+ m_pProcess(pProcess) { _ASSERTE(pProcess != NULL); };
+
+ virtual
+ ~ValueHome() {}
+
+ // releases resources as necessary
+ virtual
+ void Clear() = 0;
+
+ // gets the remote address for the value or returns NULL if none exists
+ virtual
+ CORDB_ADDRESS GetAddress() = 0;
+
+ // Gets a value and returns it in dest
+ // Argument:
+ // input: none (uses fields of the instance)
+ // output: dest - buffer containing the value retrieved as long as the returned HRESULT doesn't
+ // indicate an error.
+ // Note: Throws errors from read process memory operation or GetThreadContext operation
+ virtual
+ void GetValue(MemoryRange dest) = 0;
+
+ // Sets a location to the value provided in src
+ // Arguments:
+ // input: src - buffer containing the new value to be set--memory for this buffer is owned by the caller
+ // pType - type information about the value
+ // output: none, but on success, changes m_remoteValue to hold the new value
+ // Note: Throws errors from SafeWriteBuffer
+ virtual
+ void SetValue(MemoryRange src, CordbType * pType) = 0;
+
+ // creates an ICDValue for a field or array element or for the value type of a boxed object
+ // Arguments:
+ // input: pType - type of the internal value
+ // offset - offset to the internal value
+ // localAddress - address of thelogical buffer within the parent class' local cached
+ // copy that holds the internal element
+ // size - size of the internal value
+ // output: ppValue - the newly created ICDValue instance
+ // Note: Throws for a variety of possible failures: OOM, E_FAIL, errors from
+ // ReadProcessMemory.
+ virtual
+ void CreateInternalValue(CordbType * pType,
+ SIZE_T offset,
+ void * localAddress,
+ ULONG32 size,
+ ICorDebugValue ** ppValue) = 0;
+
+ // Gets the value of a field or element of an existing ICDValue instance and returns it in dest
+ // Arguments
+ // input: offset - offset within the value to the internal field or element
+ // output: dest - buffer to hold the value--memory for this buffer is owned by the caller
+ // Note: Throws process memory write errors
+ virtual
+ void GetInternalValue(MemoryRange dest, SIZE_T offset) = 0;
+
+ // copies register information from this to a RemoteAddress instance for FuncEval
+ // Arguments:
+ // output: pRegAddr - copy of information in m_pRemoteRegAddr, converted to
+ // an instance of RemoteAddress
+ virtual
+ void CopyToIPCEType(RemoteAddress * pRegAddr) = 0;
+
+private:
+ // unimplemented copy constructor to prevent passing by value
+ ValueHome(ValueHome * pValHome);
+
+protected:
+ // --------------
+ // data member
+ // --------------
+ CordbProcess * m_pProcess;
+}; // class ValueHome
+
+// ============================================================================
+// RemoteValueHome class
+// ============================================================================
+// to be used for CordbObjectValue, CordbArrayValue, and CordbBoxValue, none of which ever have anything but
+// a remote address
+class RemoteValueHome: public ValueHome
+{
+public:
+ // constructor
+ // Note: It's possible that remoteValue.pAddress may be NULL--FuncEval makes
+ // empty GenericValues for literals in which case we would have neither a remote address nor a
+ // register address
+ RemoteValueHome(CordbProcess * pProcess, TargetBuffer remoteValue);
+
+ // gets the remote address for the value
+ virtual
+ CORDB_ADDRESS GetAddress() { return m_remoteValue.pAddress; };
+
+ // releases resources as necessary
+ virtual
+ void Clear() {};
+
+ // Gets a value and returns it in dest
+ virtual
+ void GetValue(MemoryRange dest);
+
+ // Sets a location to the value provided in src
+ virtual
+ void SetValue(MemoryRange src, CordbType * pType);
+
+ // creates an ICDValue for a field or array element or for the value type of a boxed object
+ virtual
+ void CreateInternalValue(CordbType * pType,
+ SIZE_T offset,
+ void * localAddress,
+ ULONG32 size,
+ ICorDebugValue ** ppValue);
+
+ // Gets the value of a field or element of an existing ICDValue instance and returns it in dest
+ virtual
+ void GetInternalValue(MemoryRange dest, SIZE_T offset);
+
+ // copies register information from this to a RemoteAddress instance for FuncEval
+ virtual
+ void CopyToIPCEType(RemoteAddress * pRegAddr);
+
+
+ // ----------------
+ // data member
+ // ----------------
+
+protected:
+ TargetBuffer m_remoteValue;
+}; // class RemoteValueHome
+
+// ============================================================================
+// RegisterValueHome class
+// ============================================================================
+// for values that may either have a remote location or be enregistered--
+// to be used for CordbGenericValue, and as base for CordbVCObjectValue and CordbReferenceValue
+class RegisterValueHome: public ValueHome
+{
+public:
+ // constructor
+ RegisterValueHome(CordbProcess * pProcess,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr);
+
+ // clean up resources
+ virtual
+ void Clear();
+
+ // gets the remote address for the value or returns NULL if none exists
+ virtual
+ CORDB_ADDRESS GetAddress() { return NULL; };
+
+ // Gets a value and returns it in dest
+ virtual
+ void GetValue(MemoryRange dest);
+
+ // Sets a location to the value provided in src
+ virtual
+ void SetValue(MemoryRange src, CordbType * pType);
+
+ // creates an ICDValue for a field or array element or for the value type of a boxed object
+ virtual
+ void CreateInternalValue(CordbType * pType,
+ SIZE_T offset,
+ void * localAddress,
+ ULONG32 size,
+ ICorDebugValue ** ppValue);
+
+ // Gets the value of a field or element of an existing ICDValue instance and returns it in dest
+ virtual
+ void GetInternalValue(MemoryRange dest, SIZE_T offset);
+
+ // copies the register information from this to a RemoteAddress instance
+ virtual
+ void CopyToIPCEType(RemoteAddress * pRegAddr);
+
+protected:
+
+ // sets a remote enregistered location to a new value
+ void SetEnregisteredValue(MemoryRange src, bool fIsSigned);
+
+ // gets a value from an enregistered location
+ void GetEnregisteredValue(MemoryRange dest);
+
+ bool IsSigned(CorElementType elementType);
+
+ // ----------------
+ // data member
+ // ----------------
+
+protected:
+ // Left Side register location info for various kinds of (partly) enregistered values.
+ EnregisteredValueHome * m_pRemoteRegAddr;
+
+}; // class RegisterValueHome
+
+// ============================================================================
+// HandleValueHome class
+// ============================================================================
+
+class HandleValueHome: public ValueHome
+{
+public:
+ // constructor
+ // Arguments:
+ // input: pProcess - process to which the value belongs
+ // vmObjHandle - objectHandle holding the object address
+ HandleValueHome(CordbProcess * pProcess, VMPTR_OBJECTHANDLE vmObjHandle):
+ ValueHome(pProcess),
+ m_vmObjectHandle(vmObjHandle) {};
+
+ // releases resources as necessary
+ virtual
+ void Clear() {};
+
+ // gets the remote address for the value or returns NULL if none exists
+ virtual
+ CORDB_ADDRESS GetAddress();
+
+ // Gets a value and returns it in dest
+ virtual
+ void GetValue(MemoryRange dest);
+
+ // Sets a location to the value provided in src
+ virtual
+ void SetValue(MemoryRange src, CordbType * pType);
+
+ // creates an ICDValue for a field or array element or for the value type of a boxed object
+ virtual
+ void CreateInternalValue(CordbType * pType,
+ SIZE_T offset,
+ void * localAddress,
+ ULONG32 size,
+ ICorDebugValue ** ppValue);
+
+ // Gets the value of a field or element of an existing ICDValue instance and returns it in dest
+ virtual
+ void GetInternalValue(MemoryRange dest, SIZE_T offset);
+
+ // copies the register information from this to a RemoteAddress instance
+ virtual
+ void CopyToIPCEType(RemoteAddress * pRegAddr);
+
+ // ----------------
+ // data member
+ // ----------------
+private:
+ VMPTR_OBJECTHANDLE m_vmObjectHandle;
+}; // class HandleValueHome;
+
+// ============================================================================
+// VCRemoteValueHome class
+// ============================================================================
+// used only for CordbVCObjectValue
+class VCRemoteValueHome: public RemoteValueHome
+{
+public:
+ // constructor
+ VCRemoteValueHome(CordbProcess * pProcess,
+ TargetBuffer remoteValue):
+ RemoteValueHome(pProcess, remoteValue) {};
+
+ // Sets a location to the value provided in src
+ virtual
+ void SetValue(MemoryRange src, CordbType * pType);
+
+}; // class VCRemoteValueHome
+
+// ============================================================================
+// RefRemoteValueHome class
+// ============================================================================
+
+// used only for CordbReferenceValue
+class RefRemoteValueHome: public RemoteValueHome
+{
+public:
+ // constructor
+ // Arguments
+ RefRemoteValueHome(CordbProcess * pProcess,
+ TargetBuffer remoteValue);
+
+ // Sets a location to the value provided in src
+ virtual
+ void SetValue(MemoryRange src, CordbType * pType);
+
+}; // class RefRemoteValueHome
+
+// ============================================================================
+// RefValueHome class
+// ============================================================================
+
+// abstract superclass for derivations RefRemoteValueHome and RefRegValueHome
+class RefValueHome
+{
+public:
+ // constructor
+ RefValueHome() { m_pHome = NULL; m_fNullObjHandle = true; };
+
+ // constructor
+ RefValueHome(CordbProcess * pProcess,
+ TargetBuffer remoteValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr,
+ VMPTR_OBJECTHANDLE vmObjHandle);
+
+ // indicates whether the object handle is null
+ bool ObjHandleIsNull() { return m_fNullObjHandle; };
+ void SetObjHandleFlag(bool isNull) { m_fNullObjHandle = isNull; };
+
+ // ----------------
+ // data members
+ // ----------------
+ // appropriate instantiation of ValueHome
+ ValueHome * m_pHome;
+
+private:
+ // true iff m_pHome is an instantiation of RemoteValueHome or RegisterValueHome
+ bool m_fNullObjHandle;
+}; // class RefValueHome
+
+typedef enum {kUnboxed, kBoxed} BoxedValue;
+#define EMPTY_BUFFER TargetBuffer(PTR_TO_CORDB_ADDRESS((void *)NULL), 0)
+
+// for an inheritance graph of the ICDValue types, // See file:./ICorDebugValueTypes.vsd for a diagram of the types.
+/* ------------------------------------------------------------------------- *
+ * Value class
+ * ------------------------------------------------------------------------- */
+
+class CordbValue : public CordbBase
+{
+public:
+ //-----------------------------------------------------------
+ // Constructor/destructor
+ //-----------------------------------------------------------
+ CordbValue(CordbAppDomain * appdomain,
+ CordbType * type,
+ CORDB_ADDRESS id,
+ bool isLiteral,
+ NeuterList * pList = NULL);
+
+ virtual ~CordbValue();
+ virtual void Neuter();
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetType(CorElementType *pType)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pType, CorElementType *);
+
+ *pType = m_type->m_elementType;
+ return (S_OK);
+ }
+
+ COM_METHOD GetSize(ULONG32 *pSize)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pSize, ULONG32 *);
+
+ if (m_size > ULONG_MAX)
+ {
+ *pSize = ULONG_MAX;
+ return (COR_E_OVERFLOW);
+ }
+
+ *pSize = (ULONG)m_size;
+ return (S_OK);
+ }
+
+ COM_METHOD CreateBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint);
+
+ //-----------------------------------------------------------
+ // ICorDebugValue2
+ //-----------------------------------------------------------
+
+ COM_METHOD GetExactType(ICorDebugType **ppType);
+
+ //-----------------------------------------------------------
+ // ICorDebugValue3
+ //-----------------------------------------------------------
+
+ COM_METHOD GetSize64(ULONG64 *pSize)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pSize, ULONG64 *);
+
+ *pSize = m_size;
+ return (S_OK);
+ }
+
+ //-----------------------------------------------------------
+ // Methods not exported through COM
+ //-----------------------------------------------------------
+
+ // Helper for code:CordbValue::CreateValueByType. Create a new instance of CordbGenericValue
+ static
+ void CreateGenericValue(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ TargetBuffer remoteValue,
+ MemoryRange localValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr,
+ ICorDebugValue** ppValue);
+
+ // Helper for code:CordbValue::CreateValueByType. Create a new instance of CordbVCObjectValue or
+ // CordbReferenceValue
+ static
+ void CreateVCObjOrRefValue(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ bool boxed,
+ TargetBuffer remoteValue,
+ MemoryRange localValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr,
+ ICorDebugValue** ppValue);
+
+ // Create the proper ICDValue instance based on the given element type.
+ static void CreateValueByType(CordbAppDomain * appdomain,
+ CordbType * type,
+ bool boxed,
+ TargetBuffer remoteValue,
+ MemoryRange localValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr,
+ ICorDebugValue** ppValue);
+
+ // Create the proper ICDValue instance based on the given remote heap object
+ static ICorDebugValue* CreateHeapValue(CordbAppDomain* pAppDomain,
+ VMPTR_Object vmObj);
+
+
+ // Returns a pointer to the ValueHome field of this instance of CordbValue if one exists or NULL
+ // otherwise. Therefore, this also tells us indirectly whether this instance of CordbValue is also an
+ // instance of one of its derived types and thus has a ValueHome field.
+ virtual
+ ValueHome * GetValueHome() { return NULL; };
+
+ static ULONG32 GetSizeForType(CordbType * pType, BoxedValue boxing);
+
+ virtual CordbAppDomain *GetAppDomain()
+ {
+ return m_appdomain;
+ }
+
+ HRESULT InternalCreateHandle(
+ CorDebugHandleType handleType,
+ ICorDebugHandleValue ** ppHandle);
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+public:
+ CordbAppDomain * m_appdomain;
+ RSSmartPtr<CordbType> m_type;
+
+ // size of the value
+ SIZE_T m_size;
+
+ // true if the value is a RS fabrication.
+ bool m_isLiteral;
+
+};
+
+/* ------------------------------------------------------------------------- *
+ * Value Breakpoint class
+ * ------------------------------------------------------------------------- */
+
+class CordbValueBreakpoint : public CordbBreakpoint,
+ public ICorDebugValueBreakpoint
+{
+public:
+ CordbValueBreakpoint(CordbValue *pValue);
+
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbValueBreakpoint"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugValueBreakpoint
+ //-----------------------------------------------------------
+
+ COM_METHOD GetValue(ICorDebugValue **ppValue);
+ COM_METHOD Activate(BOOL bActive);
+ COM_METHOD IsActive(BOOL *pbActive)
+ {
+ VALIDATE_POINTER_TO_OBJECT(pbActive, BOOL *);
+
+ return BaseIsActive(pbActive);
+ }
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+
+ void Disconnect();
+
+public:
+ CordbValue *m_value;
+};
+
+/* ------------------------------------------------------------------------- *
+ * Generic Value class
+ * ------------------------------------------------------------------------- */
+
+class CordbGenericValue : public CordbValue, public ICorDebugGenericValue, public ICorDebugValue2, public ICorDebugValue3
+{
+public:
+ CordbGenericValue(CordbAppDomain * appdomain,
+ CordbType * type,
+ TargetBuffer remoteValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr);
+
+ CordbGenericValue(CordbType * pType);
+ // destructor
+ ~CordbGenericValue();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbGenericValue"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugValue
+ //-----------------------------------------------------------
+
+ // gets the type of the value
+ // Arguments:
+ // output: pType - the type of the value. The caller must guarantee that pType is non-null.
+ // Return Value: S_OK on success, E_INVALIDARG on failure
+ COM_METHOD GetType(CorElementType *pType)
+ {
+ return (CordbValue::GetType(pType));
+ }
+
+ // gets the size of the value
+ // Arguments:
+ // output: pSize - the size of the value. The caller must guarantee that pSize is non-null.
+ // Return Value: S_OK on success, E_INVALIDARG on failure
+ COM_METHOD GetSize(ULONG32 *pSize)
+ {
+ return (CordbValue::GetSize(pSize));
+ }
+ COM_METHOD CreateBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint)
+ {
+ return (CordbValue::CreateBreakpoint(ppBreakpoint));
+ }
+
+ // gets the remote (LS) address of the value. This may return NULL if the
+ // value is a literal or resides in a register.
+ // Arguments:
+ // output: pAddress - the address of the value. The caller must guarantee is
+ // non-Null
+ // Return Value: S_OK on success or E_INVALIDARG if pAddress is null
+ COM_METHOD GetAddress(CORDB_ADDRESS *pAddress)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pAddress, CORDB_ADDRESS *);
+
+ *pAddress = m_pValueHome ? m_pValueHome->GetAddress() : NULL;
+ return (S_OK);
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue2
+ //-----------------------------------------------------------
+
+ COM_METHOD GetExactType(ICorDebugType **ppType)
+ {
+ return (CordbValue::GetExactType(ppType));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue3
+ //-----------------------------------------------------------
+
+ COM_METHOD GetSize64(ULONG64 *pSize)
+ {
+ return (CordbValue::GetSize64(pSize));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugGenericValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetValue(void *pTo);
+ COM_METHOD SetValue(void *pFrom);
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+
+ // initialize a generic value by copying the necessary data, either
+ // from the remote process or from another value in this process.
+ void Init(MemoryRange localValue);
+ bool CopyLiteralData(BYTE *pBuffer);
+
+ // Returns a pointer to the ValueHome field
+ virtual
+ ValueHome * GetValueHome() { return m_pValueHome; };
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+private:
+ // hold copies of up to 64-bit values.
+ BYTE m_pCopyOfData[8];
+
+ // location information--remote or register address
+ ValueHome * m_pValueHome;
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Reference Value class
+ * ------------------------------------------------------------------------- */
+
+class CordbReferenceValue : public CordbValue, public ICorDebugReferenceValue, public ICorDebugValue2, public ICorDebugValue3
+{
+public:
+ CordbReferenceValue(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ MemoryRange localValue,
+ TargetBuffer remoteValue,
+ EnregisteredValueHomeHolder * ppRegAddr,
+ VMPTR_OBJECTHANDLE vmObjectHandle);
+ CordbReferenceValue(CordbType * pType);
+ virtual ~CordbReferenceValue();
+ virtual void Neuter();
+
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbReferenceValue"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetType(CorElementType *pType);
+
+ // get the size of the reference
+ // Arguments:
+ // output: pSize - the size of the value--this must be non-NULL
+ // Return Value: S_OK on success or E_INVALIDARG
+ COM_METHOD GetSize(ULONG32 *pSize)
+ {
+ return (CordbValue::GetSize(pSize));
+ }
+
+ COM_METHOD GetAddress(CORDB_ADDRESS *pAddress);
+ COM_METHOD CreateBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint)
+ {
+ return (CordbValue::CreateBreakpoint(ppBreakpoint));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue2
+ //-----------------------------------------------------------
+
+ COM_METHOD GetExactType(ICorDebugType **ppType)
+ {
+ return (CordbValue::GetExactType(ppType));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue3
+ //-----------------------------------------------------------
+
+ COM_METHOD GetSize64(ULONG64 *pSize)
+ {
+ return (CordbValue::GetSize64(pSize));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugReferenceValue
+ //-----------------------------------------------------------
+
+ COM_METHOD IsNull(BOOL * pfIsNull);
+ COM_METHOD GetValue(CORDB_ADDRESS *pAddress);
+ COM_METHOD SetValue(CORDB_ADDRESS address);
+ COM_METHOD Dereference(ICorDebugValue **ppValue);
+ COM_METHOD DereferenceStrong(ICorDebugValue **ppValue);
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+
+ // Helper function for SanityCheckPointer. Make an attempt to read memory at the address which is the
+ // value of the reference.
+ void TryDereferencingTarget();
+
+ // Do a sanity check on the pointer which is the value of the object reference. We can't efficiently
+ // ensure that the pointer is really good, so we settle for a quick check just to make sure the memory at
+ // the address is readable. We're actually just checking that we can dereference the pointer.
+ // If the address is invalid, this will throw.
+ void SanityCheckPointer (CorElementType type);
+
+ // get information about the reference when it's not an object address but another kind of pointer type:
+ // ELEMENT_TYPE_BYREF, ELEMENT_TYPE_PTR or ELEMENT_TYPE_FNPTR
+ void GetPointerData(CorElementType type, MemoryRange localValue);
+
+ // get basic object specific data when a reference points to an object, plus extra data if the object is
+ // an array or string
+ static
+ void GetObjectData(CordbProcess * pProcess,
+ void * objectAddress,
+ CorElementType type,
+ VMPTR_AppDomain vmAppdomain,
+ DebuggerIPCE_ObjectData * pInfo);
+
+ // get information about a TypedByRef object when the reference is the address of a TypedByRef structure.
+ static
+ void GetTypedByRefData(CordbProcess * pProcess,
+ CORDB_ADDRESS pTypedByRef,
+ CorElementType type,
+ VMPTR_AppDomain vmAppDomain,
+ DebuggerIPCE_ObjectData * pInfo);
+
+ // get the address of the object referenced
+ void * GetObjectAddress(MemoryRange localValue);
+
+ // update type information after initializing -- when we initialize, we may get more exact type
+ // information than we previously had
+ void UpdateTypeInfo();
+
+ // Initialize this CordbReferenceValue. This may involve inspecting the LS to get information about the
+ // referent.
+ HRESULT InitRef(MemoryRange localValue);
+
+ bool CopyLiteralData(BYTE *pBuffer);
+
+ static HRESULT Build(CordbAppDomain * appdomain,
+ CordbType * type,
+ TargetBuffer remoteValue,
+ MemoryRange localValue,
+ VMPTR_OBJECTHANDLE vmObjectHandle,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr,
+ CordbReferenceValue** ppValue);
+
+ static HRESULT BuildFromGCHandle(CordbAppDomain *pAppDomain, VMPTR_OBJECTHANDLE gcHandle, ICorDebugReferenceValue ** pOutRef);
+
+ // Common dereference routine shared by both CordbReferenceValue + CordbHandleValue
+ static HRESULT DereferenceCommon(CordbAppDomain * pAppDomain,
+ CordbType * pType,
+ CordbType * pRealTypeOfTypedByref,
+ DebuggerIPCE_ObjectData * m_pInfo,
+ ICorDebugValue ** ppValue);
+
+ // Returns a pointer to the ValueHome field
+ virtual
+ ValueHome * GetValueHome() { return m_valueHome.m_pHome; };
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+public:
+ DebuggerIPCE_ObjectData m_info;
+ CordbType * m_realTypeOfTypedByref; // weak ref
+
+ RefValueHome m_valueHome;
+
+ // Indicates when we last syncronized our stored data (m_info) from the left side
+ UINT m_continueCounterLastSync;
+};
+
+/* ------------------------------------------------------------------------- *
+ * Object Value class
+ *
+ * Because of the oddness of string objects in the Runtime we have one
+ * object that implements both ObjectValue and StringValue. There is a
+ * definite string type, but its really just an object of the string
+ * class. Furthermore, you can have a variable whose type is listed as
+ * "class", but its an instance of the string class and therefore needs
+ * to be treated like a string.
+ * ------------------------------------------------------------------------- */
+
+class CordbObjectValue : public CordbValue,
+ public ICorDebugObjectValue,
+ public ICorDebugObjectValue2,
+ public ICorDebugGenericValue,
+ public ICorDebugStringValue,
+ public ICorDebugValue2,
+ public ICorDebugValue3,
+ public ICorDebugHeapValue2,
+ public ICorDebugHeapValue3,
+ public ICorDebugExceptionObjectValue,
+ public ICorDebugComObjectValue
+{
+public:
+
+ CordbObjectValue(CordbAppDomain * appdomain,
+ CordbType * type,
+ TargetBuffer remoteValue,
+ DebuggerIPCE_ObjectData * pObjectData );
+
+ virtual ~CordbObjectValue();
+
+
+ virtual void Neuter();
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbObjectValue"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void ** ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetType(CorElementType * pType);
+ COM_METHOD GetSize(ULONG32 * pSize);
+ COM_METHOD GetAddress(CORDB_ADDRESS * pAddress);
+ COM_METHOD CreateBreakpoint(ICorDebugValueBreakpoint ** ppBreakpoint);
+
+ //-----------------------------------------------------------
+ // ICorDebugValue2
+ //-----------------------------------------------------------
+
+ COM_METHOD GetExactType(ICorDebugType ** ppType)
+ {
+ return (CordbValue::GetExactType(ppType));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue3
+ //-----------------------------------------------------------
+
+ COM_METHOD GetSize64(ULONG64 *pSize);
+
+ //-----------------------------------------------------------
+ // ICorDebugHeapValue
+ //-----------------------------------------------------------
+
+ COM_METHOD IsValid(BOOL * pfIsValid);
+ COM_METHOD CreateRelocBreakpoint(ICorDebugValueBreakpoint ** ppBreakpoint);
+
+ //-----------------------------------------------------------
+ // ICorDebugHeapValue2
+ //-----------------------------------------------------------
+ COM_METHOD CreateHandle(CorDebugHandleType type, ICorDebugHandleValue ** ppHandle);
+
+ //-----------------------------------------------------------
+ // ICorDebugHeapValue3
+ //-----------------------------------------------------------
+ COM_METHOD GetThreadOwningMonitorLock(ICorDebugThread **ppThread, DWORD *pAcquisitionCount);
+ COM_METHOD GetMonitorEventWaitList(ICorDebugThreadEnum **ppThreadEnum);
+
+ //-----------------------------------------------------------
+ // ICorDebugObjectValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetClass(ICorDebugClass ** ppClass);
+ COM_METHOD GetFieldValue(ICorDebugClass * pClass,
+ mdFieldDef fieldDef,
+ ICorDebugValue ** ppValue);
+ COM_METHOD GetVirtualMethod(mdMemberRef memberRef,
+ ICorDebugFunction **ppFunction);
+ COM_METHOD GetContext(ICorDebugContext ** ppContext);
+ COM_METHOD IsValueClass(BOOL * pfIsValueClass);
+ COM_METHOD GetManagedCopy(IUnknown ** ppObject);
+ COM_METHOD SetFromManagedCopy(IUnknown * pObject);
+
+ COM_METHOD GetFieldValueForType(ICorDebugType * pType,
+ mdFieldDef fieldDef,
+ ICorDebugValue ** ppValue);
+
+ COM_METHOD GetVirtualMethodAndType(mdMemberRef memberRef,
+ ICorDebugFunction ** ppFunction,
+ ICorDebugType ** ppType);
+
+ //-----------------------------------------------------------
+ // ICorDebugGenericValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetValue(void * pTo);
+ COM_METHOD SetValue(void * pFrom);
+
+ //-----------------------------------------------------------
+ // ICorDebugStringValue
+ //-----------------------------------------------------------
+ COM_METHOD GetLength(ULONG32 * pcchString);
+ COM_METHOD GetString(ULONG32 cchString,
+ ULONG32 * ppcchStrin,
+ __out_ecount_opt(cchString) WCHAR szString[]);
+
+ //-----------------------------------------------------------
+ // ICorDebugExceptionObjectValue
+ //-----------------------------------------------------------
+ COM_METHOD EnumerateExceptionCallStack(ICorDebugExceptionObjectCallStackEnum** ppCallStackEnum);
+
+ //-----------------------------------------------------------
+ // ICorDebugComObjectValue
+ //-----------------------------------------------------------
+ COM_METHOD GetCachedInterfaceTypes(BOOL bIInspectableOnly,
+ ICorDebugTypeEnum** ppInterfacesEnum);
+
+ COM_METHOD GetCachedInterfacePointers(BOOL bIInspectableOnly,
+ ULONG32 celt,
+ ULONG32 *pcEltFetched,
+ CORDB_ADDRESS * ptrs);
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+
+ HRESULT Init();
+
+ DebuggerIPCE_ObjectData GetInfo() { return m_info; }
+ CordbHangingFieldTable * GetHangingFieldTable() { return &m_hangingFieldsInstance; }
+
+ // Returns a pointer to the ValueHome field
+ virtual
+ RemoteValueHome * GetValueHome() { return &m_valueHome; };
+
+protected:
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+ DebuggerIPCE_ObjectData m_info;
+ BYTE * m_pObjectCopy; // local cached copy of the object
+ BYTE * m_objectLocalVars; // var base in _this_ process
+ // points _into_ m_pObjectCopy
+ BYTE * m_stringBuffer; // points _into_ m_pObjectCopy
+
+ // remote location information
+ RemoteValueHome m_valueHome;
+
+ // If instances fields are added by EnC, their storage will be off the objects
+ // syncblock. Cache per-object information about such fields here.
+ CordbHangingFieldTable m_hangingFieldsInstance;
+
+private:
+ HRESULT IsExceptionObject();
+
+ BOOL m_fIsExceptionObject;
+
+ HRESULT IsRcw();
+
+ BOOL m_fIsRcw;
+};
+
+/* ------------------------------------------------------------------------- *
+ * Value Class Object Value class
+ * ------------------------------------------------------------------------- */
+
+class CordbVCObjectValue : public CordbValue,
+ public ICorDebugObjectValue, public ICorDebugObjectValue2,
+ public ICorDebugGenericValue, public ICorDebugValue2,
+ public ICorDebugValue3
+{
+public:
+ CordbVCObjectValue(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ TargetBuffer remoteValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr);
+ virtual ~CordbVCObjectValue();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbVCObjectValue"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetType(CorElementType *pType);
+
+ COM_METHOD GetSize(ULONG32 *pSize)
+ {
+ return (CordbValue::GetSize(pSize));
+ }
+ COM_METHOD CreateBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint)
+ {
+ return (CordbValue::CreateBreakpoint(ppBreakpoint));
+ }
+
+ COM_METHOD GetAddress(CORDB_ADDRESS *pAddress)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pAddress, CORDB_ADDRESS *);
+
+ *pAddress = m_pValueHome->GetAddress();
+ return (S_OK);
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue2
+ //-----------------------------------------------------------
+
+ COM_METHOD GetExactType(ICorDebugType **ppType)
+ {
+ return (CordbValue::GetExactType(ppType));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue3
+ //-----------------------------------------------------------
+
+ COM_METHOD GetSize64(ULONG64 *pSize)
+ {
+ return (CordbValue::GetSize64(pSize));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugObjectValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetClass(ICorDebugClass **ppClass);
+ COM_METHOD GetFieldValue(ICorDebugClass *pClass,
+ mdFieldDef fieldDef,
+ ICorDebugValue **ppValue);
+ COM_METHOD GetVirtualMethod(mdMemberRef memberRef,
+ ICorDebugFunction **ppFunction);
+ COM_METHOD GetContext(ICorDebugContext **ppContext);
+ COM_METHOD IsValueClass(BOOL *pbIsValueClass);
+ COM_METHOD GetManagedCopy(IUnknown **ppObject);
+ COM_METHOD SetFromManagedCopy(IUnknown *pObject);
+ COM_METHOD GetFieldValueForType(ICorDebugType * pType,
+ mdFieldDef fieldDef,
+ ICorDebugValue ** ppValue);
+ COM_METHOD GetVirtualMethodAndType(mdMemberRef memberRef,
+ ICorDebugFunction **ppFunction,
+ ICorDebugType **ppType);
+
+ //-----------------------------------------------------------
+ // ICorDebugGenericValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetValue(void *pTo);
+ COM_METHOD SetValue(void *pFrom);
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+
+ // Initializes the Right-Side's representation of a Value Class object.
+ HRESULT Init(MemoryRange localValue);
+ //HRESULT ResolveValueClass();
+ CordbClass *GetClass();
+
+ // Returns a pointer to the ValueHome field
+ virtual
+ ValueHome * GetValueHome() { return m_pValueHome; };
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+private:
+
+ // local cached copy of the value class
+ BYTE * m_pObjectCopy;
+
+ // location information
+ ValueHome * m_pValueHome;
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Box Value class
+ * ------------------------------------------------------------------------- */
+
+class CordbBoxValue : public CordbValue,
+ public ICorDebugBoxValue,
+ public ICorDebugGenericValue,
+ public ICorDebugValue2,
+ public ICorDebugValue3,
+ public ICorDebugHeapValue2,
+ public ICorDebugHeapValue3
+{
+public:
+ CordbBoxValue(CordbAppDomain * appdomain,
+ CordbType * type,
+ TargetBuffer remoteValue,
+ ULONG32 size,
+ SIZE_T offsetToVars);
+ virtual ~CordbBoxValue();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbBoxValue"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetType(CorElementType *pType);
+
+ COM_METHOD GetSize(ULONG32 *pSize)
+ {
+ return (CordbValue::GetSize(pSize));
+ }
+ COM_METHOD CreateBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint)
+ {
+ return (CordbValue::CreateBreakpoint(ppBreakpoint));
+ }
+
+ COM_METHOD GetAddress(CORDB_ADDRESS *pAddress)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pAddress, CORDB_ADDRESS *);
+
+ *pAddress = m_valueHome.GetAddress();
+ return (S_OK);
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue2
+ //-----------------------------------------------------------
+
+ COM_METHOD GetExactType(ICorDebugType **ppType)
+ {
+ return (CordbValue::GetExactType(ppType));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue3
+ //-----------------------------------------------------------
+
+ COM_METHOD GetSize64(ULONG64 *pSize)
+ {
+ return (CordbValue::GetSize64(pSize));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugHeapValue
+ //-----------------------------------------------------------
+
+ COM_METHOD IsValid(BOOL *pbValid);
+ COM_METHOD CreateRelocBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint);
+
+ //-----------------------------------------------------------
+ // ICorDebugHeapValue2
+ //-----------------------------------------------------------
+ COM_METHOD CreateHandle(CorDebugHandleType type, ICorDebugHandleValue ** ppHandle);
+
+ //-----------------------------------------------------------
+ // ICorDebugHeapValue3
+ //-----------------------------------------------------------
+ COM_METHOD GetThreadOwningMonitorLock(ICorDebugThread **ppThread, DWORD *pAcquisitionCount);
+ COM_METHOD GetMonitorEventWaitList(ICorDebugThreadEnum **ppThreadEnum);
+
+ //-----------------------------------------------------------
+ // ICorDebugGenericValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetValue(void *pTo);
+ COM_METHOD SetValue(void *pFrom);
+
+ //-----------------------------------------------------------
+ // ICorDebugBoxValue
+ //-----------------------------------------------------------
+ COM_METHOD GetObject(ICorDebugObjectValue **ppObject);
+
+ // Returns a pointer to the ValueHome field
+ virtual
+ RemoteValueHome * GetValueHome() { return &m_valueHome; };
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+private:
+ SIZE_T m_offsetToVars;
+
+ // remote location information
+ RemoteValueHome m_valueHome;
+
+};
+
+/* ------------------------------------------------------------------------- *
+ * Array Value class
+ * ------------------------------------------------------------------------- */
+
+class CordbArrayValue : public CordbValue,
+ public ICorDebugArrayValue,
+ public ICorDebugGenericValue,
+ public ICorDebugValue2,
+ public ICorDebugValue3,
+ public ICorDebugHeapValue2,
+ public ICorDebugHeapValue3
+{
+public:
+ CordbArrayValue(CordbAppDomain * appdomain,
+ CordbType * type,
+ DebuggerIPCE_ObjectData * pObjectInfo,
+ TargetBuffer remoteValue);
+ virtual ~CordbArrayValue();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbArrayValue"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetType(CorElementType *pType)
+ {
+ return (CordbValue::GetType(pType));
+ }
+ COM_METHOD GetSize(ULONG32 *pSize)
+ {
+ return (CordbValue::GetSize(pSize));
+ }
+ COM_METHOD GetAddress(CORDB_ADDRESS *pAddress)
+ {
+ VALIDATE_POINTER_TO_OBJECT(pAddress, CORDB_ADDRESS *);
+ *pAddress = m_valueHome.GetAddress();
+ return (S_OK);
+ }
+ COM_METHOD CreateBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint)
+ {
+ return (CordbValue::CreateBreakpoint(ppBreakpoint));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue2
+ //-----------------------------------------------------------
+
+ COM_METHOD GetExactType(ICorDebugType **ppType)
+ {
+ return (CordbValue::GetExactType(ppType));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue3
+ //-----------------------------------------------------------
+
+ COM_METHOD GetSize64(ULONG64 *pSize)
+ {
+ return (CordbValue::GetSize64(pSize));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugHeapValue
+ //-----------------------------------------------------------
+
+ COM_METHOD IsValid(BOOL *pbValid);
+ COM_METHOD CreateRelocBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint);
+
+ //-----------------------------------------------------------
+ // ICorDebugHeapValue2
+ //-----------------------------------------------------------
+ COM_METHOD CreateHandle(CorDebugHandleType type, ICorDebugHandleValue ** ppHandle);
+
+ //-----------------------------------------------------------
+ // ICorDebugHeapValue3
+ //-----------------------------------------------------------
+ COM_METHOD GetThreadOwningMonitorLock(ICorDebugThread **ppThread, DWORD *pAcquisitionCount);
+ COM_METHOD GetMonitorEventWaitList(ICorDebugThreadEnum **ppThreadEnum);
+
+ //-----------------------------------------------------------
+ // ICorDebugArrayValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetElementType(CorElementType * pType);
+ COM_METHOD GetRank(ULONG32 * pnRank);
+ COM_METHOD GetCount(ULONG32 * pnCount);
+ COM_METHOD GetDimensions(ULONG32 cdim, ULONG32 dims[]);
+ COM_METHOD HasBaseIndicies(BOOL * pbHasBaseIndices);
+ COM_METHOD GetBaseIndicies(ULONG32 cdim, ULONG32 indices[]);
+ COM_METHOD GetElement(ULONG32 cdim, ULONG32 indices[], ICorDebugValue ** ppValue);
+ COM_METHOD GetElementAtPosition(ULONG32 nIndex, ICorDebugValue ** ppValue);
+
+ //-----------------------------------------------------------
+ // ICorDebugGenericValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetValue(void *pTo);
+ COM_METHOD SetValue(void *pFrom);
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+
+ HRESULT Init();
+
+ // Returns a pointer to the ValueHome field
+ virtual
+ RemoteValueHome * GetValueHome() { return &m_valueHome; };
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+private:
+ // contains information about the array, such as rank, number of elements, element size, etc.
+ DebuggerIPCE_ObjectData m_info;
+
+ // type of the elements
+ CordbType *m_elemtype;
+
+ // consists of three parts: a vector containing the lower bounds for each dimension,
+ // a vector containing the upper bounds for each dimension,
+ // a local cached copy of (part of) the array--initialized lazily when we
+ // request a particular element. If the array is large, we will store only
+ // part of it, swapping out the cached segment as necessary to retrieve
+ // requested elements.
+ BYTE * m_pObjectCopy;
+
+ // points to the beginning of the vector containing the lower bounds for each dimension in m_pObjectCopy
+ DWORD * m_arrayLowerBase;
+
+ // points to the beginning of the vector containing the lower bounds for each dimension in m_pObjectCopy
+ DWORD * m_arrayUpperBase;
+ // index of lower bound of data currently stored in m_pObjectCopy
+ SIZE_T m_idxLower;
+
+ // index of upper bound of data currently stored in m_pObjectCopy
+ SIZE_T m_idxUpper;
+
+ // remote location information
+ RemoteValueHome m_valueHome;
+
+};
+
+class CordbHandleValue : public CordbValue, public ICorDebugHandleValue, public ICorDebugValue2, public ICorDebugValue3
+{
+public:
+ CordbHandleValue(CordbAppDomain *appdomain,
+ CordbType *type,
+ CorDebugHandleType handleType);
+ HRESULT Init(VMPTR_OBJECTHANDLE pHandle);
+
+ virtual ~CordbHandleValue();
+
+ virtual void Neuter();
+ virtual void NeuterLeftSideResources();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbHandleValue"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugHandleValue interface
+ //-----------------------------------------------------------
+ COM_METHOD GetHandleType(CorDebugHandleType *pType);
+
+
+ /*
+ * The final release of the interface will also dispose of the handle. This
+ * API provides the ability for client to early dispose the handle.
+ *
+ */
+ COM_METHOD Dispose();
+
+ //-----------------------------------------------------------
+ // ICorDebugValue interface
+ //-----------------------------------------------------------
+ COM_METHOD GetType(CorElementType *pType);
+ COM_METHOD GetSize(ULONG32 *pSize);
+ COM_METHOD GetAddress(CORDB_ADDRESS *pAddress);
+ COM_METHOD CreateBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint);
+
+ //-----------------------------------------------------------
+ // ICorDebugValue2
+ //-----------------------------------------------------------
+
+ COM_METHOD GetExactType(ICorDebugType **ppType)
+ {
+ FAIL_IF_NEUTERED(this);
+
+ // If AppDomain is already unloaded, return error
+ if (m_appdomain->IsNeutered() == TRUE)
+ {
+ return COR_E_APPDOMAINUNLOADED;
+ }
+ if (m_vmHandle.IsNull())
+ {
+ return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED;
+ }
+
+ return (CordbValue::GetExactType(ppType));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue3
+ //-----------------------------------------------------------
+
+ COM_METHOD GetSize64(ULONG64 *pSize);
+
+ //-----------------------------------------------------------
+ // ICorDebugReferenceValue interface
+ //-----------------------------------------------------------
+
+ COM_METHOD IsNull(BOOL *pbNull);
+ COM_METHOD GetValue(CORDB_ADDRESS *pValue);
+ COM_METHOD SetValue(CORDB_ADDRESS value);
+ COM_METHOD Dereference(ICorDebugValue **ppValue);
+ COM_METHOD DereferenceStrong(ICorDebugValue **ppValue);
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+
+ // Returns a pointer to the ValueHome field
+ virtual
+ RemoteValueHome * GetValueHome() { return NULL; };
+
+private:
+ //BOOL RefreshHandleValue(void **pObjectToken);
+ HRESULT RefreshHandleValue();
+
+ // EE object handle pointer. Can be casted to OBJECTHANDLE when go to LS
+ // This instance owns the handle object and must call into the VM to release
+ // it.
+ // If this is non-null, then we increment code:CordbProces::IncrementOutstandingHandles.
+ // Once it goes null, we should decrement the count.
+ // Use AssignHandle, ClearHandle to keep this in sync.
+ VMPTR_OBJECTHANDLE m_vmHandle;
+
+
+ void AssignHandle(VMPTR_OBJECTHANDLE handle);
+ void ClearHandle();
+
+ BOOL m_fCanBeValid; // true if object "can" be valid. False when object is no longer valid.
+ CorDebugHandleType m_handleType; // handle type can be strong or weak
+ DebuggerIPCE_ObjectData m_info;
+; // ICORDebugClass of this object when we create the handle
+};
+
+// This class actually has the implementation for ICorDebugHeap3 interfaces. Any value which implements
+// the interface just delegates to these static calls.
+class CordbHeapValue3Impl
+{
+public:
+ static HRESULT GetThreadOwningMonitorLock(CordbProcess* pProcess,
+ CORDB_ADDRESS remoteObjAddress,
+ ICorDebugThread **ppThread,
+ DWORD *pAcquistionCount);
+ static HRESULT GetMonitorEventWaitList(CordbProcess* pProcess,
+ CORDB_ADDRESS remoteObjAddress,
+ ICorDebugThreadEnum **ppThreadEnum);
+};
+
+/* ------------------------------------------------------------------------- *
+ * Eval class
+ * ------------------------------------------------------------------------- */
+
+class CordbEval : public CordbBase, public ICorDebugEval, public ICorDebugEval2
+{
+public:
+ CordbEval(CordbThread* pThread);
+ virtual ~CordbEval();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbEval"; }
+#endif
+
+ virtual void Neuter();
+ virtual void NeuterLeftSideResources();
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugEval
+ //-----------------------------------------------------------
+
+ COM_METHOD CallFunction(ICorDebugFunction *pFunction,
+ ULONG32 nArgs,
+ ICorDebugValue *ppArgs[]);
+ COM_METHOD NewObject(ICorDebugFunction *pConstructor,
+ ULONG32 nArgs,
+ ICorDebugValue *ppArgs[]);
+ COM_METHOD NewObjectNoConstructor(ICorDebugClass *pClass);
+ COM_METHOD NewString(LPCWSTR string);
+ COM_METHOD NewArray(CorElementType elementType,
+ ICorDebugClass *pElementClass,
+ ULONG32 rank,
+ ULONG32 dims[],
+ ULONG32 lowBounds[]);
+ COM_METHOD IsActive(BOOL *pbActive);
+ COM_METHOD Abort();
+ COM_METHOD GetResult(ICorDebugValue **ppResult);
+ COM_METHOD GetThread(ICorDebugThread **ppThread);
+ COM_METHOD CreateValue(CorElementType elementType,
+ ICorDebugClass *pElementClass,
+ ICorDebugValue **ppValue);
+ COM_METHOD NewStringWithLength(LPCWSTR wszString, UINT iLength);
+
+ COM_METHOD CallParameterizedFunction(ICorDebugFunction * pFunction,
+ ULONG32 nTypeArgs,
+ ICorDebugType * rgpTypeArgs[],
+ ULONG32 nArgs,
+ ICorDebugValue * rgpArgs[]);
+
+ COM_METHOD CreateValueForType(ICorDebugType *pType,
+ ICorDebugValue **ppValue);
+
+ COM_METHOD NewParameterizedObject(ICorDebugFunction * pConstructor,
+ ULONG32 nTypeArgs,
+ ICorDebugType * rgpTypeArgs[],
+ ULONG32 nArgs,
+ ICorDebugValue * rgpArgs[]);
+
+ COM_METHOD NewParameterizedObjectNoConstructor(ICorDebugClass * pClass,
+ ULONG32 nTypeArgs,
+ ICorDebugType * rgpTypeArgs[]);
+
+ COM_METHOD NewParameterizedArray(ICorDebugType * pElementType,
+ ULONG32 rank,
+ ULONG32 dims[],
+ ULONG32 lowBounds[]);
+
+ //-----------------------------------------------------------
+ // ICorDebugEval2
+ //-----------------------------------------------------------
+
+ COM_METHOD RudeAbort();
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+ HRESULT GatherArgInfo(ICorDebugValue *pValue,
+ DebuggerIPCE_FuncEvalArgData *argData);
+ HRESULT SendCleanup();
+
+ // Create a RS literal for primitive type funceval result. In case the result is used as an argument for
+ // another funceval, we need to make sure that we're not relying on the LS value, which will be freed and
+ // thus unavailable.
+ HRESULT CreatePrimitiveLiteral(CordbType * pType,
+ ICorDebugValue ** ppValue);
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+ bool IsEvalDuringException() { return m_evalDuringException; }
+private:
+ // We must keep a strong reference to the thread so we can properly fail out of SendCleanup if someone releases an
+ // ICorDebugEval after the process has completely gone away.
+ RSSmartPtr<CordbThread> m_thread;
+
+ CordbFunction *m_function;
+ CordbClass *m_class;
+ DebuggerIPCE_FuncEvalType m_evalType;
+
+ HRESULT SendFuncEval(unsigned int genericArgsCount, ICorDebugType *genericArgs[], void *argData1, unsigned int argData1Size, void *argData2, unsigned int argData2Size, DebuggerIPCEvent * event);
+ HRESULT FilterHR(HRESULT hr);
+ BOOL DoAppDomainsMatch( CordbAppDomain* pAppDomain, ULONG32 nTypes, ICorDebugType *pTypes[], ULONG32 nValues, ICorDebugValue *pValues[] );
+
+public:
+ bool m_complete;
+ bool m_successful;
+ bool m_aborted;
+ void *m_resultAddr;
+
+ // This is an OBJECTHANDLE on the LS if func-eval creates a strong handle.
+ // This is a resource in the left-side and must be cleaned up in the left-side.
+ // This gets handled off to a CordbHandleValue (m_pHandleValue) once code:CordbEval::GetResult
+ // and then the CordbHandle is responsible for releasing it in the left-side.
+ // Issue!! This will be leaked if nobody calls GetResult().
+ VMPTR_OBJECTHANDLE m_vmObjectHandle;
+
+ // This is the corresponding cached CordbHandleValue for GetResult.
+ // This takes ownership of the strong handle, m_objectHandle.
+ // This is an External reference, which keeps the Value from being neutered
+ // on a NeuterAtWill sweep.
+ RSExtSmartPtr<CordbHandleValue> m_pHandleValue;
+
+ DebuggerIPCE_ExpandedTypeData m_resultType;
+ VMPTR_AppDomain m_resultAppDomainToken;
+
+ // Left-side memory that needs to be freed.
+ LSPTR_DEBUGGEREVAL m_debuggerEvalKey;
+
+
+ // If we're evalling during a thread's exception, remember the info so that we can restore it when we're done.
+ bool m_evalDuringException; // flag whether we're during the thread's exception.
+ VMPTR_OBJECTHANDLE m_vmThreadOldExceptionHandle; // object handle for thread's managed exception object.
+
+#ifdef _DEBUG
+ // Func-eval should perturb the the thread's current appdomain. So we remember it at start
+ // and then ensure that the func-eval complete restores it.
+ CordbAppDomain * m_DbgAppDomainStarted;
+#endif
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Win32 Event Thread class
+ * ------------------------------------------------------------------------- */
+const unsigned int CW32ET_UNKNOWN_PROCESS_SLOT = 0xFFffFFff; // it's a managed process,
+ //but we don't know which slot it's in - for Detach.
+
+//---------------------------------------------------------------------------------------
+//
+// Dedicated thread for win32 debugging operations.
+//
+// Notes:
+// This is owned by the ShimProcess object. That will both create this and destroy it.
+// OS restriction is that all win32 debugging APIs (CreateProcess, DebugActiveProcess,
+// DebugActiveProcessStop, WaitForDebugEvent, ContinueDebugEvent, etc) are on the same thread.
+//
+class CordbWin32EventThread
+{
+ friend class CordbProcess; //so that Detach can call ExitProcess
+public:
+ CordbWin32EventThread(Cordb * pCordb, ShimProcess * pShim);
+ virtual ~CordbWin32EventThread();
+
+ //
+ // You create a new instance of this class, call Init() to set it up,
+ // then call Start() start processing events. Stop() terminates the
+ // thread and deleting the instance cleans all the handles and such
+ // up.
+ //
+ HRESULT Init();
+ HRESULT Start();
+ HRESULT Stop();
+
+ HRESULT SendCreateProcessEvent(MachineInfo machineInfo,
+ LPCWSTR programName,
+ __in_z LPWSTR programArgs,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ PVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation,
+ CorDebugCreateProcessFlags corDebugFlags);
+
+ HRESULT SendDebugActiveProcessEvent(MachineInfo machineInfo,
+ DWORD pid,
+ bool fWin32Attach,
+ CordbProcess *pProcess);
+
+ HRESULT SendDetachProcessEvent(CordbProcess *pProcess);
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ HRESULT SendUnmanagedContinue(CordbProcess *pProcess,
+ EUMContinueType eContType);
+ HRESULT UnmanagedContinue(CordbProcess *pProcess,
+ EUMContinueType eContType);
+ void DoDbgContinue(CordbProcess * pProcess,
+ CordbUnmanagedEvent * pUnmanagedEvent);
+ void ForceDbgContinue(CordbProcess *pProcess,
+ CordbUnmanagedThread *ut,
+ DWORD contType,
+ bool contProcess);
+
+#endif //FEATURE_INTEROP_DEBUGGING
+
+ void LockSendToWin32EventThreadMutex()
+ {
+ LOG((LF_CORDB, LL_INFO10000, "W32ET::LockSendToWin32EventThreadMutex\n"));
+ m_sendToWin32EventThreadMutex.Lock();
+ }
+
+ void UnlockSendToWin32EventThreadMutex()
+ {
+ m_sendToWin32EventThreadMutex.Unlock();
+ LOG((LF_CORDB, LL_INFO10000, "W32ET::UnlockSendToWin32EventThreadMutex\n"));
+ }
+
+ bool IsWin32EventThread()
+ {
+ return (m_threadId == GetCurrentThreadId());
+ }
+
+ void Win32EventLoop();
+
+
+ INativeEventPipeline * GetNativePipeline();
+private:
+ void ThreadProc();
+ static DWORD WINAPI ThreadProc(LPVOID parameter);
+
+ void CreateProcess();
+
+
+ INativeEventPipeline * m_pNativePipeline;
+
+
+ void AttachProcess();
+
+ void HandleUnmanagedContinue();
+
+ void ExitProcess(bool fDetach);
+
+private:
+ RSSmartPtr<Cordb> m_cordb;
+
+ HANDLE m_thread;
+ DWORD m_threadId;
+ HANDLE m_threadControlEvent;
+ HANDLE m_actionTakenEvent;
+ BOOL m_run;
+
+ // The process that we're 1:1 with.
+ // This is set when we get a Create / Attach event.
+ // This is only used on the W32ET, which guarantees it will free of races.
+ RSSmartPtr<CordbProcess> m_pProcess;
+
+
+ ShimProcess * m_pShim;
+
+ // @todo - convert this into Stop-Go lock?
+ RSLock m_sendToWin32EventThreadMutex;
+
+ unsigned int m_action;
+ HRESULT m_actionResult;
+ union
+ {
+ struct
+ {
+ MachineInfo machineInfo;
+ LPCWSTR programName;
+ LPWSTR programArgs;
+ LPSECURITY_ATTRIBUTES lpProcessAttributes;
+ LPSECURITY_ATTRIBUTES lpThreadAttributes;
+ BOOL bInheritHandles;
+ DWORD dwCreationFlags;
+ PVOID lpEnvironment;
+ LPCWSTR lpCurrentDirectory;
+ LPSTARTUPINFOW lpStartupInfo;
+ LPPROCESS_INFORMATION lpProcessInformation;
+ CorDebugCreateProcessFlags corDebugFlags;
+ } createData;
+
+ struct
+ {
+ MachineInfo machineInfo;
+ DWORD processId;
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ bool fWin32Attach;
+#endif
+ CordbProcess *pProcess;
+
+ // Wrapper to determine if we're interop-debugging.
+ bool IsInteropDebugging()
+ {
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ return fWin32Attach;
+#else
+ return false;
+#endif
+ }
+ } attachData;
+
+ struct
+ {
+ CordbProcess *pProcess;
+ } detachData;
+
+ struct
+ {
+ CordbProcess *process;
+ EUMContinueType eContType;
+ } continueData;
+ } m_actionData;
+};
+
+
+// Thread-safe stack which.
+template <typename T>
+class InterlockedStack
+{
+public:
+ InterlockedStack();
+ ~InterlockedStack();
+
+ // Thread safe pushes + pops.
+ // Many threads can push simultaneously.
+ // Only 1 thread can pop.
+ void Push(T * pItem);
+ T * Pop();
+
+protected:
+ T * m_pHead;
+};
+
+//-----------------------------------------------------------------------------
+// Workitem to be placed on RCET worker queue.
+// There's 1 RCET for to be shared by all processes.
+//-----------------------------------------------------------------------------
+class RCETWorkItem
+{
+public:
+
+ virtual ~RCETWorkItem() {}
+
+ // Item is executed and then removed from the list and deleted.
+ virtual void Do() = 0;
+
+ CordbProcess * GetProcess() { return m_pProcess; }
+
+protected:
+ RCETWorkItem(CordbProcess * pProcess)
+ {
+ m_pProcess.Assign(pProcess);
+ m_next = NULL;
+ }
+
+ RSSmartPtr<CordbProcess> m_pProcess;
+
+ // This field is accessed by the InterlockedStack.
+ friend class InterlockedStack<RCETWorkItem>;
+ RCETWorkItem * m_next;
+};
+
+
+// Item to do Neutering work on ExitProcess.
+class ExitProcessWorkItem : public RCETWorkItem
+{
+public:
+ ExitProcessWorkItem(CordbProcess * pProc) : RCETWorkItem(pProc)
+ {
+ }
+
+ virtual void Do();
+};
+
+// Item to do send Attach event.
+class SendAttachProcessWorkItem : public RCETWorkItem
+{
+public:
+ SendAttachProcessWorkItem(CordbProcess * pProc) : RCETWorkItem(pProc)
+ {
+ }
+
+ virtual void Do();
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Runtime Controller Event Thread class
+ * ------------------------------------------------------------------------- */
+
+class CordbRCEventThread
+{
+public:
+ CordbRCEventThread(Cordb* cordb);
+ virtual ~CordbRCEventThread();
+
+ //
+ // You create a new instance of this class, call Init() to set it up,
+ // then call Start() start processing events. Stop() terminates the
+ // thread and deleting the instance cleans all the handles and such
+ // up.
+ //
+ HRESULT Init();
+ HRESULT Start();
+ HRESULT Stop();
+
+ // RCET will take ownership of this item and delete it.
+ void QueueAsyncWorkItem(RCETWorkItem * pItem);
+
+ HRESULT SendIPCEvent(CordbProcess* process,
+ DebuggerIPCEvent* event,
+ SIZE_T eventSize);
+
+ void ProcessStateChanged();
+ void FlushQueuedEvents(CordbProcess* process);
+
+ HRESULT WaitForIPCEventFromProcess(CordbProcess* process,
+ CordbAppDomain *pAppDomain,
+ DebuggerIPCEvent* event);
+
+ bool IsRCEventThread();
+
+private:
+ void DrainWorkerQueue();
+
+ void ThreadProc();
+ static DWORD WINAPI ThreadProc(LPVOID parameter);
+
+
+private:
+ InterlockedStack<class RCETWorkItem> m_WorkerStack;
+
+ RSSmartPtr<Cordb> m_cordb;
+ HANDLE m_thread;
+ DWORD m_threadId;
+ BOOL m_run;
+ HANDLE m_threadControlEvent;
+ BOOL m_processStateChanged;
+};
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+/* ------------------------------------------------------------------------- *
+ * Unmanaged Event struct
+ * ------------------------------------------------------------------------- */
+
+enum CordbUnmanagedEventState
+{
+
+ // The continued flags get set in one of a few patterns.
+ // 1) The event is continued having never been hijacked =>
+ // EventContinuedUnhijacked is set
+ // 2) The event is continued having been hijacked and then the process terminates or
+ // an error occurs before the hijack finishes =>
+ // EventContinuedHijacked is set
+ // 3) The event is continued having been hijacked, then the hijack completes and
+ // execution resumes in the debuggee
+ // EventContinuedHijacked is set
+ // EventContinuedUnhijacked is set
+
+ CUES_None = 0x00,
+ CUES_ExceptionCleared = 0x01,
+ CUES_EventContinuedHijacked = 0x02,
+ CUES_EventContinuedUnhijacked = 0x04,
+ CUES_Dispatched = 0x08,
+ CUES_ExceptionUnclearable = 0x10,
+
+ // This is set when a user continues the event by calling
+ // Continue()
+ CUES_UserContinued = 0x20,
+ // This is true if the event is an IB event
+ CUES_IsIBEvent = 0x40,
+};
+
+struct CordbUnmanagedEvent
+{
+public:
+ BOOL IsExceptionCleared() { return m_state & CUES_ExceptionCleared; }
+ BOOL IsEventContinuedHijacked() { return m_state & CUES_EventContinuedHijacked; }
+ BOOL IsEventContinuedUnhijacked() { return m_state & CUES_EventContinuedUnhijacked; }
+ BOOL IsEventUserContinued() { return m_state & CUES_UserContinued; }
+ BOOL IsEventWaitingForContinue()
+ {
+ return (!IsEventContinuedHijacked() && !IsEventContinuedUnhijacked());
+ }
+ BOOL IsDispatched() { return m_state & CUES_Dispatched; }
+ BOOL IsExceptionUnclearable() { return m_state & CUES_ExceptionUnclearable; }
+ BOOL IsIBEvent() { return m_state & CUES_IsIBEvent; }
+
+ void SetState(CordbUnmanagedEventState state) { m_state = (CordbUnmanagedEventState)(m_state | state); }
+ void ClearState(CordbUnmanagedEventState state) { m_state = (CordbUnmanagedEventState)(m_state & ~state); }
+
+ CordbUnmanagedThread *m_owner;
+ CordbUnmanagedEventState m_state;
+ DEBUG_EVENT m_currentDebugEvent;
+ CordbUnmanagedEvent *m_next;
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Unmanaged Thread class
+ * ------------------------------------------------------------------------- */
+
+enum CordbUnmanagedThreadState
+{
+ CUTS_None = 0x0000,
+ CUTS_Deleted = 0x0001,
+ CUTS_FirstChanceHijacked = 0x0002,
+ // Set when interop debugging needs the SS flag to be enabled
+ // regardless of what the user wants it to be
+ CUTS_IsSSFlagNeeded = 0x0004,
+ CUTS_GenericHijacked = 0x0008,
+ // when the m_raiseExceptionEntryContext is valid
+ CUTS_HasRaiseExceptionEntryCtx = 0x0010,
+ CUTS_BlockingForSync = 0x0020,
+ CUTS_Suspended = 0x0040,
+ CUTS_IsSpecialDebuggerThread = 0x0080,
+ // when the thread is re-executing RaiseException to retrigger an exception
+ CUTS_IsRaiseExceptionHijacked = 0x0100,
+ CUTS_HasIBEvent = 0x0200,
+ CUTS_HasOOBEvent = 0x0400,
+ CUTS_HasSpecialStackOverflowCase = 0x0800,
+#ifdef _DEBUG
+ CUTS_DEBUG_SingleStep = 0x1000,
+#endif
+ CUTS_SkippingNativePatch = 0x2000,
+ CUTS_HasContextSet = 0x4000,
+ // Set when interop debugging is making use of the single step flag
+ // but the user has not set it
+ CUTS_IsSSFlagHidden = 0x8000
+
+};
+
+class CordbUnmanagedThread : public CordbBase
+{
+public:
+ CordbUnmanagedThread(CordbProcess *pProcess, DWORD dwThreadId, HANDLE hThread, void *lpThreadLocalBase);
+ ~CordbUnmanagedThread();
+
+ using CordbBase::GetProcess;
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbUnmanagedThread"; }
+#endif
+
+ // CordbUnmanagedThread is a purely internal object. It's not exposed via ICorDebug APIs and so
+ // we should never use External AddRef.
+ ULONG STDMETHODCALLTYPE AddRef() { _ASSERTE(!"Don't use external addref on a CordbUnmanagedThread"); return (BaseAddRef());}
+ ULONG STDMETHODCALLTYPE Release() { _ASSERTE(!"Don't use external release on a CordbUnmanagedThread"); return (BaseRelease());}
+
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface)
+ {
+ _ASSERTE(!"Don't use QI on a CordbUnmanagedThread");
+ // Not really used since we never expose this class. If we ever do expose this class via the ICorDebug API then
+ // we should, of course, implement this.
+ return E_NOINTERFACE;
+ }
+
+ HRESULT LoadTLSArrayPtr();
+
+ // Hijacks this thread to a hijack worker function which recieves the current
+ // context and the provided exception record. The reason determines what code
+ // the hijack worker executes
+ HRESULT SetupFirstChanceHijack(EHijackReason::EHijackReason reason, const EXCEPTION_RECORD * pExceptionRecord);
+ HRESULT SetupFirstChanceHijackForSync();
+
+ HRESULT SetupGenericHijack(DWORD eventCode, const EXCEPTION_RECORD * pRecord);
+ HRESULT FixupFromGenericHijack();
+
+ HRESULT FixupAfterOOBException(CordbUnmanagedEvent * ue);
+
+ void SetupForSkipBreakpoint(NativePatch * pNativePatch);
+ void FixupForSkipBreakpoint();
+ bool IsCantStop();
+
+ // These are wrappers for the OS calls which hide
+ // the effects of hijacking and internal SS flag usage
+ HRESULT GetThreadContext(DT_CONTEXT * pContext);
+ HRESULT SetThreadContext(DT_CONTEXT * pContext);
+
+ // Turns on and off the internal usage of the SS flag
+ VOID BeginStepping();
+ VOID EndStepping();
+
+ // An accessor for &m_context, this value generally stores
+ // a context we may need to restore after a hijack completes
+ DT_CONTEXT * GetHijackCtx();
+
+private:
+ CORDB_ADDRESS m_stackBase;
+ CORDB_ADDRESS m_stackLimit;
+
+public:
+ BOOL GetStackRange(CORDB_ADDRESS *pBase, CORDB_ADDRESS *pLimit);
+
+ BOOL IsDeleted() {LIMITED_METHOD_CONTRACT; return m_state & CUTS_Deleted; }
+ BOOL IsFirstChanceHijacked() {LIMITED_METHOD_CONTRACT; return m_state & CUTS_FirstChanceHijacked; }
+ BOOL IsGenericHijacked() {LIMITED_METHOD_CONTRACT; return m_state & CUTS_GenericHijacked; }
+ BOOL IsBlockingForSync() {LIMITED_METHOD_CONTRACT; return m_state & CUTS_BlockingForSync; }
+ BOOL IsSuspended() {LIMITED_METHOD_CONTRACT; return m_state & CUTS_Suspended; }
+ BOOL IsSpecialDebuggerThread() {LIMITED_METHOD_CONTRACT; return m_state & CUTS_IsSpecialDebuggerThread; }
+ BOOL HasIBEvent() {LIMITED_METHOD_CONTRACT; return m_state & CUTS_HasIBEvent; }
+ BOOL HasOOBEvent() { return m_state & CUTS_HasOOBEvent; }
+ BOOL HasSpecialStackOverflowCase() {LIMITED_METHOD_CONTRACT; return m_state & CUTS_HasSpecialStackOverflowCase; }
+#ifdef _DEBUG
+ BOOL IsDEBUGTrace() { return m_state & CUTS_DEBUG_SingleStep; }
+#endif
+ BOOL IsSkippingNativePatch() { LIMITED_METHOD_CONTRACT; return m_state & CUTS_SkippingNativePatch; }
+ BOOL IsContextSet() { LIMITED_METHOD_CONTRACT; return m_state & CUTS_HasContextSet; }
+ BOOL IsSSFlagNeeded() { LIMITED_METHOD_CONTRACT; return m_state & CUTS_IsSSFlagNeeded; }
+ BOOL IsSSFlagHidden() { LIMITED_METHOD_CONTRACT; return m_state & CUTS_IsSSFlagHidden; }
+ BOOL HasRaiseExceptionEntryCtx() { LIMITED_METHOD_CONTRACT; return m_state & CUTS_HasRaiseExceptionEntryCtx; }
+ BOOL IsRaiseExceptionHijacked() { LIMITED_METHOD_CONTRACT; return m_state & CUTS_IsRaiseExceptionHijacked; }
+
+ void SetState(CordbUnmanagedThreadState state)
+ {
+ LIMITED_METHOD_CONTRACT;
+ m_state = (CordbUnmanagedThreadState)(m_state | state);
+ _ASSERTE(!IsSuspended() || !IsBlockingForSync());
+ _ASSERTE(!IsSuspended() || !IsFirstChanceHijacked());
+ }
+ void ClearState(CordbUnmanagedThreadState state) {LIMITED_METHOD_CONTRACT; m_state = (CordbUnmanagedThreadState)(m_state & ~state); }
+
+ void HijackToRaiseException();
+ void RestoreFromRaiseExceptionHijack();
+ void SaveRaiseExceptionEntryContext();
+ void ClearRaiseExceptionEntryContext();
+ BOOL IsExceptionFromLastRaiseException(const EXCEPTION_RECORD* pExceptionRecord);
+
+ CordbUnmanagedEvent *IBEvent() {LIMITED_METHOD_CONTRACT; return &m_IBEvent; }
+ CordbUnmanagedEvent *IBEvent2() {LIMITED_METHOD_CONTRACT; return &m_IBEvent2; }
+ CordbUnmanagedEvent *OOBEvent() { return &m_OOBEvent; }
+
+ DWORD GetOSTid()
+ {
+ return (DWORD) this->m_id;
+ }
+
+#ifdef DBG_TARGET_X86
+ // Stores the thread's current leaf SEH handler
+ HRESULT SaveCurrentLeafSeh();
+ // Restores the thread's leaf SEH handler from the previously saved value
+ HRESULT RestoreLeafSeh();
+#endif
+
+ // Logs basic data about a context to the debugging log
+ static VOID LogContext(DT_CONTEXT* pContext);
+
+public:
+ HANDLE m_handle;
+
+ // @dbgtodo - the TLS reading is only used for interop hijacks; which goes away in Arrowhead.
+ // Target address of the Thread Information Block (TIB).
+ void *m_threadLocalBase;
+
+ // Target address of the Thread Local Storage (TLS) array. This is for slots 0 -63.
+ void *m_pTLSArray;
+
+ // Target Address of extended Thread local Storage array. These are for slots about 63.
+ // This may be NULL if extended storage is not yet allocated.
+ void *m_pTLSExtendedArray;
+
+
+ CordbUnmanagedThreadState m_state;
+
+ CordbUnmanagedEvent m_IBEvent;
+ CordbUnmanagedEvent m_IBEvent2;
+ CordbUnmanagedEvent m_OOBEvent;
+
+ LSPTR_CONTEXT m_pLeftSideContext;
+ void *m_originalHandler;
+
+private:
+ // Spare context used for various purposes.
+ // See CordbUnmanagedThread::GetThreadContext for details
+ DT_CONTEXT m_context;
+
+ // The context of the thread the last time it called into kernel32!RaiseException
+ DT_CONTEXT m_raiseExceptionEntryContext;
+
+ DWORD m_raiseExceptionExceptionCode;
+ DWORD m_raiseExceptionExceptionFlags;
+ DWORD m_raiseExceptionNumberParameters;
+ ULONG_PTR m_raiseExceptionExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
+
+
+#ifdef DBG_TARGET_X86
+ // the SEH handler which was the leaf when SaveCurrentSeh was called (prior to hijack)
+ REMOTE_PTR m_pSavedLeafSeh;
+#endif
+
+ HRESULT EnableSSAfterBP();
+ bool GetEEThreadCantStopHelper();
+
+ DWORD_PTR GetTlsSlot(SIZE_T slot);
+ REMOTE_PTR GetPreDefTlsSlot(SIZE_T slot, bool * pRead);
+
+ void * m_pPatchSkipAddress;
+
+
+
+ /*
+ * This abstracts away an overload of the OS thread's TLS slot. In
+ * particular the runtime may or may not have created a thread object for
+ * a particular OS thread at any point.
+ *
+ * If the runtime has created a thread object, then it stores a pointer to
+ * that thread object in the thread's TLS slot.
+ *
+ * If not, then interop-debugging uses that TLS slot to store temporary
+ * information.
+ *
+ * To determine this, interop-debugging will set the low bit. Thus when
+ * we read the TLS slot, if it is non-NULL, anything w/o the low bit set
+ * is an EE thread object ptr. Anything with the low bit set is an
+ * interop-debugging value. Any NULL is null, and an indicator that
+ * there does not exist a runtime thread object for this thread yet.
+ *
+ */
+ REMOTE_PTR m_pEEThread;
+ REMOTE_PTR m_pdwTlsValue;
+ BOOL m_fValidTlsData;
+
+ UINT m_continueCountCached;
+
+ void CacheEEDebuggerWord();
+ HRESULT SetEEThreadValue(REMOTE_PTR EETlsValue);
+#ifdef FEATURE_IMPLICIT_TLS
+ DWORD_PTR GetEEThreadValue();
+ REMOTE_PTR GetClrModuleTlsDataAddress();
+ REMOTE_PTR GetEETlsDataBlock();
+#endif
+
+public:
+ HRESULT GetEEDebuggerWord(REMOTE_PTR *pValue);
+ HRESULT SetEEDebuggerWord(REMOTE_PTR value);
+ HRESULT GetEEThreadPtr(REMOTE_PTR *ppEEThread);
+
+ bool GetEEPGCDisabled();
+ void GetEEState(bool *threadStepping, bool *specialManagedException);
+ bool GetEEFrame();
+};
+#endif // FEATURE_INTEROP_DEBUGGING
+
+
+//********************************************************************************
+//**************** App Domain Publishing Service API *****************************
+//********************************************************************************
+
+
+class EnumElement
+{
+public:
+ EnumElement()
+ {
+ m_pData = NULL;
+ m_pNext = NULL;
+ }
+
+ void SetData (void *pData) { m_pData = pData;}
+ void *GetData () { return m_pData;}
+ void SetNext (EnumElement *pNext) { m_pNext = pNext;}
+ EnumElement *GetNext () { return m_pNext;}
+
+private:
+ void *m_pData;
+ EnumElement *m_pNext;
+};
+
+#if defined(FEATURE_DBG_PUBLISH)
+
+// Prototype of psapi!GetModuleFileNameEx.
+typedef DWORD FPGetModuleFileNameEx(HANDLE, HMODULE, LPTSTR, DWORD);
+
+
+class CorpubPublish : public CordbCommonBase, public ICorPublish
+{
+public:
+ CorpubPublish();
+ virtual ~CorpubPublish();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbPublish"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorPublish
+ //-----------------------------------------------------------
+
+ COM_METHOD EnumProcesses(
+ COR_PUB_ENUMPROCESS Type,
+ ICorPublishProcessEnum **ppIEnum);
+
+ COM_METHOD GetProcess(
+ unsigned pid,
+ ICorPublishProcess **ppProcess);
+
+ //-----------------------------------------------------------
+ // CreateObject
+ //-----------------------------------------------------------
+ static COM_METHOD CreateObject(REFIID id, void **object)
+ {
+ *object = NULL;
+
+ if (id != IID_IUnknown && id != IID_ICorPublish)
+ return (E_NOINTERFACE);
+
+ CorpubPublish *pCorPub = new (nothrow) CorpubPublish();
+
+ if (pCorPub == NULL)
+ return (E_OUTOFMEMORY);
+
+ *object = (ICorPublish*)pCorPub;
+ pCorPub->AddRef();
+
+ return (S_OK);
+ }
+
+private:
+ HRESULT GetProcessInternal( unsigned pid, CorpubProcess **ppProcess );
+
+ // Cached information to get the process name. Not available on all platforms, so may be null.
+ HModuleHolder m_hPSAPIdll;
+ FPGetModuleFileNameEx * m_fpGetModuleFileNameEx;
+};
+
+class CorpubProcess : public CordbCommonBase, public ICorPublishProcess
+{
+public:
+ CorpubProcess(DWORD dwProcessId,
+ bool fManaged,
+ HANDLE hProcess,
+ HANDLE hMutex,
+ AppDomainEnumerationIPCBlock *pAD,
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ IPCReaderInterface *pIPCReader,
+#endif // !FEATURE_DBGIPC_TRANSPORT_DI
+ FPGetModuleFileNameEx * fpGetModuleFileNameEx);
+ virtual ~CorpubProcess();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CorpubProcess"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorPublishProcess
+ //-----------------------------------------------------------
+ COM_METHOD IsManaged(BOOL *pbManaged);
+
+ /*
+ * Enumerate the list of known application domains in the target process.
+ */
+ COM_METHOD EnumAppDomains(ICorPublishAppDomainEnum **ppEnum);
+
+ /*
+ * Returns the OS ID for the process in question.
+ */
+ COM_METHOD GetProcessID(unsigned *pid);
+
+ /*
+ * Get the display name for a process.
+ */
+ COM_METHOD GetDisplayName(ULONG32 cchName,
+ ULONG32 *pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+
+ CorpubProcess *GetNextProcess () { return m_pNext;}
+ void SetNext (CorpubProcess *pNext) { m_pNext = pNext;}
+
+ // Helper to tell if this process has exited
+ bool IsExited();
+
+public:
+ DWORD m_dwProcessId;
+
+private:
+ bool m_fIsManaged;
+ HANDLE m_hProcess;
+ HANDLE m_hMutex;
+ AppDomainEnumerationIPCBlock *m_AppDomainCB;
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ IPCReaderInterface *m_pIPCReader; // controls the lifetime of the AppDomainEnumerationIPCBlock
+#endif // !FEATURE_DBGIPC_TRANSPORT_DI
+ CorpubProcess *m_pNext; // pointer to the next process in the process list
+ WCHAR *m_szProcessName;
+
+};
+
+class CorpubAppDomain : public CordbCommonBase, public ICorPublishAppDomain
+{
+public:
+ CorpubAppDomain (__in LPWSTR szAppDomainName, ULONG Id);
+ virtual ~CorpubAppDomain();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CorpubAppDomain"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface (REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorPublishAppDomain
+ //-----------------------------------------------------------
+
+ /*
+ * Get the name and ID for an application domain.
+ */
+ COM_METHOD GetID (ULONG32 *pId);
+
+ /*
+ * Get the name for an application domain.
+ */
+ COM_METHOD GetName (ULONG32 cchName,
+ ULONG32 *pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+
+ CorpubAppDomain *GetNextAppDomain () { return m_pNext;}
+ void SetNext (CorpubAppDomain *pNext) { m_pNext = pNext;}
+
+private:
+ CorpubAppDomain *m_pNext;
+ WCHAR *m_szAppDomainName;
+ ULONG m_id;
+
+};
+
+class CorpubProcessEnum : public CordbCommonBase, public ICorPublishProcessEnum
+{
+public:
+ CorpubProcessEnum(CorpubProcess *pFirst);
+ virtual ~CorpubProcessEnum();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CorpubProcessEnum"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorPublishProcessEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Skip(ULONG celt);
+ COM_METHOD Reset();
+ COM_METHOD Clone(ICorPublishEnum **ppEnum);
+ COM_METHOD GetCount(ULONG *pcelt);
+ COM_METHOD Next(ULONG celt,
+ ICorPublishProcess *objects[],
+ ULONG *pceltFetched);
+
+private:
+ CorpubProcess *m_pFirst;
+ CorpubProcess *m_pCurrent;
+
+};
+
+class CorpubAppDomainEnum : public CordbCommonBase, public ICorPublishAppDomainEnum
+{
+public:
+ CorpubAppDomainEnum(CorpubAppDomain *pFirst);
+ virtual ~CorpubAppDomainEnum();
+
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbAppDomainEnum"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorPublishAppDomainEnum
+ //-----------------------------------------------------------
+ COM_METHOD Skip(ULONG celt);
+ COM_METHOD Reset();
+ COM_METHOD Clone(ICorPublishEnum **ppEnum);
+ COM_METHOD GetCount(ULONG *pcelt);
+
+ COM_METHOD Next(ULONG celt,
+ ICorPublishAppDomain *objects[],
+ ULONG *pceltFetched);
+
+private:
+ CorpubAppDomain *m_pFirst;
+ CorpubAppDomain *m_pCurrent;
+
+};
+
+#endif // defined(FEATURE_DBG_PUBLISH)
+
+class CordbHeapEnum : public CordbBase, public ICorDebugHeapEnum
+{
+public:
+ CordbHeapEnum(CordbProcess *proc);
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbHeapEnum"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ COM_METHOD Skip(ULONG celt);
+ COM_METHOD Reset();
+ COM_METHOD Clone(ICorDebugEnum **ppEnum);
+ COM_METHOD GetCount(ULONG *pcelt);
+
+ COM_METHOD Next(ULONG celt,
+ COR_HEAPOBJECT objects[],
+ ULONG *pceltFetched);
+
+ virtual void Neuter()
+ {
+ Clear();
+ CordbBase::Neuter();
+ }
+private:
+ void Clear();
+
+private:
+ IDacDbiInterface::HeapWalkHandle mHeapHandle;
+};
+
+
+class CordbRefEnum : public CordbBase, public ICorDebugGCReferenceEnum
+{
+public:
+ CordbRefEnum(CordbProcess *proc, BOOL walkWeakRefs);
+ CordbRefEnum(CordbProcess *proc, CorGCReferenceType types);
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbHeapEnum"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ COM_METHOD Skip(ULONG celt);
+ COM_METHOD Reset();
+ COM_METHOD Clone(ICorDebugEnum **ppEnum);
+ COM_METHOD GetCount(ULONG *pcelt);
+
+ COM_METHOD Next(ULONG celt,
+ COR_GC_REFERENCE refs[],
+ ULONG *pceltFetched);
+
+ virtual void Neuter();
+
+private:
+ RefWalkHandle mRefHandle;
+ BOOL mEnumStacksFQ;
+ UINT32 mHandleMask;
+};
+
+// Since the hash table of modules is per app domain (and
+// threads is per process) (for fast lookup from the appdomain/process),
+// we need this wrapper
+// here which allows us to iterate through an assembly's
+// modules. Is basically filters out modules/threads that aren't
+// in the assembly/appdomain. This slow & awkward for assemblies, but fast
+// for the common case - appdomain lookup.
+class CordbEnumFilter : public CordbBase,
+ public ICorDebugThreadEnum,
+ public ICorDebugModuleEnum
+{
+public:
+ CordbEnumFilter(CordbBase * pOwnerObj, NeuterList * pOwnerList);
+ CordbEnumFilter(CordbEnumFilter*src);
+ virtual ~CordbEnumFilter();
+
+ virtual void Neuter();
+
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbEnumFilter"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // Common methods
+ //-----------------------------------------------------------
+ COM_METHOD Skip(ULONG celt);
+ COM_METHOD Reset();
+ COM_METHOD Clone(ICorDebugEnum **ppEnum);
+ COM_METHOD GetCount(ULONG *pcelt);
+ //-----------------------------------------------------------
+ // ICorDebugModuleEnum
+ //-----------------------------------------------------------
+ COM_METHOD Next(ULONG celt,
+ ICorDebugModule *objects[],
+ ULONG *pceltFetched);
+
+ //-----------------------------------------------------------
+ // ICorDebugThreadEnum
+ //-----------------------------------------------------------
+ COM_METHOD Next(ULONG celt,
+ ICorDebugThread *objects[],
+ ULONG *pceltFetched);
+
+ HRESULT Init (ICorDebugModuleEnum *pModEnum, CordbAssembly *pAssembly);
+ HRESULT Init (ICorDebugThreadEnum *pThreadEnum, CordbAppDomain *pAppDomain);
+
+
+private:
+ HRESULT NextWorker(ULONG celt, ICorDebugModule *objects[], ULONG *pceltFetched);
+ HRESULT NextWorker(ULONG celt,ICorDebugThread *objects[], ULONG *pceltFetched);
+
+ // Owning object is our link to the CordbProcess* tree. Never null until we're neutered.
+ // NeuterList is related to the owning object. Need to cache it so that we can pass it on
+ // to our clones.
+ CordbBase * m_pOwnerObj; // provides us w/ a CordbProcess*
+ NeuterList * m_pOwnerNeuterList;
+
+
+ EnumElement *m_pFirst;
+ EnumElement *m_pCurrent;
+ int m_iCount;
+};
+
+// Helpers to double-check the RS results against DAC.
+#if defined(_DEBUG)
+void CheckAgainstDAC(CordbFunction * pFunc, void * pIP, mdMethodDef mdExpected);
+#endif
+
+HRESULT CopyOutString(const WCHAR * pInputString, ULONG32 cchName, ULONG32 * pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+
+
+
+inline UINT AllocCookieCordbEval(CordbProcess *pProc, CordbEval* p)
+{
+ _ASSERTE(pProc->GetProcessLock()->HasLock());
+ return pProc->m_EvalTable.Add(p);
+}
+inline CordbEval * UnwrapCookieCordbEval(CordbProcess *pProc, UINT cookie)
+{
+ _ASSERTE(pProc->GetProcessLock()->HasLock());
+ return pProc->m_EvalTable.LookupAndRemove(cookie);
+}
+
+
+// We defined this at the top of the file - undef it now so that we don't pollute other files.
+#undef CRITICAL_SECTION
+
+
+#ifdef RSCONTRACTS
+
+//-----------------------------------------------------------------------------
+// For debug builds, we maintain some thread-state to track debug bits
+// to help us do some more aggressive asserts.
+//-----------------------------------------------------------------------------
+
+class PublicAPIHolder;
+class PublicReentrantAPIHolder;
+class PublicCallbackHolder;
+class PublicDebuggerErrorCallbackHolder;
+
+class DbgRSThread
+{
+public:
+ friend class PublicAPIHolder;
+ friend class PublicReentrantAPIHolder;
+ friend class PublicCallbackHolder;
+ friend class PublicDebuggerErrorCallbackHolder;
+ friend class PrivateShimCallbackHolder;
+
+ DbgRSThread();
+
+ // The TLS slot that we'll put this thread object in.
+ static DWORD s_TlsSlot;
+
+ static LONG s_Total; // Total count of thread objects
+
+ // Get a thread object for the current thread via a TLS lookup.
+ static DbgRSThread * GetThread();
+
+ // Call during DllMain to release this.
+ static DbgRSThread * Create()
+ {
+ InterlockedIncrement(&s_Total);
+
+ DbgRSThread * p = new (nothrow) DbgRSThread();
+ BOOL f = TlsSetValue(s_TlsSlot, p);
+ _ASSERT(f);
+ return p;
+ }
+
+ void Destroy()
+ {
+ InterlockedDecrement(&s_Total);
+
+ BOOL f = TlsSetValue(s_TlsSlot, NULL);
+ _ASSERT(f);
+
+ delete this;
+ }
+
+ // Return true if this thread is inside the RS.
+ bool IsInRS() { return m_cInsideRS > 0; }
+
+ // Locking API..
+ // These will assert if the operation is unsafe.
+ void NotifyTakeLock(RSLock * pLock);
+ void NotifyReleaseLock(RSLock * pLock);
+
+ // Used to map other resources (like thread access) into the lock hierachy.
+ // Note this only effects lock leveling checks and doesn't effect HoldsAnyLock().
+ void TakeVirtualLock(RSLock::ERSLockLevel level);
+ void ReleaseVirtualLock(RSLock::ERSLockLevel level);
+
+ // return true if this thread is holding any RS locks. Useful to check on Public API transition boundaries.
+ bool HoldsAnyDbgApiLocks() { return m_cTotalDbgApiLocks > 0; }
+
+ enum EThreadType
+ {
+ cOther,
+ cW32ET
+ };
+ void SetThreadType(EThreadType e) { m_eThreadType = e; }
+
+ bool IsWin32EventThread() { return m_eThreadType == cW32ET; }
+
+ void SetUnrecoverableCallback(bool fIsUnrecoverableErrorCallback)
+ {
+ // Not reentrant.
+ _ASSERTE(m_fIsUnrecoverableErrorCallback != fIsUnrecoverableErrorCallback);
+
+ m_fIsUnrecoverableErrorCallback = fIsUnrecoverableErrorCallback;
+ }
+
+ inline void AssertThreadIsLockFree()
+ {
+ // If we're in an unrecoverable callback, we may hold locks.
+ _ASSERTE(m_fIsUnrecoverableErrorCallback
+ || !HoldsAnyDbgApiLocks() ||
+ !"Thread should not have locks on public/internal transition");
+ }
+
+protected:
+ EThreadType m_eThreadType;
+
+ // More debugging tidbits - tid that we're on, and a sanity checking cookie.
+ DWORD m_tid;
+ DWORD m_Cookie;
+
+ enum ECookie
+ {
+ COOKIE_VALUE = 0x12345678
+ };
+
+
+ // This tells us if the thread is currently in the scope of a PublicAPIHolder.
+ int m_cInsideRS;
+
+ // This tells us if a thread is currently being dispatched via a callback.
+ bool m_fIsInCallback;
+
+ // We explicitly track if this thread is in an unrecoverable error callback
+ // b/c that will weaken some other asserts.
+ // It would be nice to clean up the unrecoverable error callback and have it
+ // behave like all the other callbacks. Then we can remove this.
+ bool m_fIsUnrecoverableErrorCallback;
+
+ // Locking context. Used to tell what levels of locks we hold so we can determine if a lock is safe to take.
+ int m_cLocks[RSLock::LL_MAX];
+ int m_cTotalDbgApiLocks;
+};
+
+//-----------------------------------------------------------------------------
+// Mark when we enter / exit public APIs
+//-----------------------------------------------------------------------------
+
+// Holder for Non-reentrant Public API (this is the vast majority)
+class PublicAPIHolder
+{
+public:
+ PublicAPIHolder()
+ {
+ // on entry
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+ pThread->m_cInsideRS++;
+ _ASSERTE(pThread->m_cInsideRS == 1 || !"Non-reentrant API being called re-entrantly");
+
+ // Should never be in public w/ these locks
+ pThread->AssertThreadIsLockFree();
+ }
+ ~PublicAPIHolder() {
+ // On exit.
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+ pThread->m_cInsideRS--;
+ _ASSERTE(!pThread->IsInRS());
+
+ // Should never be in public w/ these locks. If we assert here,
+ // then we're leaking locks.
+ pThread->AssertThreadIsLockFree();
+ }
+};
+
+// Holder for reentrant public API
+class PublicReentrantAPIHolder
+{
+public:
+ PublicReentrantAPIHolder()
+ {
+ // on entry
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+ pThread->m_cInsideRS++;
+
+ // Cache count now so that we can calidate it in the dtor.
+ m_oldCount = pThread->m_cInsideRS;
+ // Since a we may have been called from within the RS, we may hold locks
+ }
+ ~PublicReentrantAPIHolder()
+ {
+
+ // On exit.
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+
+ // Ensure that our children were balanced
+ _ASSERTE(pThread->m_cInsideRS == m_oldCount);
+
+ pThread->m_cInsideRS--;
+ _ASSERTE(pThread->m_cInsideRS >= 0);
+
+ // Since a we may have been called from within the RS, we may hold locks
+ }
+private:
+ int m_oldCount;
+};
+
+// Special holder for DebuggerError callback. This adjusts InsideRS count w/o
+// verifying locks. This is very dangerous. We allow this b/c the Debugger Error callback can come at any time.
+class PublicDebuggerErrorCallbackHolder
+{
+public:
+ PublicDebuggerErrorCallbackHolder()
+ {
+ // Exiting from RS; entering Cordbg via a callback
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+
+ // This callback is called from within the RS
+ _ASSERTE(pThread->IsInRS());
+
+ // Debugger error callback may be called from deep within the RS (after many nestings).
+ // So immediately jump to outside. We'll restore this in dtor.
+ m_oldCount = pThread->m_cInsideRS;
+ pThread->m_cInsideRS = 0;
+
+ _ASSERTE(!pThread->IsInRS());
+
+ // We may be leaking locks for the unrecoverable callback. We mark that so that
+ // the asserts about locking can be relaxed.
+ pThread->SetUnrecoverableCallback(true);
+ }
+
+ ~PublicDebuggerErrorCallbackHolder()
+ {
+ // Re-entering RS from after a callback.
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+
+ pThread->SetUnrecoverableCallback(false);
+ pThread->m_cInsideRS = m_oldCount;
+
+ // Our status of being "Inside the RS" is now restored.
+ _ASSERTE(pThread->IsInRS());
+ }
+private:
+ int m_oldCount;
+};
+
+//---------------------------------------------------------------------------------------
+//
+// This is the same as the PublicCallbackHolder, except that this class doesn't assert that we are not holding
+// any locks when we call out to the shim.
+//
+// Notes:
+// @dbgtodo shim, synchronization - We need to settle on one consistent relationshipo between the RS
+// and the shim. Then we can clean up the sychronization story. Right now some code considers the shim
+// to be outside of the RS, and so we cannot hold any locks when we call out to the shim. However, there
+// are cases where we must hold a lock when we call out to the shim. For example, when we call out to the
+// shim to do a V2-style stackwalk, we need to be holding the stop-go lock so that another thread can't
+// come in and call Continue(). Finally, when we fix this, we should fix
+// PUBLIC_REENTRANT_API_ENTRY_FOR_SHIM() as well.
+//
+
+class PrivateShimCallbackHolder
+{
+public:
+ PrivateShimCallbackHolder()
+ {
+ // Exiting from RS; entering Cordbg via a callback
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+
+ // This callback is called from within the RS
+ _ASSERTE(pThread->IsInRS());
+
+ // Debugger error callback may be called from deep within the RS (after many nestings).
+ // So immediately jump to outside. We'll restore this in dtor.
+ m_oldCount = pThread->m_cInsideRS;
+ pThread->m_cInsideRS = 0;
+
+ _ASSERTE(!pThread->IsInRS());
+ }
+
+ ~PrivateShimCallbackHolder()
+ {
+ // Re-entering RS from after a callback.
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+
+ pThread->m_cInsideRS = m_oldCount;
+
+ // Our status of being "Inside the RS" is now restored.
+ _ASSERTE(pThread->IsInRS());
+ }
+private:
+ int m_oldCount;
+};
+
+class InternalAPIHolder
+{
+public:
+ InternalAPIHolder()
+ {
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+
+ // Internal APIs should already be inside the RS.
+ _ASSERTE(pThread->IsInRS() ||!"Internal API being called directly from outside (there should be a public API on the stack)");
+ }
+ void dummy() {}
+};
+
+//---------------------------------------------------------------------------------------
+//
+// This is a simple holder to assert that the current thread is holding the process lock. The purpose of
+// having this holder is to enforce a lock ordering between the process lock in the RS and the DD lock in DAC.
+// If a thread needs to take the process lock, it must do so BEFORE taking the DD lock. Otherwise we could have
+// a deadlock between the process lock and the DD lock.
+//
+// Normally we take the process lock before calling out to DAC, and every DAC API takes the DD lock on entry.
+// Moreover, normally DAC doesn't call back into the RS. The exceptions we currently have are:
+// 1) enumeration callbacks (e.g. code:CordbProcess::AppDomainEnumerationCallback)
+// 2) code:IDacDbiInterface::IMetaDataLookup
+// 3) code:IDacDbiInterface::IAllocator
+// 4) code:IStringHolder
+//
+// Note that the last two are fine because they don't need to take the process lock. The first two categories
+// need to take the process lock before calling into DAC to avoid potential deadlocks.
+//
+
+class InternalDacCallbackHolder
+{
+public:
+ InternalDacCallbackHolder(CordbProcess * pProcess)
+ {
+ _ASSERTE(pProcess->ThreadHoldsProcessLock());
+ }
+};
+
+// cotract that occurs at public builds.
+#define PUBLIC_CONTRACT \
+ CONTRACTL { NOTHROW; } CONTRACTL_END;
+
+
+// Private hook for Shim to call into DBI.
+// Since Shim is considered outside DBI, we need to mark that we've re-entered.
+// Big difference is that we can throw across this boundary.
+// @dbgtodo private shim hook - Eventually, these will all go away since the shim will be fully public.
+#define PUBLIC_API_ENTRY_FOR_SHIM(_pThis) \
+ PublicAPIHolder __pah;
+
+
+#define PUBLIC_API_UNSAFE_ENTRY_FOR_SHIM(_pThis) \
+ PublicDebuggerErrorCallbackHolder __pahCallback;
+
+// @dbgtodo shim, synchronization - Because of the problem mentioned in the comments for
+// PrivateShimCallbackHolder, we need this macro so that we don't hit an assertion when we come back into
+// the RS from the shim.
+#define PUBLIC_REENTRANT_API_ENTRY_FOR_SHIM(_pThis) \
+ PublicReentrantAPIHolder __pah;
+
+//-----------------------------------------------------------------------------
+// Declare whether an API is public or internal
+// Public APIs have the following:
+// - We may be called concurrently from multiple threads (ie, not thread safe)
+// - This thread does not hold any RS Locks while entering or leaving this function.
+// - May or May-not be reentrant.
+// Internal APIs:
+// - let us specifically mark that we're not a public API, and
+// - we're only being called through a public API.
+//-----------------------------------------------------------------------------
+#define PUBLIC_API_ENTRY(_pThis) \
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "[Public API '%s', this=0x%p]\n", __FUNCTION__, _pThis); \
+ PUBLIC_CONTRACT; \
+ PublicAPIHolder __pah;
+
+// Mark public APIs that are re-entrant.
+// Very few of our APIs should be re-entrant. Even for field access APIs (like GetXXX), the
+// public version is heavier (eg, checking the HRESULT) so we benefit from having a fast
+// internal version and calling that directly.
+#define PUBLIC_REENTRANT_API_ENTRY(_pThis) \
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "[Public API (re) '%s', this=0x%p]\n", __FUNCTION__, _pThis); \
+ PUBLIC_CONTRACT; \
+ PublicReentrantAPIHolder __pah;
+
+
+
+// Mark internal APIs.
+// All internal APIs are reentrant (duh)
+#define INTERNAL_API_ENTRY(_pThis) InternalAPIHolder __pah; __pah.dummy();
+
+// Mark an internal API from ATT_REQUIRE_STOP / ATT_ALLOW_LIVE_DO_STOP_GO.
+// This can assert that we're safe to send IPC events (that we're stopped and hold the SG lock)
+// @dbgtodo synchronization - in V2, this would assert that we were synced.
+// In V3, our definition of Sync is in flux. Need to resolve this with the synchronization feature crew.
+#define INTERNAL_SYNC_API_ENTRY(pProc) \
+ CordbProcess * __pProc = (pProc); \
+ _ASSERTE(__pProc->GetStopGoLock()->HasLock() || !"Must have stop go lock for internal-sync-api"); \
+ InternalAPIHolder __pah; __pah.dummy();
+
+
+
+// Mark that a thread is owned by us. Thus the thread's "Inside RS" count > 0.
+#define INTERNAL_THREAD_ENTRY(_pThis) \
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "[Internal thread started, this=0x%p]\n", _pThis); \
+ PUBLIC_CONTRACT; \
+ PublicAPIHolder __pah;
+
+// @dbgtodo unrecoverable error - This sould be deprecated once we deprecate UnrecoverableError.
+#define PUBLIC_CALLBACK_IN_THIS_SCOPE_DEBUGGERERROR(_pThis) \
+ PublicDebuggerErrorCallbackHolder __pahCallback;
+
+#define PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(_pThis) \
+ PrivateShimCallbackHolder __pahCallback;
+
+// Mark places where DAC may call back into DBI. We need to assert that we are holding the process lock in
+// these places, since otherwise we could deadlock between the DD lock and the process lock.
+#define INTERNAL_DAC_CALLBACK(__pProcess) \
+ InternalDacCallbackHolder __idch(__pProcess);
+
+
+// Helper to log debug events.
+inline void StressLogNativeDebugEvent(const DEBUG_EVENT * pDebugEvent, bool fOOB)
+{
+ if ((pDebugEvent)->dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
+ {
+ STRESS_LOG4(LF_CORDB, LL_EVERYTHING, "[Dispatching Win32 code=1 (EXCEPTION_DEBUG_EVENT, tid=%x, oob=%d, code=0x%x, 1st=%d]\n",
+ pDebugEvent->dwThreadId,
+ fOOB,
+ pDebugEvent->u.Exception.ExceptionRecord.ExceptionCode,
+ pDebugEvent->u.Exception.dwFirstChance);
+ }
+ else
+ {
+ STRESS_LOG3(LF_CORDB, LL_EVERYTHING, "[Dispatching Win32 code=%d, tid=%x, oob=%d.]\n",
+ pDebugEvent->dwDebugEventCode, pDebugEvent->dwThreadId, fOOB);
+ }
+
+}
+
+#define PUBLIC_WIN32_CALLBACK_IN_THIS_SCOPE(_pThis, _pDebugEvent, _fOOB) \
+ StressLogNativeDebugEvent(_pDebugEvent, _fOOB); \
+ PublicCallbackHolder __pahCallback(DB_IPCE_INVALID_EVENT);
+
+// Visisbility spec for dtors.
+// Currently, dtors are like public methods b/c they can be called from Release.
+// But they're also reentrant since they may be called from an internal-release.
+// @todo - we'd like to get all "useful" work out of the dtor; in which case we may
+// be able to change this to something more aggressive.
+#define DTOR_ENTRY(_pThis) PUBLIC_REENTRANT_API_ENTRY(_pThis)
+
+
+//-----------------------------------------------------------------------------
+// Typesafe bool for thread safety. This typesafety forces us to use
+// an specific reason for thread-safety, taken from a well-known list.
+// This is mostly concerned w/ being serialized.
+// Note that this assertion must be done on a per function basis and we
+// can't have any sort of 'ThreadSafetyReason CallerIsSafe()' b/c we can't
+// enforce that all of our callers are thread safe (only that our current caller is safe).
+//-----------------------------------------------------------------------------
+struct ThreadSafetyReason
+{
+public:
+ ThreadSafetyReason(bool f) { fIsSafe = f; }
+
+ bool fIsSafe;
+};
+
+// Different valid reasons that we may be threads safe.
+inline ThreadSafetyReason HoldsLock(RSLock * pLock)
+{
+ _ASSERTE(pLock != NULL);
+ return ThreadSafetyReason(pLock->HasLock());
+}
+inline ThreadSafetyReason OnW32ET(CordbProcess * pProc)
+{
+ return ThreadSafetyReason(IsWin32EventThread(pProc));
+}
+
+inline ThreadSafetyReason OnRCET(Cordb *pCordb)
+{
+ return ThreadSafetyReason (IsRCEventThread(pCordb));
+}
+
+// We use this when we assume that a function is thread-safe (b/c it's serialized).
+// The reason also lets us assert that our assumption is true.
+// By using a function, we enforce typesafety and thus require a valid reason
+// (as opposed to an arbitrary bool)
+inline void AssertThreadSafeHelper(ThreadSafetyReason r) {
+ _ASSERTE(r.fIsSafe);
+}
+
+//-----------------------------------------------------------------------------
+// Assert that the given scope is always called on a single thread b/c of
+// xReason. Common reasons may be b/c we hold a lock or we're always
+// called on a specific thread (Eg w32et).
+// The only valid reasons are of type ThreadSafetyReason (thus forcing us to
+// choose from a well-known list of valid reasons).
+//-----------------------------------------------------------------------------
+#define ASSERT_SINGLE_THREAD_ONLY(xReason) \
+ AssertThreadSafeHelper(xReason);
+
+#else
+
+//-----------------------------------------------------------------------------
+// Retail versions just nop. See the debug implementation for these
+// for their semantics.
+//-----------------------------------------------------------------------------
+
+#define PUBLIC_CONTRACT
+#define PUBLIC_API_ENTRY_FOR_SHIM(_pThis)
+#define PUBLIC_API_UNSAFE_ENTRY_FOR_SHIM(_pThis)
+#define PUBLIC_REENTRANT_API_ENTRY_FOR_SHIM(_pThis)
+#define PUBLIC_API_ENTRY(_pThis)
+#define PUBLIC_REENTRANT_API_ENTRY(_pThis)
+#define INTERNAL_API_ENTRY(_pThis)
+#define INTERNAL_SYNC_API_ENTRY(pProc)
+#define INTERNAL_THREAD_ENTRY(_pThis)
+#define PUBLIC_CALLBACK_IN_THIS_SCOPE_DEBUGGERERROR(_pThis)
+#define PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(_pThis)
+#define INTERNAL_DAC_CALLBACK(__pProcess)
+#define PUBLIC_WIN32_CALLBACK_IN_THIS_SCOPE(_pThis, _pDebugEvent, _fOOB)
+#define DTOR_ENTRY(_pThis)
+
+
+#define ASSERT_SINGLE_THREAD_ONLY(x)
+
+#endif // #if RSCONTRACTS
+
+
+class PublicCallbackHolder
+{
+public:
+ PublicCallbackHolder(RSLockHolder * pHolder, DebuggerIPCEventType type)
+ {
+ m_pHolder = pHolder;
+ _ASSERTE(!pHolder->IsNull()); // acquired
+
+ // Release the lock. We'll reacquire it at the dtor.
+ m_pHolder->Release();
+
+ Init(type);
+ }
+
+ PublicCallbackHolder(DebuggerIPCEventType type)
+ {
+ m_pHolder = NULL;
+ Init(type);
+ }
+
+ void Init(DebuggerIPCEventType type)
+ {
+ m_type = type;
+
+#if defined(RSCONTRACTS)
+ // Exiting from RS; entering Cordbg via a callback
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+
+ // m_cInsideRS may be arbitrarily large if we're called from a PUBLIC_REENTRANT_API,
+ // so just remember the current count and blast it back to 0.
+ m_oldCount = pThread->m_cInsideRS;
+ pThread->m_cInsideRS = 0;
+
+ _ASSERTE(!pThread->IsInRS());
+
+ // Should never be in public w/ these locks. (Even if we're re-entrant.)
+ pThread->AssertThreadIsLockFree();
+#endif // RSCONTRACTS
+ }
+
+ ~PublicCallbackHolder()
+ {
+#if defined(RSCONTRACTS)
+ // Re-entering RS from after a callback.
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+ _ASSERTE(!pThread->IsInRS());
+
+ pThread->m_cInsideRS = m_oldCount;
+
+ // Should never be in public w/ these locks. (Even if we're re-entrant.)
+ pThread->AssertThreadIsLockFree();
+#endif // RSCONTRACTS
+
+ // Reacquire the lock
+ if (m_pHolder != NULL)
+ {
+ m_pHolder->Acquire();
+ }
+ }
+protected:
+ int m_oldCount;
+ DebuggerIPCEventType m_type;
+ RSLockHolder * m_pHolder;
+};
+
+
+// Mark that a thread is calling out via a callback. This will adjust the "Inside RS" counter.
+#define PUBLIC_CALLBACK_IN_THIS_SCOPE(_pThis, pLockHolder, event) \
+ STRESS_LOG1(LF_CORDB, LL_EVERYTHING, "[Dispatching '%s']\n", IPCENames::GetName((event)->type)); \
+ PublicCallbackHolder __pahCallback(pLockHolder, (event)->type);
+
+#define PUBLIC_CALLBACK_IN_THIS_SCOPE1(_pThis, pLockHolder, event, formatLiteralString, arg0) \
+ STRESS_LOG2(LF_CORDB, LL_EVERYTHING, "[Dispatching '%s' " formatLiteralString "]\n", IPCENames::GetName((event)->type), arg0); \
+ PublicCallbackHolder __pahCallback(pLockHolder, (event)->type);
+
+#define PUBLIC_CALLBACK_IN_THIS_SCOPE2(_pThis, pLockHolder, event, formatLiteralString, arg0, arg1) \
+ STRESS_LOG3(LF_CORDB, LL_EVERYTHING, "[Dispatching '%s' " formatLiteralString "]\n", IPCENames::GetName((event)->type), arg0, arg1); \
+ PublicCallbackHolder __pahCallback(pLockHolder, (event)->type);
+
+#define PUBLIC_CALLBACK_IN_THIS_SCOPE3(_pThis, pLockHolder, event, formatLiteralString, arg0, arg1, arg2) \
+ STRESS_LOG4(LF_CORDB, LL_EVERYTHING, "[Dispatching '%s' " formatLiteralString "]\n", IPCENames::GetName((event)->type), arg0, arg1, arg2); \
+ PublicCallbackHolder __pahCallback(pLockHolder, (event)->type);
+
+
+#define PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(_pThis) \
+ PublicCallbackHolder __pahCallback(DB_IPCE_INVALID_EVENT);
+
+#define PUBLIC_CALLBACK_IN_THIS_SCOPE0(_pThis, pLockHolder) \
+ PublicCallbackHolder __pahCallback(pLockHolder, DB_IPCE_INVALID_EVENT);
+
+
+//-----------------------------------------------------------------------------
+// Helpers
+inline void ValidateOrThrow(const void * p)
+{
+ if (p == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+}
+
+// aligns argBase on platforms that require it else it's a no-op
+inline void AlignAddressForType(CordbType* pArgType, CORDB_ADDRESS& argBase)
+{
+#ifdef DBG_TARGET_ARM
+// TODO: review the following
+#ifdef FEATURE_64BIT_ALIGNMENT
+ BOOL align = FALSE;
+ HRESULT hr = pArgType->RequiresAlign8(&align);
+ _ASSERTE(SUCCEEDED(hr));
+
+ if (align)
+ argBase = ALIGN_ADDRESS(argBase, 8);
+#endif // FEATURE_64BIT_ALIGNMENT
+#endif // DBG_TARGET_ARM
+}
+
+//-----------------------------------------------------------------------------
+// Macros to mark public ICorDebug functions
+// Usage:
+//
+// HRESULT CordbXYZ:Function(...)
+// {
+// HRESULT hr = S_OK;
+// PUBLIC_API_BEGIN(this);
+// // body, may throw
+// PUBLIC_API_END(hr);
+// return hr;
+// }
+#define PUBLIC_API_BEGIN(__this) \
+ CordbBase * __pThis = (__this); \
+ PUBLIC_API_ENTRY(__pThis); \
+ EX_TRY { \
+ RSLockHolder __lockHolder(__pThis->GetProcess()->GetProcessLock()); \
+ THROW_IF_NEUTERED(__pThis); \
+
+// You should not use this in general. We're adding it as a temporary workaround for a
+// particular scenario until we do the synchronization feature crew
+#define PUBLIC_API_NO_LOCK_BEGIN(__this) \
+ CordbBase * __pThis = (__this); \
+ PUBLIC_API_ENTRY(__pThis); \
+ EX_TRY { \
+ THROW_IF_NEUTERED(__pThis); \
+
+// Some APIs (that invoke callbacks), need to toggle the lock.
+#define GET_PUBLIC_LOCK_HOLDER() (&__lockHolder)
+
+#define PUBLIC_API_END(__hr) \
+ } EX_CATCH_HRESULT(__hr); \
+
+// @todo: clean up API constracts. Should we really be taking the Process lock for
+// reentrant APIS??
+#define PUBLIC_REENTRANT_API_BEGIN(__this) \
+ CordbBase * __pThis = (__this); \
+ PUBLIC_REENTRANT_API_ENTRY(__pThis); \
+ EX_TRY { \
+ RSLockHolder __lockHolder(__pThis->GetProcess()->GetProcessLock()); \
+ THROW_IF_NEUTERED(__pThis); \
+
+#define PUBLIC_REENTRANT_API_END(__hr) \
+ } EX_CATCH_HRESULT(__hr); \
+
+// If an API needs to take the stop/go lock as well as the process lock, the
+// stop/go lock has to be taken first. This is an alternative to PUBLIC_REENTRANT_API_BEGIN
+// that allows this, since it doesn't take the process lock. It should be closed with
+// PUBLIC_REENTRANT_API_END
+#define PUBLIC_REENTRANT_API_NO_LOCK_BEGIN(__this) \
+ CordbBase * __pThis = (__this); \
+ PUBLIC_REENTRANT_API_ENTRY(__pThis); \
+ EX_TRY { \
+ THROW_IF_NEUTERED(__pThis); \
+
+
+//-----------------------------------------------------------------------------
+// For debugging ease, cache some global values.
+// Include these in retail & free because that's where we need them the most!!
+// Optimized builds may not let us view locals & parameters. So Having these
+// cached as global values should let us inspect almost all of
+// the interesting parts of the RS even in a Retail build!
+//-----------------------------------------------------------------------------
+struct RSDebuggingInfo
+{
+ // There should only be 1 global Cordb object. Store it here.
+ Cordb * m_Cordb;
+
+ // We have lots of processes. Keep a pointer to the most recently touched
+ // (subjective) process, as a hint about what our "current" process is.
+ // If we're only debugging 1 process, this will be sufficient.
+ CordbProcess * m_MRUprocess;
+
+ CordbRCEventThread * m_RCET;
+};
+
+#include "rspriv.inl"
+
+#endif // #if RSPRIV_H
+
+
diff --git a/src/debug/di/rspriv.inl b/src/debug/di/rspriv.inl
new file mode 100644
index 0000000000..00e4c233b6
--- /dev/null
+++ b/src/debug/di/rspriv.inl
@@ -0,0 +1,723 @@
+// 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.
+//*****************************************************************************
+// File: rspriv.inl
+//
+
+//
+// Inline functions for rspriv.h
+//
+//*****************************************************************************
+
+#ifndef RSPRIV_INL_
+#define RSPRIV_INL_
+
+#include "rspriv.h"
+
+// Get the native pipeline object, which resides on the Win32EventThread.
+inline
+INativeEventPipeline * CordbWin32EventThread::GetNativePipeline()
+{
+ return m_pNativePipeline;
+}
+
+
+// True if we're interop-debugging, else false.
+// Note, we include this even in Non-interop builds because there are runtime checks throughout the APIs
+// that certain operations only succeed/fail in interop-debugging.
+inline
+bool CordbProcess::IsInteropDebugging()
+{
+#ifdef FEATURE_INTEROP_DEBUGGING
+ return (m_state & PS_WIN32_ATTACHED) != 0;
+#else
+ return false;
+#endif // FEATURE_INTEROP_DEBUGGING
+}
+
+
+//-----------------------------------------------------------------------------
+// Get the ShimProcess object.
+//
+// Returns:
+// ShimProcess object if available; else NULL.
+//
+// Notes:
+// This shim has V2 emulation logic.
+// If we have no ShimProcess object, then we're in a V3 codepath.
+// @dbgtodo - eventually, remove all emulation and this function.
+//-----------------------------------------------------------------------------
+inline
+ShimProcess * CordbProcess::GetShim()
+{
+ return m_pShim;
+};
+
+
+
+//---------------------------------------------------------------------------------------
+// Helper to read a structure from the target
+//
+// Arguments:
+// T - type of structure to read.
+// pRemotePtr - remote pointer into target (src).
+// pLocalBuffer - local buffer to copy into (Dest).
+//
+// Return Value:
+// Returns S_OK on success, in the event of a short read returns ERROR_PARTIAL_COPY
+//
+// Notes:
+// This just does a raw Byte copy, but does not do any Marshalling.
+// This fails if any part of the buffer can't be read.
+//
+//---------------------------------------------------------------------------------------
+template<typename T>
+HRESULT CordbProcess::SafeReadStruct(CORDB_ADDRESS pRemotePtr, T * pLocalBuffer)
+{
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ TargetBuffer tb(pRemotePtr, sizeof(T));
+ SafeReadBuffer(tb, (PBYTE) pLocalBuffer);
+ }
+ EX_CATCH_HRESULT(hr) ;
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Destructor for RSInitHolder. Will safely neuter and release the object.
+template<class T> inline
+RSInitHolder<T>::~RSInitHolder()
+{
+ if (m_pObject != NULL)
+ {
+ CordbProcess * pProcess = m_pObject->GetProcess();
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+
+ m_pObject->Neuter();
+
+ // Can't explicitly call 'delete' because somebody may have taken a reference.
+ m_pObject.Clear();
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// Helper to write a structure to the target
+//
+// Arguments:
+// T - type of structure to read.
+// pRemotePtr - remote pointer into target (dest).
+// pLocalBuffer - local buffer to write (Src).
+//
+// Return Value:
+// Returns S_OK on success, in the event of a short write returns ERROR_PARTIAL_COPY
+//
+// Notes:
+// This just does a raw Byte copy into the Target, but does not do any Marshalling.
+// This fails if any part of the buffer can't be written.
+//
+//---------------------------------------------------------------------------------------
+template<typename T> inline
+HRESULT CordbProcess::SafeWriteStruct(CORDB_ADDRESS pRemotePtr, const T* pLocalBuffer)
+{
+ HRESULT hr= S_OK;
+ EX_TRY
+ {
+ TargetBuffer tb(pRemotePtr, sizeof(T));
+ SafeWriteBuffer(tb, (BYTE *) (pLocalBuffer));
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+inline
+CordbModule *CordbJITILFrame::GetModule()
+{
+ return (m_ilCode->GetModule());
+}
+
+inline
+CordbAppDomain *CordbJITILFrame::GetCurrentAppDomain()
+{
+ return (m_nativeFrame->GetCurrentAppDomain());
+}
+
+//-----------------------------------------------------------------------------
+// Called to notify that we must flush DAC
+//-----------------------------------------------------------------------------
+inline
+void CordbProcess::ForceDacFlush()
+{
+ // We need to take the process lock here because otherwise we could race with the Arrowhead stackwalking
+ // APIs. The Arrowhead stackwalking APIs check the flush counter and refresh all the state if necessary.
+ // However, while one thread is refreshing the state of the stackwalker, another thread may come in
+ // and force a flush. That's why we need to take a process lock before we flush. We need to synchronize
+ // with other threads which are using DAC memory.
+ RSLockHolder lockHolder(GetProcessLock());
+
+ // For Mac debugging, it is not safe to call into the DAC once code:INativeEventPipeline::TerminateProcess
+ // is called. Also, we must check m_exiting under the process lock.
+ if (!m_exiting)
+ {
+ if (m_pDacPrimitives != NULL)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "Flush() - old counter: %d", m_flushCounter);
+ m_flushCounter++;
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ m_pDacPrimitives->FlushCache();
+ }
+ EX_CATCH_HRESULT(hr);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ }
+ }
+}
+
+
+inline
+CordbFunction *CordbJITILFrame::GetFunction()
+{
+ return m_nativeFrame->m_nativeCode->GetFunction();
+}
+
+//-----------------------------------------------------------------------------
+// Helpers to assert threading semantics.
+//-----------------------------------------------------------------------------
+inline bool IsWin32EventThread(CordbProcess * p)
+{
+ _ASSERTE(p!= NULL);
+ return p->IsWin32EventThread();
+}
+
+inline bool IsRCEventThread(Cordb* p)
+{
+ _ASSERTE(p!= NULL);
+ return (p->m_rcEventThread != NULL) && p->m_rcEventThread->IsRCEventThread();
+}
+
+
+
+//-----------------------------------------------------------------------------
+// StopContinueHolder. Ensure that we're synced during a certain region.
+//-----------------------------------------------------------------------------
+inline HRESULT StopContinueHolder::Init(CordbProcess * p)
+{
+ _ASSERTE(p != NULL);
+ LOG((LF_CORDB, LL_INFO100000, "Doing RS internal Stop\n"));
+ HRESULT hr = p->StopInternal(INFINITE, VMPTR_AppDomain::NullPtr());
+ if ((hr == CORDBG_E_PROCESS_TERMINATED) || SUCCEEDED(hr))
+ {
+ // Better be synced after calling Stop!
+ _ASSERTE(p->GetSynchronized());
+ m_p = p;
+ }
+
+ return hr;
+};
+
+inline StopContinueHolder::~StopContinueHolder()
+{
+ // If Init() failed to call Stop, then don't call continue
+ if (m_p == NULL)
+ return;
+
+ HRESULT hr;
+ LOG((LF_CORDB, LL_INFO100000, "Doing RS internal Continue\n"));
+ hr = m_p->ContinueInternal(false);
+ SIMPLIFYING_ASSUMPTION(
+ (hr == CORDBG_E_PROCESS_TERMINATED) ||
+ (hr == CORDBG_E_PROCESS_DETACHED) ||
+ (hr == CORDBG_E_OBJECT_NEUTERED) ||
+ (hr == E_ACCESSDENIED) || //Sadly in rare cases we leak this error code instead of PROCESS_TERMINATED
+ //See Dev10 bug 872621
+ SUCCEEDED(hr));
+}
+
+//-----------------------------------------------------------------------------
+// Neutering on the base object
+//-----------------------------------------------------------------------------
+inline
+void CordbCommonBase::Neuter()
+{
+ LOG((LF_CORDB, LL_EVERYTHING, "Memory: CordbBase object neutered: this=%p, id=%p\n", this, m_id));
+ m_fIsNeutered = 1;
+}
+
+// Unsafe neuter for an object that's already dead. Only use this if you know exactly what you're doing.
+// The point here is that we can mark the object neutered even though we may not hold the stop-go lock.
+inline
+void CordbCommonBase::UnsafeNeuterDeadObject()
+{
+ LOG((LF_CORDB, LL_EVERYTHING, "Memory: CordbBase object neutered: this=%p, id=%p\n", this, m_id));
+ m_fIsNeutered = 1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Reference Counting
+//-----------------------------------------------------------------------------
+inline
+void CordbCommonBase::InternalAddRef()
+{
+ CONSISTENCY_CHECK_MSGF((m_RefCount & CordbBase_InternalRefCountMask) != (CordbBase_InternalRefCountMax),
+ ("Internal AddRef overlow, External Count = %d,\n'%s' @ 0x%p",
+ (m_RefCount >> CordbBase_ExternalRefCountShift), this->DbgGetName(), this));
+
+ // Since the internal ref-count is the lower bits, and we know we'll never overflow ;)
+ // we can just do an interlocked increment on the whole 32 bits.
+#ifdef TRACK_OUTSTANDING_OBJECTS
+ MixedRefCountUnsigned Count =
+#endif
+
+ InterlockedIncrement64((MixedRefCountSigned*) &m_RefCount);
+
+
+#ifdef _DEBUG_IMPL
+
+ // For leak detection in debug builds, track all internal references.
+ InterlockedIncrement(&Cordb::s_DbgMemTotalOutstandingInternalRefs);
+#endif
+
+#ifdef TRACK_OUTSTANDING_OBJECTS
+ if ((Count & CordbBase_InternalRefCountMask) != 1)
+ {
+ return;
+ }
+
+ LONG i;
+
+ for (i = 0; i < Cordb::s_DbgMemOutstandingObjectMax; i++)
+ {
+ if (Cordb::s_DbgMemOutstandingObjects[i] == NULL)
+ {
+ if (InterlockedCompareExchangeT(&(Cordb::s_DbgMemOutstandingObjects[i]), (LPVOID) this, NULL) == NULL)
+ {
+ return;
+ }
+ }
+ }
+
+ do
+ {
+ i = Cordb::s_DbgMemOutstandingObjectMax + 1;
+ }
+ while ((i < MAX_TRACKED_OUTSTANDING_OBJECTS) &&
+ (InterlockedCompareExchange(&Cordb::s_DbgMemOutstandingObjectMax, i, i - 1) != (i - 1)));
+
+ if (i < MAX_TRACKED_OUTSTANDING_OBJECTS)
+ {
+ Cordb::s_DbgMemOutstandingObjects[i] = this;
+ }
+#endif
+
+}
+
+// Derived versions of AddRef / Release will call these.
+// External AddRef.
+inline
+ULONG CordbCommonBase::BaseAddRef()
+{
+ Volatile<MixedRefCountUnsigned> ref;
+ MixedRefCountUnsigned refNew;
+ ExternalRefCount cExternalCount;
+
+ // Compute what refNew ought to look like; and then If m_RefCount hasn't changed on us
+ // (via another thread), then stash the new one in.
+ do
+ {
+ ref = m_RefCount;
+
+ cExternalCount = (ExternalRefCount) (ref >> CordbBase_ExternalRefCountShift);
+
+ if (cExternalCount == CordbBase_InternalRefCountMax)
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("Overflow in External AddRef. Internal Count =%d,\n'%s' @ 0x%p",
+ (ref & CordbBase_InternalRefCountMask), this->DbgGetName(), this));
+
+ // Ignore any AddRefs beyond this... This will screw up Release(), but we're
+ // probably already so screwed it wouldn't matter.
+ return cExternalCount;
+ }
+
+ cExternalCount++;
+
+ refNew = (((MixedRefCountUnsigned)cExternalCount) << CordbBase_ExternalRefCountShift) | (ref & CordbBase_InternalRefCountMask);
+ }
+ while ((MixedRefCountUnsigned)InterlockedCompareExchange64((MixedRefCountSigned*)&m_RefCount, refNew, ref) != ref);
+
+ return cExternalCount;
+}
+
+// Do an AddRef against the External count. This is a semantics issue.
+// We use this when an internal component Addrefs out-parameters (which Cordbg will call Release on).
+inline
+void CordbCommonBase::ExternalAddRef()
+{
+ // Call on BaseAddRef() to avoid any asserts that prevent stuff from inside the RS from bumping
+ // up the external ref count.
+ BaseAddRef();
+}
+
+inline
+void CordbCommonBase::InternalRelease()
+{
+ CONSISTENCY_CHECK_MSGF((m_RefCount & CordbBase_InternalRefCountMask) != 0,
+ ("Internal Release underflow, External Count = %d,\n'%s' @ 0x%p",
+ (m_RefCount >> CordbBase_ExternalRefCountShift), this->DbgGetName(), this));
+
+#ifdef _DEBUG_IMPL
+ // For leak detection in debug builds, track all internal references.
+ InterlockedDecrement(&Cordb::s_DbgMemTotalOutstandingInternalRefs);
+#endif
+
+
+
+ // The internal count is in the low 16 bits, and we know that we'll never underflow the internal
+ // release. ;)
+ // Furthermore we know that ExternalRelease will prevent us from underflowing the external release count.
+ // Thus we can just do an simple decrement here, and compare against 0x00000000 (which is the value
+ // when both the Internal + External counts are at 0)
+ MixedRefCountSigned cRefCount = InterlockedDecrement64((MixedRefCountSigned*) &m_RefCount);
+
+#ifdef TRACK_OUTSTANDING_OBJECTS
+ if ((cRefCount & CordbBase_InternalRefCountMask) == 0)
+ {
+ for (LONG i = 0; i < Cordb::s_DbgMemOutstandingObjectMax; i++)
+ {
+ if (Cordb::s_DbgMemOutstandingObjects[i] == this)
+ {
+ Cordb::s_DbgMemOutstandingObjects[i] = NULL;
+ break;
+ }
+ }
+ }
+#endif
+
+
+ if (cRefCount == 0x00000000)
+ {
+ delete this;
+ }
+}
+
+// Do an external release.
+inline
+ULONG CordbCommonBase::BaseRelease()
+{
+ Volatile<MixedRefCountUnsigned> ref;
+ MixedRefCountUnsigned refNew;
+ ExternalRefCount cExternalCount;
+
+ // Compute what refNew ought to look like; and then If m_RefCount hasn't changed on us
+ // (via another thread), then stash the new one in.
+ do
+ {
+ ref = m_RefCount;
+
+ cExternalCount = (ExternalRefCount) (ref >> CordbBase_ExternalRefCountShift);
+
+ if (cExternalCount == 0)
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("Underflow in External Release. Internal Count = %d\n'%s' @ 0x%p",
+ (ref & CordbBase_InternalRefCountMask), this->DbgGetName(), this));
+
+ // Ignore any Releases beyond this... This will screw up Release(), but we're
+ // probably already so screwed it wouldn't matter.
+ // It's very important that we don't let the release count go negative (both
+ // Releases assumes this when deciding whether to delete)
+ return 0;
+ }
+
+ cExternalCount--;
+
+ refNew = (((MixedRefCountUnsigned) cExternalCount) << CordbBase_ExternalRefCountShift) | (ref & CordbBase_InternalRefCountMask);
+ }
+ while ((MixedRefCountUnsigned)InterlockedCompareExchange64((MixedRefCountSigned*)&m_RefCount, refNew, ref) != ref);
+
+ // If the external count just dropped to 0, then this object can be neutered.
+ if (cExternalCount == 0)
+ {
+ m_fNeuterAtWill = 1;
+ }
+
+ if (refNew == 0)
+ {
+ delete this;
+ return 0;
+ }
+ return cExternalCount;
+
+}
+
+
+inline ULONG CordbCommonBase::BaseAddRefEnforceExternal()
+{
+ // External refs shouldn't be called while in the RS
+#ifdef RSCONTRACTS
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+ CONSISTENCY_CHECK_MSGF(!pThread->IsInRS(),
+ ("External addref for pThis=0x%p, name='%s' called from within RS",
+ this, this->DbgGetName()
+ ));
+#endif
+ return (BaseAddRef());
+
+}
+
+inline ULONG CordbCommonBase::BaseReleaseEnforceExternal()
+{
+#ifdef RSCONTRACTS
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+
+ CONSISTENCY_CHECK_MSGF(!pThread->IsInRS(),
+ ("External release for pThis=0x%p, name='%s' called from within RS",
+ this, this->DbgGetName()
+ ));
+#endif
+
+ return (BaseRelease());
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Locks
+//-----------------------------------------------------------------------------
+
+// Base class
+#ifdef _DEBUG
+inline bool RSLock::HasLock()
+{
+ CONSISTENCY_CHECK_MSGF(IsInit(), ("RSLock '%s' not inited", m_szTag));
+ return m_tidOwner == ::GetCurrentThreadId();
+}
+#endif
+
+#ifdef _DEBUG
+// Ctor+ Dtor are only used for asserts.
+inline RSLock::RSLock()
+{
+ m_eAttr = cLockUninit;
+ m_tidOwner = (DWORD)-1;
+};
+
+inline RSLock::~RSLock()
+{
+ // If this lock is still ininitialized, then no body ever deleted the critical section
+ // for it and we're leaking.
+ CONSISTENCY_CHECK_MSGF(!IsInit(), ("Leaking Critical section for RS Lock '%s'", m_szTag));
+}
+#endif
+
+
+// Initialize a lock.
+inline void RSLock::Init(const char * szTag, int eAttr, ERSLockLevel level)
+{
+ CONSISTENCY_CHECK_MSGF(!IsInit(), ("RSLock '%s' already inited", szTag));
+#ifdef _DEBUG
+ m_szTag = szTag;
+ m_eAttr = eAttr;
+ m_count = 0;
+ m_level = level;
+
+ // Must be either re-entrant xor flat. (not neither; not both)
+ _ASSERTE(IsReentrant() ^ ((m_eAttr & cLockFlat) == cLockFlat));
+#endif
+ _ASSERTE((level >= 0) && (level <= RSLock::LL_MAX));
+
+ _ASSERTE(IsInit());
+
+ InitializeCriticalSection(&m_lock);
+}
+
+// Cleanup a lock.
+inline void RSLock::Destroy()
+{
+ CONSISTENCY_CHECK_MSGF(IsInit(), ("RSLock '%s' not inited", m_szTag));
+ DeleteCriticalSection(&m_lock);
+
+#ifdef _DEBUG
+ m_eAttr = cLockUninit; // No longer initialized.
+ _ASSERTE(!IsInit());
+#endif
+}
+
+inline void RSLock::Lock()
+{
+ CONSISTENCY_CHECK_MSGF(IsInit(), ("RSLock '%s' not inited", m_szTag));
+
+#ifdef RSCONTRACTS
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+ pThread->NotifyTakeLock(this);
+#endif
+
+ EnterCriticalSection(&m_lock);
+#ifdef _DEBUG
+ m_tidOwner = ::GetCurrentThreadId();
+ m_count++;
+
+ // Either count == 1 or we're re-entrant.
+ _ASSERTE((m_count == 1) || (m_eAttr == cLockReentrant));
+#endif
+}
+
+inline void RSLock::Unlock()
+{
+ CONSISTENCY_CHECK_MSGF(IsInit(), ("RSLock '%s' not inited", m_szTag));
+
+#ifdef _DEBUG
+ _ASSERTE(HasLock());
+ m_count--;
+ _ASSERTE(m_count >= 0);
+ if (m_count == 0)
+ {
+ m_tidOwner = (DWORD)-1;
+ }
+#endif
+
+#ifdef RSCONTRACTS
+ // NotifyReleaseLock needs to be called before we release the lock.
+ // Note that HasLock()==false at this point. NotifyReleaseLock relies on that.
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+ pThread->NotifyReleaseLock(this);
+#endif
+
+ LeaveCriticalSection(&m_lock);
+}
+
+template <class T>
+inline T* CordbSafeHashTable<T>::GetBase(ULONG_PTR id, BOOL fFab)
+{
+ return static_cast<T*>(UnsafeGetBase(id, fFab));
+}
+
+template <class T>
+inline T* CordbSafeHashTable<T>::GetBaseOrThrow(ULONG_PTR id, BOOL fFab)
+{
+ T* pResult = GetBase(id, fFab);
+ if (pResult == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+ else
+ {
+ return pResult;
+ }
+}
+
+// Copy the contents of the hash to an strong-ref array
+//
+// Arguments:
+// pArray - array to allocate storage and copy to
+//
+// Assumptions:
+// Caller locks.
+//
+// Notes:
+// Array takes strong internal references.
+// This can be useful for dancing around locks; eg: If we want to iterate on a hash
+// and do an operation that requires a lock that can't be held when iterating.
+// (Example: Neuter needs Big stop-go lock; Hash is protected by little Process-lock).
+//
+template <class T>
+inline void CordbSafeHashTable<T>::CopyToArray(RSPtrArray<T> * pArray)
+{
+ // Assumes caller has necessary locks to iterate
+ UINT32 count = GetCount();
+ pArray->AllocOrThrow(count);
+
+
+ HASHFIND find;
+ UINT32 idx = 0;
+
+ T * pCordbBase = FindFirst(&find);
+ while(idx < count)
+ {
+ pArray->Assign(idx, pCordbBase);
+ idx++;
+ pCordbBase = FindNext(&find);
+ }
+
+ // Assert is at end.
+ _ASSERTE(pCordbBase == NULL);
+}
+
+// Empty the contents of the hash to an array. Array gets ownersship.
+//
+// Arguments:
+// pArray - array to allocate and get ownership
+//
+// Assumptions:
+// Caller locks.
+//
+// Notes:
+// Hashtable will be empty after this.
+template <class T>
+inline void CordbSafeHashTable<T>::TransferToArray(RSPtrArray<T> * pArray)
+{
+ // Assumes caller has necessary locks
+
+ HASHFIND find;
+ UINT32 count = GetCount();
+ UINT32 idx = 0;
+
+ pArray->AllocOrThrow(count);
+
+ while(idx < count)
+ {
+ T * pCordbBase = FindFirst(&find);
+ _ASSERTE(pCordbBase != NULL);
+ pArray->Assign(idx, pCordbBase);
+
+ idx++;
+ // We're removing while iterating the collection.
+ // But we reset the iteration each time by calling FindFirst.
+ RemoveBase((ULONG_PTR)pCordbBase->m_id); // this will call release, adjust GetCount()
+ }
+
+ // Assert is at end.
+ _ASSERTE(GetCount() == 0);
+}
+
+//
+// Neuter all elements in the hash table and empty the hash.
+//
+// Arguments:
+// pLock - lock required to iterate through hash.
+//
+// Assumptions:
+// Caller ensured it's safe to Neuter.
+// Caller has locked the hash.
+//
+template <class T>
+inline void CordbSafeHashTable<T>::NeuterAndClear(RSLock * pLock)
+{
+ _ASSERTE(pLock->HasLock());
+
+ HASHFIND find;
+ UINT32 count = GetCount();
+ UINT32 idx = 0;
+
+ while(idx < count)
+ {
+ T * pCordbBase = FindFirst(&find);
+ _ASSERTE(pCordbBase != NULL);
+
+ // Using this Validate to help track down bug DevDiv bugs 739406
+ pCordbBase->ValidateObject();
+ pCordbBase->Neuter();
+ idx++;
+
+ // We're removing while iterating the collection.
+ // But we reset the iteration each time by calling FindFirst.
+ RemoveBase((ULONG_PTR)pCordbBase->m_id); // this will call release, adjust GetCount()
+ }
+
+ // Assert is at end.
+ _ASSERTE(GetCount() == 0);
+}
+
+
+#endif // RSPRIV_INL_
diff --git a/src/debug/di/rsregsetcommon.cpp b/src/debug/di/rsregsetcommon.cpp
new file mode 100644
index 0000000000..1a2c57f63f
--- /dev/null
+++ b/src/debug/di/rsregsetcommon.cpp
@@ -0,0 +1,248 @@
+// 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.
+//*****************************************************************************
+// File: RSRegSetCommon.cpp
+//
+
+// Common cross-platform behavior of reg sets.
+// Platform specific stuff is in CordbRegisterSet.cpp located in
+// the platform sub-dir.
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "primitives.h"
+
+/* ------------------------------------------------------------------------- *
+ * Common (cross-platform) Register-Set stuff
+ * ------------------------------------------------------------------------- */
+
+
+CordbRegisterSet::CordbRegisterSet(
+ DebuggerREGDISPLAY * pRegDisplay,
+ CordbThread * pThread,
+ bool fActive,
+ bool fQuickUnwind,
+ bool fTakeOwnershipOfDRD /*= false*/)
+ : CordbBase(pThread->GetProcess(), 0, enumCordbRegisterSet)
+{
+ _ASSERTE( pRegDisplay != NULL );
+ _ASSERTE( pThread != NULL );
+ m_rd = pRegDisplay;
+ m_thread = pThread;
+ m_active = fActive;
+ m_quickUnwind = fQuickUnwind;
+
+ m_fTakeOwnershipOfDRD = fTakeOwnershipOfDRD;
+
+ // Add to our parent thread's neuter list.
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ pThread->GetRefreshStackNeuterList()->Add(GetProcess(), this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+}
+
+void CordbRegisterSet::Neuter()
+{
+ m_thread = NULL;
+ if (m_fTakeOwnershipOfDRD)
+ {
+ delete m_rd;
+ }
+ m_rd = NULL;
+
+ CordbBase::Neuter();
+}
+
+CordbRegisterSet::~CordbRegisterSet()
+{
+ _ASSERTE(this->IsNeutered());
+}
+
+
+HRESULT CordbRegisterSet::QueryInterface(REFIID riid, void **ppInterface)
+{
+ // <NOTE>
+ // This is an exception to the rule that a QI for a higher version API should fail if
+ // the debugger does not support that version of the API. The reasoning is that
+ // while higher versions of other APIs support enhanced functionality and are not
+ // required, this particular API is required on IA64. An example scenario is when an
+ // Everett debuggger is ported to Whidbey and the user wants to use the debugger on IA64.
+ // The user should not be required to implement the ICorDebugManagedCallback2 API, as would
+ // be the case if we make the versioning check like other higher version APIs.
+ // </NOTE>
+ if (riid == IID_ICorDebugRegisterSet)
+ {
+ *ppInterface = static_cast<ICorDebugRegisterSet*>(this);
+ }
+ else if (riid == IID_ICorDebugRegisterSet2)
+ {
+ *ppInterface = static_cast<ICorDebugRegisterSet2*>(this);
+ }
+ else if (riid == IID_IUnknown)
+ {
+ *ppInterface = static_cast<IUnknown*>(static_cast<ICorDebugRegisterSet*>(this));
+ }
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// This is just a convenience function to convert a regdisplay into a Context.
+// Since a context has more info than a regdisplay, the conversion isn't perfect
+// and the context can't be fully accurate.
+//
+// Inputs:
+// contextSize - sizeof incoming context buffer in bytes
+// context - buffer to copy this regdisplay's OS CONTEXT structure into.
+//
+// Returns S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbRegisterSet::GetThreadContext(ULONG32 contextSize, BYTE context[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ _ASSERTE( m_thread != NULL );
+ if( contextSize < sizeof( DT_CONTEXT ))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ ValidateOrThrow(context);
+
+ DT_CONTEXT *pInputContext = reinterpret_cast<DT_CONTEXT *> (context);
+
+ // Just to be safe, zero out the buffer we got in while preserving the ContextFlags.
+ // On X64 the ContextFlags field is not the first 4 bytes of the DT_CONTEXT.
+ DWORD dwContextFlags = pInputContext->ContextFlags;
+ ZeroMemory(context, contextSize);
+ pInputContext->ContextFlags = dwContextFlags;
+
+ // Augment the leafmost (active) register w/ information from the current context.
+ DT_CONTEXT * pLeafContext = NULL;
+ if (m_active)
+ {
+ EX_TRY
+ {
+ // This may fail, but it is not a disastrous failure in this case. All we care is whether
+ // pLeafContext is updated to a non-NULL value.
+ m_thread->GetManagedContext( &pLeafContext);
+ }
+ EX_CATCH
+ {
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ if (pLeafContext != NULL)
+ {
+ // @todo - shouldn't this be a context-flags sensitive copy?
+ memmove( pInputContext, pLeafContext, sizeof( DT_CONTEXT) );
+ }
+ }
+
+
+ // Now update the registers based on the current frame.
+ // This is a very platform specific action.
+ InternalCopyRDToContext(pInputContext);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Helpers to impl IRegSet2 on top of original IRegSet.
+// These are useful on platforms that don't need IRegSet2 (like x86 + amd64).
+// See CorDebug.idl for details.
+//
+// Inputs:
+// regCount - size of pAvailable buffer in bytes
+// pAvailable - buffer to hold bitvector of available registers.
+// On success, bit at position CorDebugRegister is 1 iff that
+// register is available.
+// Returns S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbRegisterSet::GetRegistersAvailableAdapter(
+ ULONG32 regCount,
+ BYTE pAvailable[])
+{
+ // Defer to call on v1.0 interface
+ HRESULT hr = S_OK;
+
+ if (regCount < sizeof(ULONG64))
+ {
+ return E_INVALIDARG;
+ }
+
+ _ASSERTE(pAvailable != NULL);
+
+ ULONG64 availRegs;
+ hr = this->GetRegistersAvailable(&availRegs);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ // Nor marshal our 64-bit value into the outgoing byte array.
+ for(int iBit = 0; iBit < (int) sizeof(availRegs) * 8; iBit++)
+ {
+ ULONG64 test = SETBITULONG64(iBit);
+ if (availRegs & test)
+ {
+ SET_BIT_MASK(pAvailable, iBit);
+ }
+ else
+ {
+ RESET_BIT_MASK(pAvailable, iBit);
+ }
+ }
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Helpers to impl IRegSet2 on top of original IRegSet.
+// These are useful on platforms that don't need IRegSet2 (like x86 + amd64).
+// See CorDebug.idl for details.
+//
+// Inputs:
+// maskCount - size of mask buffer in bytes.
+// mask - input buffer specifying registers to request
+// regCount - size of regBuffer in bytes
+// regBuffer - output buffer, regBuffer[n] = value of register at n-th active
+// bit in mask.
+// Returns S_OK on success.
+//-----------------------------------------------------------------------------
+
+// mask input requrest registers, which get written to regCount buffer.
+HRESULT CordbRegisterSet::GetRegistersAdapter(
+ ULONG32 maskCount, BYTE mask[],
+ ULONG32 regCount, CORDB_REGISTER regBuffer[])
+{
+ // Convert input mask to orig mask.
+ ULONG64 maskOrig = 0;
+
+ for(UINT iBit = 0; iBit < maskCount * 8; iBit++)
+ {
+ if (IS_SET_BIT_MASK(mask, iBit))
+ {
+ maskOrig |= SETBITULONG64(iBit);
+ }
+ }
+
+ return this->GetRegisters(maskOrig,
+ regCount, regBuffer);
+}
diff --git a/src/debug/di/rsstackwalk.cpp b/src/debug/di/rsstackwalk.cpp
new file mode 100644
index 0000000000..8ade4c9a74
--- /dev/null
+++ b/src/debug/di/rsstackwalk.cpp
@@ -0,0 +1,822 @@
+// 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.
+//
+// RsStackWalk.cpp
+//
+
+//
+// This file contains the implementation of the V3 managed stackwalking API.
+//
+// ======================================================================================
+
+#include "stdafx.h"
+#include "primitives.h"
+
+
+//---------------------------------------------------------------------------------------
+//
+// Constructor for CordbStackWalk.
+//
+// Arguments:
+// pCordbThread - the thread on which this stackwalker is created
+//
+
+CordbStackWalk::CordbStackWalk(CordbThread * pCordbThread)
+ : CordbBase(pCordbThread->GetProcess(), 0, enumCordbStackWalk),
+ m_pCordbThread(pCordbThread),
+ m_pSFIHandle(NULL),
+ m_cachedSetContextFlag(SET_CONTEXT_FLAG_ACTIVE_FRAME),
+ m_cachedHR(S_OK),
+ m_fIsOneFrameAhead(false)
+{
+ m_pCachedFrame.Clear();
+}
+
+void CordbStackWalk::Init()
+{
+ CordbProcess * pProcess = GetProcess();
+ m_lastSyncFlushCounter = pProcess->m_flushCounter;
+
+ IDacDbiInterface * pDAC = pProcess->GetDAC();
+ pDAC->CreateStackWalk(m_pCordbThread->m_vmThreadToken,
+ &m_context,
+ &m_pSFIHandle);
+
+ // see the function header of code:CordbStackWalk::CheckForLegacyHijackCase
+ CheckForLegacyHijackCase();
+
+ // Add itself to the neuter list.
+ m_pCordbThread->GetRefreshStackNeuterList()->Add(GetProcess(), this);
+}
+
+// ----------------------------------------------------------------------------
+// CordbStackWalk::CheckForLegacyHijackCase
+//
+// Description:
+// @dbgtodo legacy interop debugging - In the case of an unhandled hardware exception, the
+// thread will be hijacked to code:Debugger::GenericHijackFunc, which the stackwalker doesn't know how to
+// unwind. We can teach the stackwalker to recognize that hijack stub, but since it's going to be deprecated
+// anyway, it's not worth the effort. So we check for the hijack CONTEXT here and use it as the CONTEXT. This
+// check should be removed when we are completely
+// out-of-process.
+//
+
+void CordbStackWalk::CheckForLegacyHijackCase()
+{
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ CordbProcess * pProcess = GetProcess();
+
+ // Only do this if we have a shim and we are interop-debugging.
+ if ((pProcess->GetShim() != NULL) &&
+ pProcess->IsInteropDebugging())
+ {
+ // And only if we have a CordbUnmanagedThread and we are hijacked to code:Debugger::GenericHijackFunc
+ CordbUnmanagedThread * pUT = pProcess->GetUnmanagedThread(m_pCordbThread->GetVolatileOSThreadID());
+ if (pUT != NULL)
+ {
+ if (pUT->IsFirstChanceHijacked() || pUT->IsGenericHijacked())
+ {
+ // The GetThreadContext function hides the effects of hijacking and returns the unhijacked context
+ m_context.ContextFlags = DT_CONTEXT_FULL;
+ pUT->GetThreadContext(&m_context);
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ pDAC->SetStackWalkCurrentContext(m_pCordbThread->m_vmThreadToken,
+ m_pSFIHandle,
+ SET_CONTEXT_FLAG_ACTIVE_FRAME,
+ &m_context);
+ }
+ }
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Destructor for CordbStackWalk.
+//
+// Notes:
+// We don't really need to do anything here since the CordbStackWalk should have been neutered already.
+//
+
+CordbStackWalk::~CordbStackWalk()
+{
+ _ASSERTE(IsNeutered());
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This function resets all the state on a CordbStackWalk and releases all the memory.
+// It is used for neutering and refreshing.
+//
+
+void CordbStackWalk::DeleteAll()
+{
+ _ASSERTE(GetProcess()->GetProcessLock()->HasLock());
+
+ // delete allocated memory
+ if (m_pSFIHandle)
+ {
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ // For Mac debugging, it's not safe to call into the DAC once
+ // code:INativeEventPipeline::TerminateProcess is called. This is because the transport will not
+ // work anymore. The sole purpose of calling DeleteStackWalk() is to release the resources and
+ // memory allocated for the stackwalk. In the remote debugging case, the memory is allocated in
+ // the debuggee process. If the process is already terminated, then it's ok to skip the call.
+ if (!GetProcess()->m_exiting)
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+ {
+ // This Delete call shouldn't actually throw. Worst case, the DDImpl leaked memory.
+ GetProcess()->GetDAC()->DeleteStackWalk(m_pSFIHandle);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ m_pSFIHandle = NULL;
+ }
+
+ // clear out the cached frame
+ m_pCachedFrame.Clear();
+ m_cachedHR = S_OK;
+ m_fIsOneFrameAhead = false;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Release all memory used by the stackwalker.
+//
+//
+// Notes:
+// CordbStackWalk is neutered by CordbThread or CleanupStack().
+//
+
+void CordbStackWalk::Neuter()
+{
+ if (IsNeutered())
+ {
+ return;
+ }
+
+ DeleteAll();
+ CordbBase::Neuter();
+}
+
+// standard QI function
+HRESULT CordbStackWalk::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugStackWalk)
+ {
+ *pInterface = static_cast<ICorDebugStackWalk*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugStackWalk*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Refreshes all the state stored on the CordbStackWalk. This is necessary because sending IPC events to
+// the LS flushes the DAC cache, and m_pSFIHandle is allocated entirely in DAC memory. So, we keep track
+// of whether we have sent an IPC event and refresh the CordbStackWalk if necessary.
+//
+// Notes:
+// Throws on error.
+//
+
+void CordbStackWalk::RefreshIfNeeded()
+{
+ CordbProcess * pProcess = GetProcess();
+ _ASSERTE(pProcess->GetProcessLock()->HasLock());
+
+ // check if we need to refresh
+ if (m_lastSyncFlushCounter != pProcess->m_flushCounter)
+ {
+ // Make a local copy of the CONTEXT here. DeleteAll() will delete the CONTEXT on the cached frame,
+ // and CreateStackWalk() actually uses the CONTEXT buffer we pass to it.
+ DT_CONTEXT ctx;
+ if (m_fIsOneFrameAhead)
+ {
+ ctx = *(m_pCachedFrame->GetContext());
+ }
+ else
+ {
+ ctx = m_context;
+ }
+
+ // clear all the state
+ DeleteAll();
+
+ // create a new stackwalk handle
+ pProcess->GetDAC()->CreateStackWalk(m_pCordbThread->m_vmThreadToken,
+ &m_context,
+ &m_pSFIHandle);
+
+ // advance the stackwalker to where we originally were
+ SetContextWorker(m_cachedSetContextFlag, sizeof(DT_CONTEXT), reinterpret_cast<BYTE *>(&ctx));
+
+ // update the sync counter
+ m_lastSyncFlushCounter = pProcess->m_flushCounter;
+ }
+} // CordbStackWalk::RefreshIfNeeded()
+
+//---------------------------------------------------------------------------------------
+//
+// Retrieves the CONTEXT of the current frame.
+//
+// Arguments:
+// contextFlags - context flags used to determine the required size for the buffer
+// contextBufSize - size of the CONTEXT buffer
+// pContextSize - out parameter; returns the size required for the CONTEXT buffer
+// pbContextBuf - the CONTEXT buffer
+//
+// Return Value:
+// Return S_OK on success.
+// Return CORDBG_E_PAST_END_OF_STACK if we are already at the end of the stack.
+// Return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) if the buffer is too small.
+// Return E_FAIL on other failures.
+//
+
+HRESULT CordbStackWalk::GetContext(ULONG32 contextFlags,
+ ULONG32 contextBufSize,
+ ULONG32 * pContextSize,
+ BYTE pbContextBuf[])
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ RefreshIfNeeded();
+
+ // set the required size for the CONTEXT buffer
+ if (pContextSize != NULL)
+ {
+ *pContextSize = ContextSizeForFlags(contextFlags);
+ }
+
+ // If all the user wants to know is the CONTEXT size, then we are done.
+ if ((contextBufSize != 0) && (pbContextBuf != NULL))
+ {
+ if (contextBufSize < 4)
+ {
+ ThrowWin32(ERROR_INSUFFICIENT_BUFFER);
+ }
+
+ DT_CONTEXT * pContext = reinterpret_cast<DT_CONTEXT *>(pbContextBuf);
+
+ // Some helper functions that examine the context expect the flags to be initialized.
+ pContext->ContextFlags = contextFlags;
+
+ // check the size of the incoming buffer
+ if (!CheckContextSizeForBuffer(contextBufSize, pbContextBuf))
+ {
+ ThrowWin32(ERROR_INSUFFICIENT_BUFFER);
+ }
+
+ // Check if we are one frame ahead. If so, returned the CONTEXT on the cached frame.
+ if (m_fIsOneFrameAhead)
+ {
+ if (m_pCachedFrame != NULL)
+ {
+ const DT_CONTEXT * pSrcContext = m_pCachedFrame->GetContext();
+ _ASSERTE(pSrcContext);
+ CORDbgCopyThreadContext(pContext, pSrcContext);
+ }
+ else
+ {
+ // We encountered a problem when we were trying to initialize the CordbNativeFrame.
+ // However, the problem occurred after we have unwound the current frame.
+ // What do we do here? We don't have the CONTEXT anymore.
+ _ASSERTE(FAILED(m_cachedHR));
+ ThrowHR(m_cachedHR);
+ }
+ }
+ else
+ {
+ // No easy way out in this case. We have to call the DDI.
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+
+ IDacDbiInterface::FrameType ft = pDAC->GetStackWalkCurrentFrameInfo(m_pSFIHandle, NULL);
+ if (ft == IDacDbiInterface::kInvalid)
+ {
+ ThrowHR(E_FAIL);
+ }
+ else if (ft == IDacDbiInterface::kAtEndOfStack)
+ {
+ ThrowHR(CORDBG_E_PAST_END_OF_STACK);
+ }
+ else if (ft == IDacDbiInterface::kExplicitFrame)
+ {
+ ThrowHR(CORDBG_E_NO_CONTEXT_FOR_INTERNAL_FRAME);
+ }
+ else
+ {
+ // We always store the current CONTEXT, so just copy it into the buffer.
+ CORDbgCopyThreadContext(pContext, &m_context);
+ }
+ }
+ }
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Set the stackwalker to the specified CONTEXT.
+//
+// Arguments:
+// flag - context flags used to determine the size of the CONTEXT
+// contextSize - the size of the CONTEXT
+// context - the CONTEXT as a byte array
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if context is NULL
+// Return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) if the CONTEXT is too small.
+// Return E_FAIL on other failures.
+//
+
+HRESULT CordbStackWalk::SetContext(CorDebugSetContextFlag flag, ULONG32 contextSize, BYTE context[])
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ RefreshIfNeeded();
+ SetContextWorker(flag, contextSize, context);
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Refer to the comment for code:CordbStackWalk::SetContext
+//
+
+void CordbStackWalk::SetContextWorker(CorDebugSetContextFlag flag, ULONG32 contextSize, BYTE context[])
+{
+ if (context == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ if (!CheckContextSizeForBuffer(contextSize, context))
+ {
+ ThrowWin32(ERROR_INSUFFICIENT_BUFFER);
+ }
+
+ // invalidate the cache
+ m_pCachedFrame.Clear();
+ m_cachedHR = S_OK;
+ m_fIsOneFrameAhead = false;
+
+ DT_CONTEXT * pSrcContext = reinterpret_cast<DT_CONTEXT *>(context);
+
+ // Check the incoming CONTEXT using a temporary CONTEXT buffer before updating our real CONTEXT buffer.
+ // The incoming CONTEXT is not required to have all the bits set in its CONTEXT flags, so only update
+ // the registers specified by the CONTEXT flags. Note that CORDbgCopyThreadContext() honours the CONTEXT
+ // flags on both the source and the destination CONTEXTs when it copies them.
+ DT_CONTEXT tmpCtx = m_context;
+ tmpCtx.ContextFlags |= pSrcContext->ContextFlags;
+ CORDbgCopyThreadContext(&tmpCtx, pSrcContext);
+
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ IfFailThrow(pDAC->CheckContext(m_pCordbThread->m_vmThreadToken, &tmpCtx));
+
+ // At this point we have done all of our checks to verify that the incoming CONTEXT is sane, so we can
+ // update our internal CONTEXT buffer.
+ m_context = tmpCtx;
+ m_cachedSetContextFlag = flag;
+
+ pDAC->SetStackWalkCurrentContext(m_pCordbThread->m_vmThreadToken,
+ m_pSFIHandle,
+ flag,
+ &m_context);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Helper to perform all the necessary operations when we unwind, including:
+// 1) Unwind
+// 2) Save the new unwound CONTEXT
+//
+// Return Value:
+// Return TRUE if we successfully unwind to the next frame.
+// Return FALSE if there is no more frame to walk.
+// Throw on error.
+//
+
+BOOL CordbStackWalk::UnwindStackFrame()
+{
+ CordbProcess * pProcess = GetProcess();
+ _ASSERTE(pProcess->GetProcessLock()->HasLock());
+
+ IDacDbiInterface * pDAC = pProcess->GetDAC();
+ BOOL retVal = pDAC->UnwindStackWalkFrame(m_pSFIHandle);
+
+ // Now that we have unwound, make sure we update the CONTEXT buffer to reflect the current stack frame.
+ // This call is safe regardless of whether the unwind is successful or not.
+ pDAC->GetStackWalkCurrentContext(m_pSFIHandle, &m_context);
+
+ return retVal;
+} // CordbStackWalk::UnwindStackWalkFrame
+
+//---------------------------------------------------------------------------------------
+//
+// Unwind the stackwalker to the next frame.
+//
+// Return Value:
+// Return S_OK on success.
+// Return CORDBG_E_FAIL_TO_UNWIND_FRAME if the unwind fails.
+// Return CORDBG_S_AT_END_OF_STACK if we have reached the end of the stack as a result of this unwind.
+// Return CORDBG_E_PAST_END_OF_STACK if we are already at the end of the stack to begin with.
+//
+
+HRESULT CordbStackWalk::Next()
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ RefreshIfNeeded();
+ if (m_fIsOneFrameAhead)
+ {
+ // We have already unwound to the next frame when we materialize the CordbNativeFrame
+ // for the current frame. So we just need to clear the cache because we are already at
+ // the next frame.
+ if (m_pCachedFrame != NULL)
+ {
+ m_pCachedFrame.Clear();
+ }
+ m_cachedHR = S_OK;
+ m_fIsOneFrameAhead = false;
+ }
+ else
+ {
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ IDacDbiInterface::FrameType ft = IDacDbiInterface::kInvalid;
+
+ ft = pDAC->GetStackWalkCurrentFrameInfo(this->m_pSFIHandle, NULL);
+ if (ft == IDacDbiInterface::kAtEndOfStack)
+ {
+ ThrowHR(CORDBG_E_PAST_END_OF_STACK);
+ }
+
+ // update the cahced flag to indicate that we have reached an unwind CONTEXT
+ m_cachedSetContextFlag = SET_CONTEXT_FLAG_UNWIND_FRAME;
+
+ if (UnwindStackFrame())
+ {
+ hr = S_OK;
+ }
+ else
+ {
+ hr = CORDBG_S_AT_END_OF_STACK;
+ }
+ }
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Retrieves an ICDFrame corresponding to the current frame:
+// Stopped At Out Parameter Return Value
+// ---------- ------------- ------------
+// explicit frame CordbInternalFrame S_OK
+// managed stack frame CordbNativeFrame S_OK
+// native stack frame NULL S_FALSE
+//
+// Arguments:
+// ppFrame - out parameter; return the ICDFrame
+//
+// Return Value:
+// On success return the HRs above.
+// Return CORDBG_E_PAST_END_OF_STACK if we are already at the end of the stack.
+// Return E_INVALIDARG if ppFrame is NULL
+// Return E_FAIL on other errors.
+//
+// Notes:
+// This is just a wrapper with an EX_TRY/EX_CATCH_HRESULT for GetFrameWorker().
+//
+
+HRESULT CordbStackWalk::GetFrame(ICorDebugFrame ** ppFrame)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_NO_LOCK_BEGIN(this)
+ {
+ ATT_REQUIRE_STOPPED_MAY_FAIL_OR_THROW(GetProcess(), ThrowHR);
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ RefreshIfNeeded();
+ hr = GetFrameWorker(ppFrame);
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+
+ if (FAILED(hr))
+ {
+ if (m_fIsOneFrameAhead && (m_pCachedFrame == NULL))
+ {
+ // We encountered a problem when we try to materialize a CordbNativeFrame.
+ // Cache the failure HR so that we can return it later if the caller
+ // calls GetFrame() again or GetContext().
+ m_cachedHR = hr;
+ }
+ }
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Refer to the comment for code:CordbStackWalk::GetFrame
+//
+
+HRESULT CordbStackWalk::GetFrameWorker(ICorDebugFrame ** ppFrame)
+{
+ _ASSERTE(GetProcess()->GetProcessLock()->HasLock());
+
+ if (ppFrame == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+ *ppFrame = NULL;
+
+ RSInitHolder<CordbFrame> pResultFrame(NULL);
+
+ if (m_fIsOneFrameAhead)
+ {
+ if (m_pCachedFrame != NULL)
+ {
+ pResultFrame.Assign(m_pCachedFrame);
+ pResultFrame.TransferOwnershipExternal(ppFrame);
+ return S_OK;
+ }
+ else
+ {
+ // We encountered a problem when we were trying to initialize the CordbNativeFrame.
+ // However, the problem occurred after we have unwound the current frame.
+ // Whatever error code we return, it should be the same one GetContext() returns.
+ _ASSERTE(FAILED(m_cachedHR));
+ ThrowHR(m_cachedHR);
+ }
+ }
+
+ IDacDbiInterface * pDAC = NULL;
+ DebuggerIPCE_STRData frameData;
+ ZeroMemory(&frameData, sizeof(frameData));
+ IDacDbiInterface::FrameType ft = IDacDbiInterface::kInvalid;
+
+ pDAC = GetProcess()->GetDAC();
+ ft = pDAC->GetStackWalkCurrentFrameInfo(m_pSFIHandle, &frameData);
+
+ if (ft == IDacDbiInterface::kInvalid)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CSW::GFW - invalid stackwalker (%p)", this);
+ ThrowHR(E_FAIL);
+ }
+ else if (ft == IDacDbiInterface::kAtEndOfStack)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CSW::GFW - past end of stack (%p)", this);
+ ThrowHR(CORDBG_E_PAST_END_OF_STACK);
+ }
+ else if (ft == IDacDbiInterface::kNativeStackFrame)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CSW::GFW - native stack frame (%p)", this);
+ return S_FALSE;
+ }
+ else if (ft == IDacDbiInterface::kExplicitFrame)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CSW::GFW - explicit frame (%p)", this);
+
+ // We no longer expect to get internal frames by unwinding.
+ GetProcess()->TargetConsistencyCheck(false);
+ }
+ else if (ft == IDacDbiInterface::kManagedStackFrame)
+ {
+ _ASSERTE(frameData.eType == DebuggerIPCE_STRData::cMethodFrame);
+
+ HRESULT hr = S_OK;
+
+ // In order to find the FramePointer on x86, we need to unwind to the next frame.
+ // Technically, only x86 needs to do this, because the x86 runtime stackwalker doesn't uwnind
+ // one frame ahead of time. However, we are doing this on all platforms to keep things simple.
+ BOOL fSuccess = UnwindStackFrame();
+ (void)fSuccess; //prevent "unused variable" error from GCC
+ _ASSERTE(fSuccess);
+
+ m_fIsOneFrameAhead = true;
+#if defined(DBG_TARGET_X86)
+ frameData.fp = pDAC->GetFramePointer(m_pSFIHandle);
+#endif // DBG_TARGET_X86
+
+ // currentFuncData contains general information about the method.
+ // It has no information about any particular jitted instance of the method.
+ DebuggerIPCE_FuncData * pFuncData = &(frameData.v.funcData);
+
+ // currentJITFuncData contains information about the current jitted instance of the method
+ // on the stack.
+ DebuggerIPCE_JITFuncData * pJITFuncData = &(frameData.v.jitFuncData);
+
+ // Lookup the appdomain that the thread was in when it was executing code for this frame. We pass this
+ // to the frame when we create it so we can properly resolve locals in that frame later.
+ CordbAppDomain * pCurrentAppDomain = GetProcess()->LookupOrCreateAppDomain(frameData.vmCurrentAppDomainToken);
+ _ASSERTE(pCurrentAppDomain != NULL);
+
+ // Lookup the module
+ CordbModule* pModule = pCurrentAppDomain->LookupOrCreateModule(pFuncData->vmDomainFile);
+ PREFIX_ASSUME(pModule != NULL);
+
+ // Create or look up a CordbNativeCode. There is one for each jitted instance of a method,
+ // and we may have multiple instances because of generics.
+ CordbNativeCode * pNativeCode = pModule->LookupOrCreateNativeCode(pFuncData->funcMetadataToken,
+ pJITFuncData->vmNativeCodeMethodDescToken,
+ pJITFuncData->nativeStartAddressPtr);
+ IfFailThrow(hr);
+
+ // The native code object will create the function object if needed
+ CordbFunction * pFunction = pNativeCode->GetFunction();
+
+ // A CordbFunction is theoretically the uninstantiated method, yet for back-compat we allow
+ // debuggers to assume that it corresponds to exactly 1 native code blob. In order for
+ // an open generic function to know what native code to give back, we attach an arbitrary
+ // native code that we located through code inspection.
+ // Note that not all CordbFunction objects get created via stack traces because you can also
+ // create them by name. In that case you still won't get code for Open generic functions
+ // because we will never have attached one and the lookup by token is insufficient. This
+ // behavior mimics our 2.0 debugging behavior though so its not a regression.
+ pFunction->NotifyCodeCreated(pNativeCode);
+
+ IfFailThrow(hr);
+
+ _ASSERTE((pFunction != NULL) && (pNativeCode != NULL));
+
+ // initialize the auxiliary info required for funclets
+ CordbMiscFrame miscFrame(pJITFuncData);
+
+ // Create the native frame.
+ CordbNativeFrame* pNativeFrame = new CordbNativeFrame(m_pCordbThread,
+ frameData.fp,
+ pNativeCode,
+ pJITFuncData->nativeOffset,
+ &(frameData.rd),
+ frameData.v.taAmbientESP,
+ !!frameData.quicklyUnwound,
+ pCurrentAppDomain,
+ &miscFrame,
+ &(frameData.ctx));
+
+ pResultFrame.Assign(static_cast<CordbFrame *>(pNativeFrame));
+ m_pCachedFrame.Assign(static_cast<CordbFrame *>(pNativeFrame));
+
+ // @dbgtodo dynamic language debugging
+ // If we are dealing with a dynamic method (e.g. an IL stub, a LCG method, etc.),
+ // then we don't have the metadata or the debug info (sequence points, etc.).
+ // This means that we can't do anything meaningful with a CordbJITILFrame anyway,
+ // so let's not create the CordbJITILFrame at all. Note that methods created with
+ // RefEmit are okay, i.e. they have metadata.
+
+ // The check for IsNativeImpl() != CordbFunction::kNativeOnly catches an odd profiler
+ // case. A profiler can rewrite assemblies at load time so that a P/invoke becomes a
+ // regular managed method. mscordbi isn't yet designed to handle runtime metadata
+ // changes, so it still thinks the method is a p/invoke. If we only relied on
+ // frameData.v.fNoMetadata which is populated by the DAC, that will report
+ // FALSE (the method does have metadata/IL now). However pNativeCode->LoadNativeInfo
+ // is going to check DBI's metadata and calculate this is a p/invoke, which will
+ // throw an exception that the method isn't IL.
+ // Ideally we probably want to expose the profiler's change to the method,
+ // however that will take significant work. Part of that is correctly detecting and
+ // updating metadata in DBI, part is determinging if/how the debugger is notified,
+ // and part is auditing mscordbi to ensure that anything we cached based on the
+ // old metadata is correctly invalidated.
+ // Since this is a late fix going into a controlled servicing release I have
+ // opted for a much narrower fix. Doing the check for IsNativeImpl() != CordbFunction::kNativeOnly
+ // will continue to treat our new method as though it was a p/invoke, and the
+ // debugger will not provide IL for it. The debugger can't inspect within the profiler
+ // modified method, but at least the error won't leak out to interfere with inspection
+ // of the callstack as a whole.
+ if (!frameData.v.fNoMetadata &&
+ pNativeCode->GetFunction()->IsNativeImpl() != CordbFunction::kNativeOnly)
+ {
+ pNativeCode->LoadNativeInfo();
+
+ // By design, when a managed exception occurs we return the sequence point containing the faulting
+ // instruction in the leaf frame. In the past we didn't always achieve this,
+ // but we are being more deliberate about this behavior now.
+
+ // If jsutAfterILThrow is true, it means nativeOffset points to the return address of IL_Throw
+ // (or another JIT exception helper) after an exception has been thrown.
+ // In such cases we want to adjust nativeOffset, so it will point an actual exception callsite.
+ // By subtracting STACKWALK_CONTROLPC_ADJUST_OFFSET from nativeOffset you can get
+ // an address somewhere inside CALL instruction.
+ // This ensures more consistent placement of exception line highlighting in Visual Studio
+ DWORD nativeOffsetToMap = pJITFuncData->jsutAfterILThrow ?
+ (DWORD)pJITFuncData->nativeOffset - STACKWALK_CONTROLPC_ADJUST_OFFSET :
+ (DWORD)pJITFuncData->nativeOffset;
+ CorDebugMappingResult mappingType;
+ ULONG uILOffset = pNativeCode->GetSequencePoints()->MapNativeOffsetToIL(
+ nativeOffsetToMap,
+ &mappingType);
+
+ // Find or create the IL Code, and the pJITILFrame.
+ RSExtSmartPtr<CordbILCode> pCode;
+
+ // The code for populating CordbFunction ILCode looks really bizzare... it appears to only grab the
+ // correct version of the IL if that is still the current EnC version yet it is populated deliberately
+ // late bound at which point the latest version may be different. In fact even here the latest version
+ // could already be different, but this is no worse than what the code used to do
+ hr = pFunction->GetILCode(&pCode);
+ IfFailThrow(hr);
+ _ASSERTE(pCode != NULL);
+
+ // We populate the code for ReJit eagerly to make sure we still have it if the profiler removes the
+ // instrumentation later. Of course the only way it will still be accesible to our caller is if he
+ // saves a pointer to the ILCode.
+ // I'm not sure if ignoring rejit for mini-dumps is the right call long term, but we aren't doing
+ // anything special to collect the memory at dump time so we better be prepared to not fetch it here.
+ // We'll attempt to treat it as not being instrumented, though I suspect the abstraction is leaky.
+ RSSmartPtr<CordbReJitILCode> pReJitCode;
+ EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY
+ {
+ VMPTR_ReJitInfo reJitInfo = VMPTR_ReJitInfo::NullPtr();
+ IfFailThrow(GetProcess()->GetDAC()->GetReJitInfo(pJITFuncData->vmNativeCodeMethodDescToken, pJITFuncData->nativeStartAddressPtr, &reJitInfo));
+ if (!reJitInfo.IsNull())
+ {
+ VMPTR_SharedReJitInfo sharedReJitInfo = VMPTR_SharedReJitInfo::NullPtr();
+ IfFailThrow(GetProcess()->GetDAC()->GetSharedReJitInfo(reJitInfo, &sharedReJitInfo));
+ IfFailThrow(pFunction->LookupOrCreateReJitILCode(sharedReJitInfo, &pReJitCode));
+ }
+ }
+ EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY
+
+
+
+ RSInitHolder<CordbJITILFrame> pJITILFrame(new CordbJITILFrame(pNativeFrame,
+ pCode,
+ uILOffset,
+ mappingType,
+ frameData.v.exactGenericArgsToken,
+ frameData.v.dwExactGenericArgsTokenIndex,
+ !!frameData.v.fVarArgs,
+ pReJitCode));
+
+ // Initialize the frame. This is a nop if the method is not a vararg method.
+ hr = pJITILFrame->Init();
+ IfFailThrow(hr);
+
+ pNativeFrame->m_JITILFrame.Assign(pJITILFrame);
+ pJITILFrame.ClearAndMarkDontNeuter();
+ }
+
+ STRESS_LOG3(LF_CORDB, LL_INFO1000, "CSW::GFW - managed stack frame (%p): CNF - 0x%p, CJILF - 0x%p",
+ this, pNativeFrame, pNativeFrame->m_JITILFrame.GetValue());
+ } // kManagedStackFrame
+ else if (ft == IDacDbiInterface::kNativeRuntimeUnwindableStackFrame)
+ {
+ _ASSERTE(frameData.eType == DebuggerIPCE_STRData::cRuntimeNativeFrame);
+
+ // In order to find the FramePointer on x86, we need to unwind to the next frame.
+ // Technically, only x86 needs to do this, because the x86 runtime stackwalker doesn't uwnind
+ // one frame ahead of time. However, we are doing this on all platforms to keep things simple.
+ BOOL fSuccess = UnwindStackFrame();
+ (void)fSuccess; //prevent "unused variable" error from GCC
+ _ASSERTE(fSuccess);
+
+ m_fIsOneFrameAhead = true;
+#if defined(DBG_TARGET_X86)
+ frameData.fp = pDAC->GetFramePointer(m_pSFIHandle);
+#endif // DBG_TARGET_X86
+
+ // Lookup the appdomain that the thread was in when it was executing code for this frame. We pass this
+ // to the frame when we create it so we can properly resolve locals in that frame later.
+ CordbAppDomain * pCurrentAppDomain =
+ GetProcess()->LookupOrCreateAppDomain(frameData.vmCurrentAppDomainToken);
+ _ASSERTE(pCurrentAppDomain != NULL);
+
+ CordbRuntimeUnwindableFrame * pRuntimeFrame = new CordbRuntimeUnwindableFrame(m_pCordbThread,
+ frameData.fp,
+ pCurrentAppDomain,
+ &(frameData.ctx));
+
+ pResultFrame.Assign(static_cast<CordbFrame *>(pRuntimeFrame));
+ m_pCachedFrame.Assign(static_cast<CordbFrame *>(pRuntimeFrame));
+
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "CSW::GFW - runtime unwindable stack frame (%p): 0x%p",
+ this, pRuntimeFrame);
+ }
+
+ pResultFrame.TransferOwnershipExternal(ppFrame);
+
+ return S_OK;
+}
diff --git a/src/debug/di/rsthread.cpp b/src/debug/di/rsthread.cpp
new file mode 100644
index 0000000000..ae9b43cd01
--- /dev/null
+++ b/src/debug/di/rsthread.cpp
@@ -0,0 +1,11006 @@
+// 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.
+//*****************************************************************************
+
+//
+// File: rsthread.cpp
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "primitives.h"
+#include <float.h>
+#include <tls.h>
+
+// Stack-based holder for RSPTRs that we allocated to give to the LS.
+// If LS successfully takes ownership of them, then call SuppressRelease().
+// Else, dtor will free them up.
+// This is using a table protected by the ProcessLock().
+template <class T>
+class RsPtrHolder
+{
+ T * m_pObject;
+ RsPointer<T> m_ptr;
+public:
+ RsPtrHolder(T* pObject)
+ {
+ _ASSERTE(pObject != NULL);
+ m_ptr.AllocHandle(pObject->GetProcess(), pObject);
+ m_pObject = pObject;
+ }
+
+ // If owner didn't call SuppressRelease() to take ownership, then have dtor free it.
+ ~RsPtrHolder()
+ {
+ if (!m_ptr.IsNull())
+ {
+ // @dbgtodo synchronization - push this up. Note that since this is in a dtor;
+ // need to order it well against RSLockHolder.
+ RSLockHolder lockHolder(m_pObject->GetProcess()->GetProcessLock());
+ T* pObjTest = m_ptr.UnWrapAndRemove(m_pObject->GetProcess());
+ (void)pObjTest; //prevent "unused variable" error from GCC
+ _ASSERTE(pObjTest == m_pObject);
+ }
+ }
+
+ RsPointer<T> Ptr()
+ {
+ return m_ptr;
+ }
+ void SuppressRelease()
+ {
+ m_ptr = RsPointer<T>::NullPtr();
+ }
+
+};
+
+/* ------------------------------------------------------------------------- *
+ * Managed Thread classes
+ * ------------------------------------------------------------------------- */
+
+
+//---------------------------------------------------------------------------------------
+//
+// Instantiate a CordbThread object, which represents a managed thread.
+//
+// Arguments:
+// process - non-null process object that this thread lives in.
+// id - OS thread id of this thread.
+// handle - OS Handle to the native thread in the debuggee.
+//
+//---------------------------------------------------------------------------------------
+
+CordbThread::CordbThread(CordbProcess * pProcess, VMPTR_Thread vmThread) :
+ CordbBase(pProcess,
+ VmPtrToCookie(vmThread),
+ enumCordbThread),
+ m_pContext(NULL),
+ m_fContextFresh(false),
+ m_pAppDomain(NULL),
+ m_debugState(THREAD_RUN),
+ m_fFramesFresh(false),
+#if !defined(DBG_TARGET_ARM) // @ARMTODO
+ m_fFloatStateValid(false),
+ m_floatStackTop(0),
+#endif // !DBG_TARGET_ARM @ARMTODO
+ m_fException(false),
+ m_fCreationEventQueued(false),
+ m_EnCRemapFunctionIP(NULL),
+ m_userState(kInvalidUserState),
+ m_hCachedThread(INVALID_HANDLE_VALUE),
+ m_hCachedOutOfProcThread(INVALID_HANDLE_VALUE)
+{
+ m_fHasUnhandledException = FALSE;
+ m_pExceptionRecord = NULL;
+
+ // Thread id may be a "fake" OS id for a CLRHosted thread.
+ m_vmThreadToken = vmThread;
+
+ // This id must be unique for the thread. V2 uses the current OS thread id.
+ // If we ever support fibers, then we need to use something more unique than that.
+ m_dwUniqueID = pProcess->GetDAC()->GetUniqueThreadID(vmThread); // may throw
+
+ LOG((LF_CORDB, LL_INFO1000, "CT::CT new thread 0x%p vmptr=0x%p id=0x%x\n",
+ this, m_vmThreadToken, m_dwUniqueID));
+
+ // Unique ID should never be 0.
+ _ASSERTE(m_dwUniqueID != 0);
+
+ m_vmLeftSideContext = VMPTR_CONTEXT::NullPtr();
+ m_vmExcepObjHandle = VMPTR_OBJECTHANDLE::NullPtr();
+
+#if defined(_DEBUG) && !defined(DBG_TARGET_ARM) // @ARMTODO
+ for (unsigned int i = 0;
+ i < (sizeof(m_floatValues) / sizeof(m_floatValues[0]));
+ i++)
+ {
+ m_floatValues[i] = 0;
+ }
+#endif
+
+ // Set AppDomain
+ VMPTR_AppDomain vmAppDomain = pProcess->GetDAC()->GetCurrentAppDomain(vmThread);
+ m_pAppDomain = pProcess->LookupOrCreateAppDomain(vmAppDomain);
+ _ASSERTE(m_pAppDomain != NULL);
+}
+
+
+CordbThread::~CordbThread()
+{
+ // We've already been neutered, thus we don't need to call CleanupStack().
+ // That will have neutered + cleared frames + chains.
+ _ASSERTE(IsNeutered());
+
+ // Cleared in neuter
+ _ASSERTE(m_pContext == NULL);
+ _ASSERTE(m_hCachedThread == INVALID_HANDLE_VALUE);
+ _ASSERTE(m_pExceptionRecord == NULL);
+}
+
+// Neutered by the CordbProcess
+void CordbThread::Neuter()
+{
+ if (IsNeutered())
+ {
+ return;
+ }
+
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ delete m_pExceptionRecord;
+ m_pExceptionRecord = NULL;
+
+ // Neuter frames & Chains.
+ CleanupStack();
+
+
+ if (m_hCachedThread != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(m_hCachedThread);
+ m_hCachedThread = INVALID_HANDLE_VALUE;
+ }
+
+ if( m_pContext != NULL )
+ {
+ delete [] m_pContext;
+ m_pContext = NULL;
+ }
+
+ ClearStackFrameCache();
+
+ CordbBase::Neuter();
+}
+
+HRESULT CordbThread::QueryInterface(REFIID id, void ** ppInterface)
+{
+ if (id == IID_ICorDebugThread)
+ {
+ *ppInterface = static_cast<ICorDebugThread *>(this);
+ }
+ else if (id == IID_ICorDebugThread2)
+ {
+ *ppInterface = static_cast<ICorDebugThread2 *>(this);
+ }
+ else if (id == IID_ICorDebugThread3)
+ {
+ *ppInterface = static_cast<ICorDebugThread3*>(this);
+ }
+ else if (id == IID_ICorDebugThread4)
+ {
+ *ppInterface = static_cast<ICorDebugThread4*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *ppInterface = static_cast<IUnknown *>(static_cast<ICorDebugThread *>(this));
+ }
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+
+#ifdef _DEBUG
+// Callback helper for code:CordbThread::DbgAssertThreadDeleted
+//
+// Arguments:
+// vmThread - thread from enumeration of threads in the target.
+// pUserData - the CordbThread for the thread that's deleted
+//
+// static
+void CordbThread::DbgAssertThreadDeletedCallback(VMPTR_Thread vmThread, void * pUserData)
+{
+ CordbThread * pThis = reinterpret_cast<CordbThread *>(pUserData);
+ INTERNAL_DAC_CALLBACK(pThis->GetProcess());
+
+ VMPTR_Thread vmThreadDelete = pThis->m_vmThreadToken;
+
+ CONSISTENCY_CHECK_MSGF((vmThread != vmThreadDelete),
+ ("A Thread Exit event was sent, but it still shows up in the enumeration.\n vmThreadDelete=%p\n",
+ VmPtrToCookie(vmThreadDelete)));
+}
+
+// Debug-only helper to Assert that this thread is no longer discoverable in DacDbi enumerations
+// This is designed to enforce the code:IDacDbiInterface#Enumeration rules for enumerations.
+void CordbThread::DbgAssertThreadDeleted()
+{
+ // Enumerate through all threads and ensure the deleted threads don't show up.
+ GetProcess()->GetDAC()->EnumerateThreads(
+ DbgAssertThreadDeletedCallback,
+ this);
+}
+#endif // _DEBUG
+
+
+//---------------------------------------------------------------------------------------
+// Mark that this thread has an unhandled native exception on it.
+//
+// Arguments
+// pRecord - exception record of 2nd-chance exception that we're hijacking at. This will
+// get deep copied into the CordbThread object in case it's needed for hijacking later.
+//
+// Notes:
+// This bit is cleared in code:CordbThread::HijackForUnhandledException
+void CordbThread::SetUnhandledNativeException(const EXCEPTION_RECORD * pExceptionRecord)
+{
+ m_fHasUnhandledException = true;
+
+ if (m_pExceptionRecord == NULL)
+ {
+ m_pExceptionRecord = new EXCEPTION_RECORD(); // throws
+ }
+ memcpy(m_pExceptionRecord, pExceptionRecord, sizeof(EXCEPTION_RECORD));
+}
+
+//-----------------------------------------------------------------------------
+// Returns true if the thread has an unhandled exception
+// This is during the window after code:CordbThread::SetUnhandledNativeException is called,
+// but before code:CordbThread::HijackForUnhandledException
+bool CordbThread::HasUnhandledNativeException()
+{
+ return m_fHasUnhandledException;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Determine if the thread's latest exception is a managed exception
+//
+// Notes:
+// The CLR's UnhandledExceptionFilter has to make this same determination.
+//
+BOOL CordbThread::IsThreadExceptionManaged()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ // A Thread's latest exception is managed if the VM Thread object has a managed object
+ // for the thread's Current Exception property. The CLR's Exception system is very diligent
+ // about tracking and clearing the thread's managed exception property. The runtime will clear
+ // the object if the exception is caught by unmanaged code (it can do this in the 2nd-pass).
+
+ // It's the presence of a throwable that makes the difference between a managed
+ // exception event and an unmanaged exception event.
+
+ VMPTR_OBJECTHANDLE vmObject = GetProcess()->GetDAC()->GetCurrentException(m_vmThreadToken);
+
+ bool fHasThrowable = !vmObject.IsNull();
+
+ return fHasThrowable;
+
+}
+
+// ----------------------------------------------------------------------------
+// CordbThread::CreateCordbRegisterSet
+//
+// Description:
+// This is a private hook for the shim to create a CordbRegisterSet for a ShimChain.
+//
+// Arguments:
+// * pContext - the CONTEXT to be converted; this must be the leaf CONTEXT of a chain
+// * fLeaf - whether the chain is the leaf chain or not
+// * reason - the chain reason; this is needed for legacy reasons (see below)
+// * ppRegSet - out parameter; return the newly created ICDRegisterSet
+//
+// Notes:
+// * Note that the fQuickUnwind argument of the ctor of CordbRegisterSet is only true
+// for an enter-managed chain. We need to keep the same behaviour here. That's why we need the
+// chain reason.
+//
+
+void CordbThread::CreateCordbRegisterSet(DT_CONTEXT * pContext,
+ BOOL fLeaf,
+ CorDebugChainReason reason,
+ ICorDebugRegisterSet ** ppRegSet)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ PUBLIC_REENTRANT_API_ENTRY_FOR_SHIM(GetProcess());
+
+ IfFailThrow(EnsureThreadIsAlive());
+
+ // The CordbRegisterSet is responsible for freeing this memory.
+ NewHolder<DebuggerREGDISPLAY> pDRD(new DebuggerREGDISPLAY());
+
+ // convert the CONTEXT to a DebuggerREGDISPLAY
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ pDAC->ConvertContextToDebuggerRegDisplay(pContext, pDRD, fLeaf);
+
+ // create the CordbRegisterSet
+ RSInitHolder<CordbRegisterSet> pRS(new CordbRegisterSet(pDRD,
+ this,
+ (fLeaf == TRUE),
+ (reason == CHAIN_ENTER_MANAGED),
+ true));
+ pDRD.SuppressRelease();
+
+ pRS.TransferOwnershipExternal(ppRegSet);
+}
+
+// ----------------------------------------------------------------------------
+// CordbThread::ConvertFrameForILMethodWithoutMetadata
+//
+// Description:
+// This is a private hook for the shim to convert an ICDFrame into an ICDInternalFrame for a dynamic
+// method. There are two cases where we need this:
+// 1) In Arrowhead, dynamic methods are exposed as first-class stack frames, not internal frames. Thus,
+// the shim needs a way to convert an ICDNativeFrame for a dynamic method in Arrowhead to an
+// ICDInternalFrame of type STUBFRAME_LIGHTWEIGHT_FUNCTION in V2. Furthermore, IL stubs,
+// which are also considered as a type of dynamic methods, are not exposed in V2 at all.
+//
+// 2) In V2, PrestubMethodFrames (PMFs) can be exposed as one of two things: a chain of type
+// CHAIN_CLASS_INIT in most cases, or an internal frame of type STUBFRAME_LIGHTWEIGHT_FUNCTION if
+// the method being jitted is a dynamic method. There is no way to make this distinction at the
+// public ICD level.
+//
+// Arguments:
+// * pNativeFrame - the native frame to be converted
+// * ppInternalFrame - out parameter; the converted internal frame; could be NULL (see Notes below)
+//
+// Returns:
+// Return TRUE if conversion has occurred. Note that even if the return value is TRUE, ppInternalFrame
+// could be NULL. See Notes below.
+//
+// Notes:
+// * There are two main types of dynamic methods: ones which are generated by the runtime itself for
+// internal purposes (i.e. IL stubs), and ones which are generated by the user. ppInternalFrame
+// is NULL for IL stubs. We need this functionality because IL stubs are not exposed at all in V2.
+//
+
+BOOL CordbThread::ConvertFrameForILMethodWithoutMetadata(ICorDebugFrame * pFrame,
+ ICorDebugInternalFrame2 ** ppInternalFrame2)
+{
+ PUBLIC_REENTRANT_API_ENTRY_FOR_SHIM(GetProcess());
+
+ _ASSERTE(ppInternalFrame2 != NULL);
+ *ppInternalFrame2 = NULL;
+
+ HRESULT hr = E_FAIL;
+
+ CordbFrame * pRealFrame = CordbFrame::GetCordbFrameFromInterface(pFrame);
+
+ CordbInternalFrame * pInternalFrame = pRealFrame->GetAsInternalFrame();
+ if (pInternalFrame != NULL)
+ {
+ // The input is an internal frame.
+
+ // Check its frame type.
+ CorDebugInternalFrameType type;
+ hr = pInternalFrame->GetFrameType(&type);
+ IfFailThrow(hr);
+
+ if (type != STUBFRAME_JIT_COMPILATION)
+ {
+ // No conversion is necessary.
+ return FALSE;
+ }
+ else
+ {
+ // We are indeed dealing with a PrestubMethodFrame.
+ return pInternalFrame->ConvertInternalFrameForILMethodWithoutMetadata(ppInternalFrame2);
+ }
+ }
+ else
+ {
+ // The input is a native frame.
+ CordbNativeFrame * pNativeFrame = pRealFrame->GetAsNativeFrame();
+ _ASSERTE(pNativeFrame != NULL);
+
+ return pNativeFrame->ConvertNativeFrameForILMethodWithoutMetadata(ppInternalFrame2);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Hijack a thread at an unhandled exception. This lets it execute the
+// CLR's Unhandled Exception Filter (which will send the managed 2nd-chance exception event)
+//
+// Notes:
+// OS will not execute Unhandled Exception Filter (UEF) when debugger is attached.
+// The CLR's UEF does useful work, like dispatching 2nd-chance managed exception event
+// and allowing Func-eval and Continuable Exceptions for unhandled exceptions.
+// So hijack the thread, and the hijack will then execute the CLR's UEF just
+// like the OS would.
+void CordbThread::HijackForUnhandledException()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(m_pExceptionRecord != NULL);
+
+ _ASSERTE(m_fHasUnhandledException);
+ m_fHasUnhandledException = false;
+
+
+ ULONG32 dwThreadId = GetVolatileOSThreadID();
+
+ // Note that the data-target is not atomic, and we have no rollback mechanism.
+ // We have to do several writes. If the data-target fails the writes half-way through the
+ // target will be inconsistent.
+
+ // We don't bother remembering the original context. LS hijack will have the
+ // context on its stack and will pass it to RS just like it does for filter-context.
+ GetProcess()->GetDAC()->Hijack(
+ m_vmThreadToken,
+ dwThreadId,
+ m_pExceptionRecord,
+ NULL, // LS will have the context.
+ 0, // size of context
+ EHijackReason::kUnhandledException,
+ NULL,
+ NULL);
+
+ // Notify debugger to clear the exception.
+ // This will invoke the data-target.
+ GetProcess()->ContinueStatusChanged(dwThreadId, DBG_CONTINUE);
+}
+
+
+HRESULT CordbThread::GetProcess(ICorDebugProcess ** ppProcess)
+{
+ PUBLIC_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppProcess, ICorDebugProcess **);
+ FAIL_IF_NEUTERED(this);
+
+ *ppProcess = GetProcess();
+ GetProcess()->ExternalAddRef();
+
+ return S_OK;
+}
+
+// Public implementation of ICorDebugThread::GetID
+// Back in V1.0, GetID originally meant the OS thread ID that this managed thread was running on.
+// In theory, that can change (fibers, logical thread scheduling, etc). However, in practice, in V1.0, it would
+// not. Thus debuggers took a depedency on GetID being constant.
+// In V2, this returns an opaque handle that is unique to this thread and stable for this thread's lifetime.
+//
+// Compare to code:CordbThread::GetVolatileOSThreadID, which returns the actual OS thread Id (which may change).
+HRESULT CordbThread::GetID(DWORD * pdwThreadId)
+{
+ PUBLIC_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pdwThreadId, DWORD *);
+ FAIL_IF_NEUTERED(this);
+
+ *pdwThreadId = GetUniqueId();
+
+ return S_OK;
+}
+
+// Returns a unique ID that's stable for the life of this thread.
+// In a non-hosted scenarios, this can be the OS thread id.
+DWORD CordbThread::GetUniqueId()
+{
+ return m_dwUniqueID;
+}
+
+// Implementation of public API, ICorDebugThread::GetHandle
+// @dbgtodo ICDThread - deprecate in V3, offload to Shim
+HRESULT CordbThread::GetHandle(HANDLE * phThreadHandle)
+{
+ PUBLIC_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(phThreadHandle, HANDLE *);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (GetProcess()->GetShim() == NULL)
+ {
+ _ASSERTE(!"CordbThread::GetHandle() should be not be called on the new architecture");
+ *phThreadHandle = NULL;
+ return E_NOTIMPL;
+ }
+
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ HANDLE hThread;
+ InternalGetHandle(&hThread); // throws on error
+ *phThreadHandle = hThread;
+ }
+ EX_CATCH_HRESULT(hr);
+#else // FEATURE_DBGIPC_TRANSPORT_DI
+ // In the old SL implementation of Mac debugging, we return a thread handle faked up by the PAL on the Mac.
+ // The returned handle is meaningless. Here we explicitly return E_NOTIMPL. We plan to deprecate this
+ // function in Dev10 anyway.
+ //
+ // @dbgtodo Mac - Check with VS to see if they need the thread handle, e.g. for waiting on thread
+ // termination.
+ HRESULT hr = E_NOTIMPL;
+#endif // !FEATURE_DBGIPC_TRANSPORT_DI
+
+ return hr;
+}
+
+// Note that we can return invalid handle
+void CordbThread::InternalGetHandle(HANDLE * phThread)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ RefreshHandle(phThread);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This is a simple helper to check if a frame lives on the stack of the current thread.
+//
+// Arguments:
+// pFrame - the stack frame to check
+//
+// Return Value:
+// whether the frame lives on the stack of the current thread
+//
+// Assumption:
+// This function assumes that the stack frames are valid, i.e. the stack frames have not been
+// made dirty since the last stackwalk.
+//
+
+bool CordbThread::OwnsFrame(CordbFrame * pFrame)
+{
+ // preliminary checking
+ if ( (pFrame != NULL) &&
+ (!pFrame->IsNeutered()) &&
+ (pFrame->m_pThread == this)
+ )
+ {
+ //
+ // Note that this is one of the two remaining places where we need to use the cached stack frames.
+ // Theoretically, since this is not an exact check anyway, we could just use the thread's stack
+ // range instead of looping through all the individual frames. However, since we need to maintain
+ // the stack frame cache for code:CordbThread::GetActiveFunctions, we might as well use the cache here.
+ //
+
+ // make sure this thread actually have frames to check
+ if (m_stackFrames.Count() != 0)
+ {
+ // get the stack range of this thread
+ FramePointer fpLeaf = (*(m_stackFrames.Get(0)))->GetFramePointer();
+ FramePointer fpRoot = (*(m_stackFrames.Get(m_stackFrames.Count() - 1)))->GetFramePointer();
+
+ FramePointer fpCurrent = pFrame->GetFramePointer();
+
+ // compare the stack range against the frame pointer of the specified frame
+ if (IsEqualOrCloserToLeaf(fpLeaf, fpCurrent) && IsEqualOrCloserToRoot(fpRoot, fpCurrent))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This routine is a internal helper function for ICorDebugThread2::GetTaskId.
+//
+// Arguments:
+// pHandle - return thread handle here after fetching from the left side. Can return SWITCHOUT_HANDLE_VALUE.
+//
+// Return Value:
+// hr - It can fail with CORDBG_E_THREAD_NOT_SCHEDULED.
+//
+// Notes:
+// This method will most likely be deprecated in V3.0. We can't always return the thread handle.
+// For example, what does it mean to return a thread handle in remote debugging scenarios?
+//
+void CordbThread::RefreshHandle(HANDLE * phThread)
+{
+ // here is where we will put code in to fetch the thread handle from the left side.
+ // This should only happen when CLRTask is hosted.
+ // Make sure that we are setting the right HR when thread is being switched out.
+ THROW_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ if (phThread == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+ *phThread = INVALID_HANDLE_VALUE;
+
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ HANDLE hThread = pDAC->GetThreadHandle(m_vmThreadToken);
+
+ if (hThread == SWITCHOUT_HANDLE_VALUE)
+ {
+ *phThread = SWITCHOUT_HANDLE_VALUE;
+ ThrowHR(CORDBG_E_THREAD_NOT_SCHEDULED);
+ }
+
+ _ASSERTE(hThread != INVALID_HANDLE_VALUE);
+ PREFAST_ASSUME(hThread != NULL);
+
+ // need to dup handle here
+ if (hThread == m_hCachedOutOfProcThread)
+ {
+ *phThread = m_hCachedThread;
+ }
+ else
+ {
+ BOOL fSuccess = TRUE;
+ if (m_hCachedThread != INVALID_HANDLE_VALUE)
+ {
+ // clear the previous cache
+ CloseHandle(m_hCachedThread);
+ m_hCachedOutOfProcThread = INVALID_HANDLE_VALUE;
+ m_hCachedThread = INVALID_HANDLE_VALUE;
+ }
+
+ // now duplicate the out-of-proc handle
+ fSuccess = DuplicateHandle(GetProcess()->UnsafeGetProcessHandle(),
+ hThread,
+ GetCurrentProcess(),
+ &m_hCachedThread,
+ NULL,
+ FALSE,
+ DUPLICATE_SAME_ACCESS);
+ *phThread = m_hCachedThread;
+
+ if (fSuccess)
+ {
+ m_hCachedOutOfProcThread = hThread;
+ }
+ else
+ {
+ ThrowLastError();
+ }
+ }
+} // CordbThread::RefreshHandle
+
+
+//---------------------------------------------------------------------------------------
+//
+// This routine sets the debug state of a thread.
+//
+// Arguments:
+// state - The debug state to set to.
+//
+// Return Value:
+// Normal HRESULT semantics.
+//
+HRESULT CordbThread::SetDebugState(CorDebugThreadState state)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ LOG((LF_CORDB, LL_INFO1000, "CT::SDS: thread=0x%08x 0x%x, state=%d\n", this, m_id, state));
+
+ // @dbgtodo- , sync - decide on how to suspend a thread. V2 leverages synchronization
+ // (see below). For V3, do we just hard suspend the thread?
+ if (GetProcess()->GetShim() == NULL)
+ {
+ return E_NOTIMPL;
+ }
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ hr = EnsureThreadIsAlive();
+
+ if (SUCCEEDED(hr))
+ {
+ // This lets the debugger suspend / resume threads. This is only called when when the
+ // target is already synchronized. That means all the threads are already suspended. So
+ // setting the suspend bit here just means that the debugger's continue logic won't resume
+ // this thread when we do a Continue.
+ if ((state != THREAD_SUSPEND) && (state != THREAD_RUN))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ pDAC->SetDebugState(m_vmThreadToken, state);
+
+ m_debugState = state;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbThread::GetDebugState(CorDebugThreadState * pState)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ VALIDATE_POINTER_TO_OBJECT(pState, CorDebugThreadState *);
+
+ *pState = m_debugState;
+
+ return S_OK;
+}
+
+
+// Public implementation of ICorDebugThread::GetUserState
+// Arguments:
+// pState - out parameter; return the user state
+//
+// Return Value:
+// Return S_OK if the operation is successful.
+// Return E_INVALIDARG if the out parameter is NULL.
+// Return other failure HRs returned by the call to the DDI.
+HRESULT CordbThread::GetUserState(CorDebugUserState * pState)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pState, CorDebugUserState *);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (pState == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+ *pState = GetUserState();
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Retrieve the user state of the current thread.
+//
+// Notes:
+// This caches results between continues. The cache is cleared when the target continues or is flushed.
+// See code:CordbThread::CleanupStack, code:CordbThread::MarkStackFramesDirty
+//
+CorDebugUserState CordbThread::GetUserState()
+{
+ if (m_userState == kInvalidUserState)
+ {
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ m_userState = pDAC->GetUserState(m_vmThreadToken);
+ }
+
+ return m_userState;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// This routine finds and returns the current exception off of a thread.
+//
+// Arguments:
+// ppExceptionObject - OUT: Space for storing the exception found on the thread as a value.
+//
+// Return Value:
+// Normal HRESULT semantics.
+//
+HRESULT CordbThread::GetCurrentException(ICorDebugValue ** ppExceptionObject)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT(ppExceptionObject, ICorDebugValue **);
+ *ppExceptionObject = NULL;
+
+ EX_TRY
+ {
+ if (!HasException())
+ {
+ //
+ // Go to the LS and retrieve any exception object.
+ //
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ VMPTR_OBJECTHANDLE vmObjHandle = pDAC->GetCurrentException(m_vmThreadToken);
+
+ if (vmObjHandle.IsNull())
+ {
+ hr = S_FALSE;
+ }
+ else
+ {
+#if defined(_DEBUG)
+ // Since we know an exception is in progress on this thread, our assumption about the
+ // thread's current AppDomain should be correct
+ VMPTR_AppDomain vmAppDomain = pDAC->GetCurrentAppDomain(m_vmThreadToken);
+ _ASSERTE(GetAppDomain()->GetADToken() == vmAppDomain);
+#endif // _DEBUG
+
+ m_vmExcepObjHandle = vmObjHandle;
+ }
+ }
+
+ if (hr == S_OK)
+ {
+ // We've believe this assert may fire in the wild.
+ // We've seen m_vmExcepObjHandle null in retail builds after stack overflow.
+ _ASSERTE(!m_vmExcepObjHandle.IsNull());
+
+ ICorDebugReferenceValue * pRefValue = NULL;
+ hr = CordbReferenceValue::BuildFromGCHandle(GetAppDomain(), m_vmExcepObjHandle, &pRefValue);
+ *ppExceptionObject = pRefValue;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbThread::ClearCurrentException()
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // This API is not implemented. For Continuable Exceptions, see InterceptCurrentException.
+ // @todo - should it return E_NOTIMPL?
+ return S_OK;
+}
+
+HRESULT CordbThread::CreateStepper(ICorDebugStepper ** ppStepper)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT(ppStepper, ICorDebugStepper **);
+
+ CordbStepper * pStepper = new (nothrow) CordbStepper(this, NULL);
+
+ if (pStepper == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ pStepper->ExternalAddRef();
+ *ppStepper = pStepper;
+
+ return S_OK;
+}
+
+//Returns true if current user state of a thread is USER_WAIT_SLEEP_JOIN
+bool CordbThread::IsThreadWaitingOrSleeping()
+{
+ CorDebugUserState userState = m_userState;
+ if (userState == kInvalidUserState)
+ {
+ //If m_userState is not ready, we'll read from DAC only part of it which
+ //is important for us now, bacuase we don't want possible side effects
+ //of reading USER_UNSAFE_POINT flag.
+ //We don't cache the value, because it's potentially incomplete.
+ IDacDbiInterface *pDAC = GetProcess()->GetDAC();
+ userState = pDAC->GetPartialUserState(m_vmThreadToken);
+ }
+
+ return (userState & USER_WAIT_SLEEP_JOIN) != 0;
+}
+
+//----------------------------------------------------------------------------
+// check if the thread is dead
+//
+// Returns: true if the thread is dead.
+//
+bool CordbThread::IsThreadDead()
+{
+ return GetProcess()->GetDAC()->IsThreadMarkedDead(m_vmThreadToken);
+}
+
+// Helper to return CORDBG_E_BAD_THREAD_STATE if IsThreadDead
+//
+// Notes:
+// IsThreadDead queries the VM Thread's actual state, regardless of what ExitThread
+// callbacks have or have not been sent / queued / dispatched.
+HRESULT CordbThread::EnsureThreadIsAlive()
+{
+ if (IsThreadDead())
+ {
+ return CORDBG_E_BAD_THREAD_STATE;
+ }
+ else
+ {
+ return S_OK;
+ }
+}
+
+// ----------------------------------------------------------------------------
+// CordbThread::EnumerateChains
+//
+// Description:
+// Create and return an ICDChainEnum for enumerating chains on the stack. Since chains have been
+// deprecated in Arrowhead, this function returns E_NOTIMPL unless there is a shim.
+//
+// Arguments:
+// * ppChains - out parameter; return the ICDChainEnum
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if ppChains is NULL.
+// Return CORDBG_E_OBJECT_NEUTERED if the CordbThread is neutered.
+// Return E_NOTIMPL if there is no shim.
+//
+
+HRESULT CordbThread::EnumerateChains(ICorDebugChainEnum ** ppChains)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ VALIDATE_POINTER_TO_OBJECT(ppChains, ICorDebugChainEnum **);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ *ppChains = NULL;
+
+ if (GetProcess()->GetShim() != NULL)
+ {
+ hr = EnsureThreadIsAlive();
+
+ if (SUCCEEDED(hr))
+ {
+ // use the shim to create an ICDChainEnum
+ PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(GetProcess());
+ ShimStackWalk * pSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(this);
+ pSW->EnumerateChains(ppChains);
+ }
+ }
+ else
+ {
+ // This is the Arrowhead case, where ICDChain has been deprecated.
+ hr = E_NOTIMPL;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+// ----------------------------------------------------------------------------
+// CordbThread::GetActiveChain
+//
+// Description:
+// Retrieve the leaf chain on this thread. Since chains have been deprecated in Arrowhead,
+// this function returns E_NOTIMPL unless there is a shim.
+//
+// Arguments:
+// * ppChain - out parameter; return the leaf chain
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if ppChain is NULL.
+// Return CORDBG_E_OBJECT_NEUTERED if the CordbThread is neutered.
+// Return E_NOTIMPL if there is no shim.
+//
+
+HRESULT CordbThread::GetActiveChain(ICorDebugChain ** ppChain)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ VALIDATE_POINTER_TO_OBJECT(ppChain, ICorDebugChain **);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ *ppChain = NULL;
+ hr = EnsureThreadIsAlive();
+
+ if (SUCCEEDED(hr))
+ {
+ if (GetProcess()->GetShim() != NULL)
+ {
+ // use the shim to retrieve the leaf chain
+ PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(GetProcess());
+ ShimStackWalk * pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(this);
+ pSSW->GetActiveChain(ppChain);
+ }
+ else
+ {
+ // This is the Arrowhead case, where ICDChain has been deprecated.
+ hr = E_NOTIMPL;
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+// ----------------------------------------------------------------------------
+// CordbThread::GetActiveFrame
+//
+// Description:
+// Retrieve the leaf frame on this thread. Unfortunately, this is one of the cases where we need to
+// do different things depending on whether there is a shim. See the Notes below.
+//
+// Arguments:
+// * ppFrame - out parameter; return the leaf frame
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if ppFrame is NULL.
+// Return CORDBG_E_OBJECT_NEUTERED if the CordbThread is neutered.
+// Also return whatever CreateStackWalk() and GetFrame() return if they fail.
+//
+// Notes:
+// In V2, we return NULL if the leaf frame is not in the leaf chain, i.e. if the leaf chain is
+// empty. Note that managed chains are never empty. Also, in V2 it is possible that this API
+// will return an internal frame as the active frame on a thread.
+//
+// The Arrowhead implementation two breaking changes:
+// 1) It never returns an internal frame.
+// 2) We return a frame if the leaf frame is managed. Otherwise, we return NULL.
+//
+
+HRESULT CordbThread::GetActiveFrame(ICorDebugFrame ** ppFrame)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ VALIDATE_POINTER_TO_OBJECT(ppFrame, ICorDebugFrame **);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ *ppFrame = NULL;
+ hr = EnsureThreadIsAlive();
+
+ if (SUCCEEDED(hr))
+ {
+ if (GetProcess()->GetShim() != NULL)
+ {
+ PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(GetProcess());
+ ShimStackWalk * pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(this);
+ pSSW->GetActiveFrame(ppFrame);
+ }
+ else
+ {
+ // This is the Arrowhead case. We could call RefreshStack() here, but since we only need the
+ // leaf frame, there is no point in walking the entire stack.
+ RSExtSmartPtr<ICorDebugStackWalk> pSW;
+ hr = CreateStackWalk(&pSW);
+ IfFailThrow(hr);
+
+ hr = pSW->GetFrame(ppFrame);
+ IfFailThrow(hr);
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+// ----------------------------------------------------------------------------
+// CordbThread::GetActiveRegister
+//
+// Description:
+// In V2, retrieve the ICDRegisterSet for the leaf chain. In Arrowhead, retrieve the ICDRegisterSet
+// for the leaf CONTEXT.
+//
+// Arguments:
+// * ppRegisters - out parameter; return the ICDRegister
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if ppFrame is NULL.
+// Return CORDBG_E_OBJECT_NEUTERED if the CordbThread is neutered.
+// Also return whatever CreateStackWalk() and GetContext() return if they fail.
+//
+
+HRESULT CordbThread::GetRegisterSet(ICorDebugRegisterSet ** ppRegisters)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ VALIDATE_POINTER_TO_OBJECT(ppRegisters, ICorDebugRegisterSet **);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ *ppRegisters = NULL;
+ hr = EnsureThreadIsAlive();
+
+ if (SUCCEEDED(hr))
+ {
+ if (GetProcess()->GetShim() != NULL)
+ {
+ // use the shim to retrieve the active ICDRegisterSet
+ PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(GetProcess());
+ ShimStackWalk * pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(this);
+ pSSW->GetActiveRegisterSet(ppRegisters);
+ }
+ else
+ {
+ // This is the Arrowhead case. We could call RefreshStack() here, but since we only need the
+ // leaf frame, there is no point in walking the entire stack.
+ RSExtSmartPtr<ICorDebugStackWalk> pSW;
+ hr = CreateStackWalk(&pSW);
+ IfFailThrow(hr);
+
+ // retrieve the leaf CONTEXT
+ DT_CONTEXT ctx;
+ hr = pSW->GetContext(CONTEXT_FULL, sizeof(ctx), NULL, reinterpret_cast<BYTE *>(&ctx));
+ IfFailThrow(hr);
+
+ // the CordbRegisterSet is responsible for freeing this memory
+ NewHolder<DebuggerREGDISPLAY> pDRD(new DebuggerREGDISPLAY());
+
+ // convert the CONTEXT to a DebuggerREGDISPLAY
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ pDAC->ConvertContextToDebuggerRegDisplay(&ctx, pDRD, true);
+
+ // create the CordbRegisterSet
+ RSInitHolder<CordbRegisterSet> pRS(new CordbRegisterSet(pDRD,
+ this,
+ true, // active
+ false, // !fQuickUnwind
+ true)); // own DRD memory
+ pDRD.SuppressRelease();
+
+ pRS.TransferOwnershipExternal(ppRegisters);
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT CordbThread::CreateEval(ICorDebugEval ** ppEval)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT(ppEval, ICorDebugEval **);
+
+ CordbEval * pEval = new (nothrow) CordbEval(this);
+ if (pEval == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ pEval->ExternalAddRef();
+ *ppEval = static_cast<ICorDebugEval *>(pEval);
+
+ return S_OK;
+}
+
+// DAC check
+
+// Double check our results w/ DAC.
+// This gives DAC some great coverage.
+// Given an IP and the md token (that the RS obtained), use DAC to lookup the md token. Then
+// we can compare DAC & the RS and make sure DACs working.
+void CheckAgainstDAC(CordbFunction * pFunc, void * pIP, mdMethodDef mdExpected)
+{
+ // This is a hook to add DAC checks agaisnt a {function, ip}
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Internal function to build up a stack trace.
+//
+//
+// Return Value:
+// S_OK on success.
+//
+// Assumptions:
+// Process is stopped.
+//
+// Notes:
+// Send a IPC events to the LS to build up the stack.
+//
+//---------------------------------------------------------------------------------------
+void CordbThread::RefreshStack()
+{
+ THROW_IF_NEUTERED(this);
+
+ // We must have the Stop-Go lock to change our thread's stack-state.
+ // Also, our caller should have guaranteed that we're synced. And b/c we hold the stop-go lock,
+ // that shouldn't have changed.
+ // INTERNAL_SYNC_API_ENTRY() checks that we have the lock and that we are synced.
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ // bail out early if the stack hasn't changed
+ if (m_fFramesFresh)
+ {
+ return;
+ }
+
+ HRESULT hr = S_OK;
+
+ //
+ // Clean up old snapshot.
+ //
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ // clear the stack frame cache
+ ClearStackFrameCache();
+
+ //
+ // If we don't have a debugger thread token, then this thread has never
+ // executed managed code and we have no frame information for it.
+ //
+ if (m_vmThreadToken.IsNull())
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ // walk the stack using the V3 API and populate the stack frame cache
+ RSInitHolder<CordbStackWalk> pSW(new CordbStackWalk(this));
+ pSW->Init();
+ do
+ {
+ RSExtSmartPtr<ICorDebugFrame> pIFrame;
+ hr = pSW->GetFrame(&pIFrame);
+ IfFailThrow(hr);
+
+ if (pIFrame != NULL)
+ {
+ // add the stack frame to the cache
+ CordbFrame ** ppCFrame = m_stackFrames.AppendThrowing();
+ *ppCFrame = CordbFrame::GetCordbFrameFromInterface(pIFrame);
+
+ // Now that we have saved the pointer, increment the ref count.
+ // This has to match the InternalRelease() in code:CordbThread::ClearStackFrameCache.
+ (*ppCFrame)->InternalAddRef();
+ }
+
+ // advance to the next frame
+ hr = pSW->Next();
+ IfFailThrow(hr);
+ }
+ while (hr != CORDBG_S_AT_END_OF_STACK);
+
+ m_fFramesFresh = true;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// This function is used to invalidate and clean up the cached stack trace.
+//
+
+void CordbThread::CleanupStack()
+{
+ _ASSERTE(GetProcess()->GetProcessLock()->HasLock());
+
+ // Neuter outstanding CordbChainEnums, CordbFrameEnums, some CordbTypeEnums, and some CordbValueEnums.
+ m_RefreshStackNeuterList.NeuterAndClear(GetProcess());
+
+ m_fContextFresh = false; // invalidate the cached active CONTEXT
+ m_vmLeftSideContext = VMPTR_CONTEXT::NullPtr(); // set the LS pointer to the active CONTEXT to NULL
+ m_fFramesFresh = false; // invalidate the cached stack trace (frames & chains)
+ m_userState = kInvalidUserState; // clear the cached user state
+
+ // tell the shim to flush its caches as well
+ if (GetProcess()->GetShim() != NULL)
+ {
+ GetProcess()->GetShim()->NotifyOnStackInvalidate();
+ }
+}
+
+// Notifying the thread that the process is being continued.
+// This will cause our caches to get invalidated without actually cleaning the caches.
+void CordbThread::MarkStackFramesDirty()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+#if !defined(DBG_TARGET_ARM) // @ARMTODO
+ // invalidate the cached floating point state
+ m_fFloatStateValid = false;
+#endif // !defined(DBG_TARGET_ARM) @ARMTODO
+
+ // This flag is only true between the window when we get an exception callback and
+ // when we call continue. Since this function is only called when we continue, we
+ // need to reset this flag here. Note that in the case of an outstanding funceval,
+ // we'll set this flag again when the funceval is completed.
+ m_fException = false;
+
+ // Clear the stashed EnC remap IP address if any
+ // This is important to ensure we don't try to write into LS memory which is no longer
+ // being used to hold the remap IP.
+ m_EnCRemapFunctionIP = NULL;
+
+ m_fContextFresh = false; // invalidate the cached active CONTEXT
+ m_vmLeftSideContext = VMPTR_CONTEXT::NullPtr(); // set the LS pointer to the active CONTEXT to NULL
+ m_fFramesFresh = false; // invalidate the cached stack trace (frames & chains)
+ m_userState = kInvalidUserState; // clear the cached user state
+
+ m_RefreshStackNeuterList.NeuterAndClear(GetProcess());
+
+ // tell the shim to flush its caches as well
+ if (GetProcess()->GetShim() != NULL)
+ {
+ GetProcess()->GetShim()->NotifyOnStackInvalidate();
+ }
+}
+
+// Set that there's an outstanding exception on this thread.
+// This can be called when the process object receives an exception notification.
+// This is cleared in code:CordbThread::MarkStackFramesDirty.
+void CordbThread::SetExInfo(VMPTR_OBJECTHANDLE vmExcepObjHandle)
+{
+ m_fException = true;
+ m_vmExcepObjHandle = vmExcepObjHandle;
+
+ // CordbThread::GetCurrentException assumes that we always have a m_vmExcepObjHandle when at an exception.
+ // Push that assert up here.
+ _ASSERTE(!m_vmExcepObjHandle.IsNull());
+}
+
+
+// ----------------------------------------------------------------------------
+// CordbThread::FindFrame
+//
+// Description:
+// Given a FramePointer, find the matching CordbFrame.
+//
+// Arguments:
+// * ppFrame - out parameter; the CordbFrame to be returned
+// * fp - the input FramePointer
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_FAIL on failure.
+//
+// Assumptions:
+// * This function is only called from the shim.
+//
+// Notes:
+// * Currently this function is only used by the shim to map the FramePointer it gets via the
+// DB_IPCE_EXCEPTION_CALLBACK2 callback. When we figure out what to do with the
+// DB_IPCE_EXCEPTION_CALLBACK2, we should remove this function.
+//
+
+HRESULT CordbThread::FindFrame(ICorDebugFrame ** ppFrame, FramePointer fp)
+{
+ FAIL_IF_NEUTERED(this);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ _ASSERTE(ppFrame != NULL);
+ *ppFrame = NULL;
+
+ _ASSERTE(GetProcess()->GetShim() != NULL);
+
+ PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(GetProcess());
+ ShimStackWalk * pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(this);
+
+ for (UINT32 i = 0; i < pSSW->GetFrameCount(); i++)
+ {
+ ICorDebugFrame * pIFrame = pSSW->GetFrame(i);
+ CordbFrame * pCFrame = CordbFrame::GetCordbFrameFromInterface(pIFrame);
+
+#if defined(_WIN64)
+ // On 64-bit we can simply compare the FramePointer.
+ if (pCFrame->GetFramePointer() == fp)
+#else // !_WIN64
+ // On other platforms, we need to do a more elaborate check.
+ if (pCFrame->IsContainedInFrame(fp))
+#endif // _WIN64
+ {
+ *ppFrame = pIFrame;
+ (*ppFrame)->AddRef();
+ return S_OK;
+ }
+ }
+
+ // Cannot find the frame.
+ return E_FAIL;
+}
+
+
+#if !defined(DBG_TARGET_ARM) // @ARMTODO
+
+#if defined(CROSS_COMPILE) && defined(_TARGET_ARM64_)
+extern "C" double FPFillR8(void* pFillSlot)
+{
+ _ASSERTE(!"nyi for platform");
+ return 0;
+}
+#elif defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_)
+extern "C" double FPFillR8(void* pFillSlot);
+#endif
+
+
+#if defined(_TARGET_X86_)
+
+// CordbThread::Get32bitFPRegisters
+// Converts the values in the floating point register area of the context to real number values. See
+// code:CordbThread::LoadFloatState for more details.
+// Arguments:
+// input: pContext
+// output: none (initializes m_floatValues)
+
+void CordbThread::Get32bitFPRegisters(CONTEXT * pContext)
+{
+ // On X86, we get the values by saving our current FPU state, loading
+ // the other thread's FPU state into our own, saving out each
+ // value off the FPU stack, and then restoring our FPU state.
+ //
+ FLOATING_SAVE_AREA floatarea = pContext->FloatSave; // copy FloatSave
+
+ //
+ // Take the TOP out of the FPU status word. Note, our version of the
+ // stack runs from 0->7, not 7->0...
+ //
+ unsigned int floatStackTop = 7 - ((floatarea.StatusWord & 0x3800) >> 11);
+
+ FLOATING_SAVE_AREA currentFPUState;
+
+ __asm fnsave currentFPUState // save the current FPU state.
+
+ floatarea.StatusWord &= 0xFF00; // remove any error codes.
+ floatarea.ControlWord |= 0x3F; // mask all exceptions.
+
+ // the x86 FPU stores real numbers as 10 byte values in IEEE format. Here we use
+ // the hardware to convert these to doubles.
+
+ // @dbgtodo Microsoft crossplat: the conversion from a series of bytes to a floating
+ // point value will need to be done with an explicit conversion routine to unpack
+ // the IEEE format and compute the real number value represented.
+
+ __asm
+ {
+ fninit
+ frstor floatarea ;; reload the threads FPU state.
+ }
+
+ unsigned int i;
+
+ for (i = 0; i <= floatStackTop; i++)
+ {
+ long double td;
+ __asm fstp td // copy out the double
+ m_floatValues[i] = td;
+ }
+
+ __asm
+ {
+ fninit
+ frstor currentFPUState ;; restore our saved FPU state.
+ }
+
+ m_fFloatStateValid = true;
+ m_floatStackTop = floatStackTop;
+} // CordbThread::Get32bitFPRegisters
+
+#elif defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_)
+
+// CordbThread::Get64bitFPRegisters
+// Converts the values in the floating point register area of the context to real number values. See
+// code:CordbThread::LoadFloatState for more details.
+// Arguments:
+// input: pFPRegisterBase - starting address of the floating point register storage of the CONTEXT
+// registerSize - the size of a floating point register
+// start - the index into m_floatValues where we start initializing. For amd64, we start
+// at the beginning, but for ia64, the first two registers have fixed values,
+// so we start at two.
+// nRegisters - the number of registers to be initialized
+// output: none (initializes m_floatValues)
+
+void CordbThread::Get64bitFPRegisters(FPRegister64 * rgContextFPRegisters, int start, int nRegisters)
+{
+ // make sure no one has changed the type definition for 64-bit FP registers
+ _ASSERTE(sizeof(FPRegister64) == 16);
+ // We convert and copy all the fp registers.
+ for (int reg = start; reg < nRegisters; reg++)
+ {
+ // @dbgtodo Microsoft crossplat: the conversion from a FLOAT128 or M128A struct to a floating
+ // point value will need to be done with an explicit conversion routine instead
+ // of the call to FPFillR8
+ m_floatValues[reg] = FPFillR8(&rgContextFPRegisters[reg - start]);
+ }
+} // CordbThread::Get64bitFPRegisters
+
+#endif // _TARGET_X86_
+
+// CordbThread::LoadFloatState
+// Initializes the float state members of this instance of CordbThread. This function gets the context and
+// converts the floating point values from their context representation to a real number value. Floating
+// point numbers are represented in IEEE format on all current platforms. We store them in the context as a
+// pair of 64-bit integers (IA64 and AMD64) or a series of bytes (x86). Rather than unpack them explicitly
+// and do the appropriate mathematical operations to produce the corresponding floating point value, we let
+// the hardware do it instead. We load a floating point register with the representation from the context
+// and then store it in m_floatValues. Using the hardware is obviously a huge perf win. If/when we make
+// cross-plat work, we should at least code necessary conversion routines in assembly. Even with cross-plat,
+// we can probably still use the hardware in most cases, as long as the size is appropriate.
+//
+// Arguments: none
+// Return Value: none (initializes data members)
+// Note: Throws
+
+void CordbThread::LoadFloatState()
+{
+ THROW_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ DT_CONTEXT tempContext;
+ GetProcess()->GetDAC()->GetContext(m_vmThreadToken, &tempContext);
+
+#if defined(_TARGET_X86_)
+ Get32bitFPRegisters((CONTEXT*) &tempContext);
+#elif defined(_TARGET_AMD64_)
+ // we have no fixed-value registers, so we begin with the first one and initialize all 16
+ Get64bitFPRegisters((FPRegister64*) &(tempContext.Xmm0), 0, 16);
+#elif defined(_TARGET_ARM64_)
+ Get64bitFPRegisters((FPRegister64*) &(tempContext.V), 0, 32);
+#else
+ _ASSERTE(!"nyi for platform");
+#endif // !_TARGET_X86_
+
+ m_fFloatStateValid = true;
+} // CordbThread::LoadFloatState
+
+#endif // !DBG_TARGET_ARM @ARMTODO
+
+const bool SetIP_fCanSetIPOnly = TRUE;
+const bool SetIP_fSetIP = FALSE;
+
+const bool SetIP_fIL = TRUE;
+const bool SetIP_fNative = FALSE;
+
+//---------------------------------------------------------------------------------------
+//
+// Issues a SetIP command to the left-side and returns the result
+//
+// Arguments:
+// fCanSetIPOnly - TRUE if only to do the setip command and not refresh stacks as well.
+// debuggerModule - LS token to the debugger module.
+// mdMethod - Metadata token for the method.
+// nativeCodeJITInfoToken - LS token to the DebuggerJitInfo for the method.
+// offset - Offset within the method to set the IP to.
+// fIsIl - Is this an IL offset?
+//
+// Return Value:
+// S_OK on success.
+//
+HRESULT CordbThread::SetIP(bool fCanSetIPOnly,
+ CordbNativeCode * pNativeCode,
+ SIZE_T offset,
+ bool fIsIL)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VMPTR_DomainFile vmDomainFile = pNativeCode->GetModule()->m_vmDomainFile;
+ _ASSERTE(!vmDomainFile.IsNull());
+
+ // If this thread is stopped due to an exception, never allow SetIP
+ if (HasException())
+ {
+ return (CORDBG_E_SET_IP_NOT_ALLOWED_ON_EXCEPTION);
+ }
+
+ DebuggerIPCEvent event;
+ GetProcess()->InitIPCEvent(&event, DB_IPCE_SET_IP, true, GetAppDomain()->GetADToken());
+ event.SetIP.fCanSetIPOnly = fCanSetIPOnly;
+ event.SetIP.vmThreadToken = m_vmThreadToken;
+ event.SetIP.vmDomainFile = vmDomainFile;
+ event.SetIP.mdMethod = pNativeCode->GetMetadataToken();
+ event.SetIP.vmMethodDesc = pNativeCode->GetVMNativeCodeMethodDescToken();
+ event.SetIP.startAddress = pNativeCode->GetAddress();
+ event.SetIP.offset = offset;
+ event.SetIP.fIsIL = fIsIL;
+
+
+ LOG((LF_CORDB, LL_INFO10000, "[%x] CT::SIP: Info:thread:0x%x"
+ "mod:0x%x MethodDef:0x%x offset:0x%x il?:0x%x\n",
+ GetCurrentThreadId(),
+ VmPtrToCookie(m_vmThreadToken),
+ VmPtrToCookie(vmDomainFile),
+ pNativeCode->GetMetadataToken(),
+ offset,
+ fIsIL));
+
+ LOG((LF_CORDB, LL_INFO10000, "[%x] CT::SIP: sizeof(DebuggerIPCEvent):0x%x **********\n",
+ sizeof(DebuggerIPCEvent)));
+
+ HRESULT hr = GetProcess()->m_cordb->SendIPCEvent(GetProcess(), &event, sizeof(DebuggerIPCEvent));
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ _ASSERTE(event.type == DB_IPCE_SET_IP);
+
+ if (!fCanSetIPOnly && SUCCEEDED(event.hr))
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ CleanupStack();
+ }
+
+ return ErrWrapper(event.hr);
+}
+
+// Get the context from a thread in managed code.
+// This thread should be stopped gracefully by the LS in managed code.
+HRESULT CordbThread::GetManagedContext(DT_CONTEXT ** ppContext)
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ if (ppContext == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ *ppContext = NULL;
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // Each CordbThread object allocates the m_pContext's DT_CONTEXT structure only once, the first time GetContext is
+ // invoked.
+ if(m_pContext == NULL)
+ {
+ // Throw if the allocation fails.
+ m_pContext = reinterpret_cast<DT_CONTEXT *>(new BYTE[sizeof(DT_CONTEXT)]);
+ }
+
+ HRESULT hr = S_OK;
+
+ if (m_fContextFresh == false)
+ {
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ m_vmLeftSideContext = pDAC->GetManagedStoppedContext(m_vmThreadToken);
+
+ if (m_vmLeftSideContext.IsNull())
+ {
+ // We don't have a context in managed code.
+ ThrowHR(CORDBG_E_CONTEXT_UNVAILABLE);
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CT::GC: getting context from left side pointer.\n"));
+
+ // The thread we're examining IS handling an exception, So grab the CONTEXT of the exception, NOT the
+ // currently executing thread's CONTEXT (which would be the context of the exception handler.)
+ hr = GetProcess()->SafeReadThreadContext(m_vmLeftSideContext.ToLsPtr(), m_pContext);
+ IfFailThrow(hr);
+ }
+
+ // m_fContextFresh should be marked false when CleanupStack, MarkAllFramesAsDirty, etc get called.
+ m_fContextFresh = true;
+ }
+
+ _ASSERTE(SUCCEEDED(hr));
+ (*ppContext) = m_pContext;
+
+ return hr;
+}
+
+HRESULT CordbThread::SetManagedContext(DT_CONTEXT * pContext)
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ if(pContext == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ m_vmLeftSideContext = pDAC->GetManagedStoppedContext(m_vmThreadToken);
+
+ if (m_vmLeftSideContext.IsNull())
+ {
+ ThrowHR(CORDBG_E_CONTEXT_UNVAILABLE);
+ }
+ else
+ {
+ // The thread we're examining IS handling an exception, So set the CONTEXT of the exception, NOT the currently
+ // executing thread's CONTEXT (which would be the context of the exception handler.)
+ //
+ // Note: we read the remote context and merge the new one in, then write it back. This ensures that we don't
+ // write too much information into the remote process.
+ DT_CONTEXT tempContext = { 0 };
+ hr = GetProcess()->SafeReadThreadContext(m_vmLeftSideContext.ToLsPtr(), &tempContext);
+ IfFailThrow(hr);
+
+ CORDbgCopyThreadContext(&tempContext, pContext);
+
+ hr = GetProcess()->SafeWriteThreadContext(m_vmLeftSideContext.ToLsPtr(), &tempContext);
+ IfFailThrow(hr);
+
+ // @todo - who's updating the regdisplay to guarantee that's in sync w/ our new context?
+ }
+
+ _ASSERTE(SUCCEEDED(hr));
+ if (m_fContextFresh && (m_pContext != NULL))
+ {
+ *m_pContext = *pContext;
+ }
+
+ return hr;
+}
+
+
+HRESULT CordbThread::GetAppDomain(ICorDebugAppDomain ** ppAppDomain)
+{
+ // We don't use the cached m_pAppDomain pointer here because it might be incorrect
+ // if the thread has transitioned to another domain but we haven't received any events
+ // from it yet. So we need to ask the left-side for the current domain.
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ ValidateOrThrow(ppAppDomain);
+ *ppAppDomain = NULL;
+ hr = EnsureThreadIsAlive();
+
+ if (SUCCEEDED(hr))
+ {
+ CordbAppDomain * pAppDomain = NULL;
+ hr = GetCurrentAppDomain(&pAppDomain);
+ IfFailThrow(hr);
+ _ASSERTE( pAppDomain != NULL );
+
+ *ppAppDomain = static_cast<ICorDebugAppDomain *> (pAppDomain);
+ pAppDomain->ExternalAddRef();
+ }
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Issues a get appdomain command and returns it.
+//
+// Arguments:
+// ppAppDomain - OUT: Space for storing the app domain of this thread.
+//
+// Return Value:
+// S_OK on success.
+//
+HRESULT CordbThread::GetCurrentAppDomain(CordbAppDomain ** ppAppDomain)
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_API_ENTRY(GetProcess());
+
+ *ppAppDomain = NULL;
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // @dbgtodo ICDThread - push this up
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ hr = EnsureThreadIsAlive();
+
+ if (SUCCEEDED(hr))
+ {
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ VMPTR_AppDomain vmAppDomain = pDAC->GetCurrentAppDomain(m_vmThreadToken);
+
+ CordbAppDomain * pAppDomain = GetProcess()->LookupOrCreateAppDomain(vmAppDomain);
+ _ASSERTE(pAppDomain != NULL); // we should be aware of all AppDomains
+
+ *ppAppDomain = pAppDomain;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Issues a get_object command and returns the thread object as a value.
+//
+// Arguments:
+// ppThreadObject - OUT: Space for storing the thread object of this thread as a value
+//
+// Return Value:
+// S_OK on success.
+//
+HRESULT CordbThread::GetObject(ICorDebugValue ** ppThreadObject)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT(ppThreadObject, ICorDebugObjectValue **);
+
+ // Default to NULL
+ *ppThreadObject = NULL;
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // @dbgtodo ICDThread - push this up
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ hr = EnsureThreadIsAlive();
+
+ if (SUCCEEDED(hr))
+ {
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ VMPTR_OBJECTHANDLE vmObjHandle = pDAC->GetThreadObject(m_vmThreadToken);
+ if (vmObjHandle.IsNull())
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ // We create the object relative to the current AppDomain of the thread
+ // Thread objects aren't really agile (eg. their m_Context field is domain-bound and
+ // fixed up manually during transitions). This means that a thread object can only
+ // be used in the domain the thread was in when the object was created.
+ VMPTR_AppDomain vmAppDomain = pDAC->GetCurrentAppDomain(m_vmThreadToken);
+
+ CordbAppDomain * pThreadCurrentDomain = NULL;
+ pThreadCurrentDomain = GetProcess()->m_appDomains.GetBaseOrThrow(VmPtrToCookie(vmAppDomain));
+ _ASSERTE(pThreadCurrentDomain != NULL); // we should be aware of all AppDomains
+
+ if (pThreadCurrentDomain == NULL)
+ {
+ // fall back to some domain to avoid crashes in retail -
+ // safe enough for getting the name of the thread etc.
+ pThreadCurrentDomain = GetProcess()->GetDefaultAppDomain();
+ }
+
+ lockHolder.Release();
+
+ ICorDebugReferenceValue * pRefValue = NULL;
+ hr = CordbReferenceValue::BuildFromGCHandle(pThreadCurrentDomain, vmObjHandle, &pRefValue);
+ *ppThreadObject = pRefValue;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // Don't return a null pointer with S_OK.
+ _ASSERTE((hr != S_OK) || (*ppThreadObject != NULL));
+ return hr;
+}
+
+/*
+ *
+ * GetActiveFunctions
+ *
+ * This routine is the interface function for ICorDebugThread2::GetActiveFunctions.
+ *
+ * Parameters:
+ * cFunctions - the count of the number of COR_ACTIVE_FUNCTION in pFunctions. Zero
+ * indicates no pFunctions buffer.
+ * pcFunctions - pointer to storage for the count of elements filled in to pFunctions, or
+ * count that would be needed to fill pFunctions, if cFunctions is 0.
+ * pFunctions - buffer to store results. May be NULL.
+ *
+ * Return Value:
+ * HRESULT from the helper routine.
+ *
+ */
+
+HRESULT CordbThread::GetActiveFunctions(
+ ULONG32 cFunctions,
+ ULONG32 * pcFunctions,
+ COR_ACTIVE_FUNCTION pFunctions[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ ULONG32 index;
+ ULONG32 iRealIndex;
+ ULONG32 last;
+
+ if (((cFunctions != 0) && (pFunctions == NULL)) || (pcFunctions == NULL))
+ {
+ return E_INVALIDARG;
+ }
+
+ //
+ // Default to 0
+ //
+ *pcFunctions = 0;
+
+ // @dbgtodo synchronization - The ATT macro may slip the thread to a sychronized state. The
+ // synchronization feature crew needs to figure out what to do here. Then we can use the
+ // PUBLIC_API_BEGIN macro in this function.
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ if (IsThreadDead())
+ {
+ //
+ // Return zero active functions on this thread.
+ //
+ hr = S_OK;
+ }
+ else
+ {
+ ULONG32 cAllFrames = 0; // the total number of frames (stack frames and internal frames)
+ ULONG32 cStackFrames = 0; // the number of stack frames
+ ShimStackWalk * pSSW = NULL;
+
+ if (GetProcess()->GetShim() != NULL)
+ {
+ PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(GetProcess());
+ pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(this);
+
+ // initialize the frame counts
+ cAllFrames = pSSW->GetFrameCount();
+ for (ULONG32 i = 0; i < cAllFrames; i++)
+ {
+ // filter out internal frames
+ if (CordbFrame::GetCordbFrameFromInterface(pSSW->GetFrame(i))->GetAsNativeFrame() != NULL)
+ {
+ cStackFrames += 1;
+ }
+ }
+
+ _ASSERTE(cStackFrames <= cAllFrames);
+ }
+ else
+ {
+ RefreshStack();
+
+ cAllFrames = m_stackFrames.Count();
+ cStackFrames = cAllFrames;
+
+ // In Arrowhead, the stackwalking API doesn't return internal frames,
+ // so the frame counts should be equal.
+ _ASSERTE(cStackFrames == cAllFrames);
+ }
+
+ *pcFunctions = cStackFrames;
+
+ //
+ // If all we want is the count, then return that.
+ //
+ if ((pFunctions == NULL) || (cFunctions == 0))
+ {
+ hr = S_OK;
+ }
+ else
+ {
+ //
+ // Now go down list of frames, storing information
+ //
+ last = (cFunctions < cStackFrames) ? cFunctions : cStackFrames;
+ iRealIndex = 0;
+ index =0;
+
+ while((index < last) && (iRealIndex < cAllFrames))
+ {
+ CordbFrame * pThisFrame = NULL;
+ if (GetProcess()->GetShim())
+ {
+ _ASSERTE(pSSW != NULL);
+ pThisFrame = CordbFrame::GetCordbFrameFromInterface(pSSW->GetFrame(iRealIndex));
+ }
+ else
+ {
+ pThisFrame = *(m_stackFrames.Get(iRealIndex));
+ _ASSERTE(pThisFrame->GetAsNativeFrame() != NULL);
+ }
+
+ iRealIndex++;
+
+ CordbNativeFrame * pNativeFrame = pThisFrame->GetAsNativeFrame();
+ if (pNativeFrame == NULL)
+ {
+ // filter out internal frames
+ _ASSERTE(pThisFrame->GetAsInternalFrame() != NULL);
+ continue;
+ }
+
+ //
+ // Fill in the easy stuff.
+ //
+ CordbFunction * pFunction;
+
+ pFunction = (static_cast<CordbFrame *>(pNativeFrame))->GetFunction();
+ ASSERT(pFunction != NULL);
+
+ hr = pFunction->QueryInterface(IID_ICorDebugFunction2,
+ reinterpret_cast<void **>(&(pFunctions[index].pFunction)));
+ ASSERT(!FAILED(hr));
+
+ CordbModule * pModule = pFunction->GetModule();
+ pFunctions[index].pModule = pModule;
+ pModule->ExternalAddRef();
+
+ CordbAppDomain * pAppDomain = pNativeFrame->GetCurrentAppDomain();
+ pFunctions[index].pAppDomain = pAppDomain;
+ pAppDomain->ExternalAddRef();
+
+ pFunctions[index].flags = 0;
+
+ //
+ // Now go to the IL frame (if one exists) to the get the offset.
+ //
+ CordbJITILFrame * pJITILFrame;
+
+ pJITILFrame = pNativeFrame->m_JITILFrame;
+
+ if (pJITILFrame != NULL)
+ {
+ hr = pJITILFrame->GetIP(&(pFunctions[index].ilOffset), NULL);
+ ASSERT(!FAILED(hr));
+ }
+ else
+ {
+ pFunctions[index].ilOffset = (DWORD) NO_MAPPING;
+ }
+
+ // Update to the next count.
+ index++;
+ }
+
+ // @todo - The spec says that pcFunctions == # of elements in pFunctions,
+ // but the behavior here is that it's always the total.
+ // If we want to fix that, we should uncomment the assignment here:
+ //*pcFunctions = index;
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// This is the entry point for continuable exceptions.
+// It implements ICorDebugThread2::InterceptCurrentException.
+//
+// Arguments:
+// pFrame - the stack frame to intercept at
+//
+// Return Value:
+// HRESULT indicating success or failure
+//
+// Notes:
+// Since we cannot intercept an exception at an internal frame,
+// pFrame should not be an ICorDebugInternalFrame.
+//
+
+HRESULT CordbThread::InterceptCurrentException(ICorDebugFrame * pFrame)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ // Continuable exceptions are not implemented on rotor and Mac.
+ return E_NOTIMPL;
+
+#else // !FEATURE_DBGIPC_TRANSPORT_DI
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ DebuggerIPCEvent event;
+
+ if (pFrame == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ //
+ // Verify we were passed a real stack frame, and not an internal
+ // CLR mocked up one.
+ //
+ {
+ RSExtSmartPtr<ICorDebugInternalFrame> pInternalFrame;
+ hr = pFrame->QueryInterface(IID_ICorDebugInternalFrame, (void **)&pInternalFrame);
+
+ if (!FAILED(hr))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+ }
+
+
+ //
+ // If the thread is detached, then there should be no frames on its stack.
+ //
+ hr = EnsureThreadIsAlive();
+
+ if (SUCCEEDED(hr))
+ {
+ //
+ // Refresh the stack frames for this thread and verify pFrame is on it.
+ //
+
+ RefreshStack();
+
+ //
+ // Now check if the frame actually lives on the stack of the current thread.
+ //
+
+ // "Cast" the ICDFrame pointer to a CordbFrame pointer.
+ CordbFrame * pRealFrame = CordbFrame::GetCordbFrameFromInterface(pFrame);
+ if (!OwnsFrame(pRealFrame))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ //
+ // pFrame is on the stack - good. Now tell the LS to intercept at that frame.
+ //
+
+ GetProcess()->InitIPCEvent(&event, DB_IPCE_INTERCEPT_EXCEPTION, true, VMPTR_AppDomain::NullPtr());
+
+ event.InterceptException.vmThreadToken = m_vmThreadToken;
+ event.InterceptException.frameToken = pRealFrame->GetFramePointer();
+
+ hr = GetProcess()->m_cordb->SendIPCEvent(GetProcess(), &event, sizeof(DebuggerIPCEvent));
+
+ //
+ // Stop now if we can't even send the event.
+ //
+ if (!SUCCEEDED(hr))
+ {
+ ThrowHR(hr);
+ }
+
+ _ASSERTE(event.type == DB_IPCE_INTERCEPT_EXCEPTION_RESULT);
+
+ hr = event.hr;
+ // Since we are going to exit anyway, we don't need to throw here.
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Return S_OK if there is a current exception and it is unhandled, otherwise
+// return S_FALSE
+//
+HRESULT CordbThread::HasUnhandledException()
+{
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_FALSE;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ if(pDAC->HasUnhandledException(m_vmThreadToken))
+ {
+ hr = S_OK;
+ }
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Create a stackwalker on the current thread. Initially, the stackwalker is stopped at the
+// managed filter CONTEXT if there is one. Otherwise it is stopped at the leaf CONTEXT.
+//
+// Arguments:
+// ppStackWalk - out parameter; return the new stackwalker
+//
+// Return Value:
+// Return S_OK on succcess.
+// Return E_FAIL on error.
+//
+// Notes:
+// The filter CONTEXT will be removed in V3.0.
+//
+
+HRESULT CordbThread::CreateStackWalk(ICorDebugStackWalk ** ppStackWalk)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT(ppStackWalk, ICorDebugStackWalk **);
+
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ hr = EnsureThreadIsAlive();
+
+ if (SUCCEEDED(hr))
+ {
+ RSInitHolder<CordbStackWalk> pSW(new CordbStackWalk(this));
+ pSW->Init();
+ pSW.TransferOwnershipExternal(ppStackWalk);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// This is a callback function used to enumerate the internal frames on a thread.
+// Each time this callback is invoked, we'll create a new CordbInternalFrame and store it
+// in an array. See code:DacDbiInterfaceImpl::EnumerateInternalFrames for more information.
+//
+// Arguments:
+// pFrameData - contains information about the current internal frame in the enumeration
+// pUserData - This is a GetActiveInternalFramesData.
+// It contains an array of internl frames to be filled.
+//
+
+// static
+void CordbThread::GetActiveInternalFramesCallback(const DebuggerIPCE_STRData * pFrameData,
+ void * pUserData)
+{
+ // Retrieve the CordbThread.
+ GetActiveInternalFramesData * pCallbackData = reinterpret_cast<GetActiveInternalFramesData *>(pUserData);
+ CordbThread * pThis = pCallbackData->pThis;
+ INTERNAL_DAC_CALLBACK(pThis->GetProcess());
+
+ // Make sure we are getting invoked for internal frames.
+ _ASSERTE(pFrameData->eType == DebuggerIPCE_STRData::cStubFrame);
+
+ // Look up the CordbAppDomain.
+ CordbAppDomain * pAppDomain = NULL;
+ VMPTR_AppDomain vmCurrentAppDomain = pFrameData->vmCurrentAppDomainToken;
+ if (!vmCurrentAppDomain.IsNull())
+ {
+ pAppDomain = pThis->GetProcess()->LookupOrCreateAppDomain(vmCurrentAppDomain);
+ }
+
+ // Create a CordbInternalFrame.
+ CordbInternalFrame * pInternalFrame = new CordbInternalFrame(pThis,
+ pFrameData->fp,
+ pAppDomain,
+ pFrameData);
+
+ // Store the internal frame in the array and update the index to prepare for the next one.
+ pCallbackData->pInternalFrames.Assign(pCallbackData->uIndex, pInternalFrame);
+ pCallbackData->uIndex++;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This function returns an array of ICDInternalFrame2. Each element represents an internal frame
+// on the thread. If ppInternalFrames is NULL or cInternalFrames is 0, then we just return
+// the number of internal frames on the thread.
+//
+// Arguments:
+// cInternalFrames - the number of elements in ppInternalFrames
+// pcInternalFrames - out parameter; return the number of internal frames on the thread
+// ppInternalFrames - a buffer to store the array of internal frames
+//
+// Return Value:
+// S_OK on success.
+// E_INVALIDARG if
+// - ppInternalFrames is NULL but cInternalFrames is not 0
+// - pcInternalFrames is NULL
+// - cInternalFrames is smaller than the number of internal frames actually on the thread
+//
+
+HRESULT CordbThread::GetActiveInternalFrames(ULONG32 cInternalFrames,
+ ULONG32 * pcInternalFrames,
+ ICorDebugInternalFrame2 * ppInternalFrames[])
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this);
+ {
+ if ( ((cInternalFrames != 0) && (ppInternalFrames == NULL)) ||
+ (pcInternalFrames == NULL) )
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ *pcInternalFrames = 0;
+
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ ULONG32 cActiveInternalFrames = pDAC->GetCountOfInternalFrames(m_vmThreadToken);
+
+ // Set the count.
+ *pcInternalFrames = cActiveInternalFrames;
+
+ // Don't need to do anything else if the user is only asking for the count.
+ if ((cInternalFrames != 0) && (ppInternalFrames != NULL))
+ {
+ if (cInternalFrames < cActiveInternalFrames)
+ {
+ ThrowWin32(ERROR_INSUFFICIENT_BUFFER);
+ }
+ else
+ {
+ // initialize the callback data
+ GetActiveInternalFramesData data;
+ data.pThis = this;
+ data.uIndex = 0;
+ data.pInternalFrames.AllocOrThrow(cActiveInternalFrames);
+ // We want to ensure it's automatically cleaned up in all cases
+ // e.g. if we're debugging a MiniDumpNormal and we fail to
+ // retrieve memory from the target. The exception will be
+ // caught above this frame.
+ data.pInternalFrames.EnableAutoClear();
+
+ pDAC->EnumerateInternalFrames(m_vmThreadToken,
+ &CordbThread::GetActiveInternalFramesCallback,
+ &data);
+ _ASSERTE(cActiveInternalFrames == data.pInternalFrames.Length());
+
+ // Copy the internal frames we have accumulated in GetActiveInternalFramesData to the out
+ // argument.
+ for (unsigned int i = 0; i < data.pInternalFrames.Length(); i++)
+ {
+ RSInitHolder<CordbInternalFrame> pInternalFrame(data.pInternalFrames[i]);
+ pInternalFrame.TransferOwnershipExternal(&(ppInternalFrames[i]));
+ }
+ }
+ }
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+
+// ICorDebugThread4
+
+// -------------------------------------------------------------------------------
+// Gets the current custom notification on this thread or NULL if no such object exists
+// Arguments:
+// output: ppNotificationObject - current CustomNotification object.
+// if we aren't currently inside a CustomNotification callback, this will
+// always return NULL.
+// return value:
+// S_OK on success
+// S_FALSE if no object exists
+// CORDBG_E_BAD_REFERENCE_VALUE if the reference is bad
+HRESULT CordbThread::GetCurrentCustomDebuggerNotification(ICorDebugValue ** ppNotificationObject)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_NO_LOCK_BEGIN(this);
+ {
+ ATT_REQUIRE_STOPPED_MAY_FAIL_OR_THROW(GetProcess(), ThrowHR);
+
+ if (ppNotificationObject == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ *ppNotificationObject = NULL;
+
+ //
+ // Go to the LS and retrieve any notification object.
+ //
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ VMPTR_OBJECTHANDLE vmObjHandle = pDAC->GetCurrentCustomDebuggerNotification(m_vmThreadToken);
+
+#if defined(_DEBUG)
+ // Since we know a notification has occurred on this thread, our assumption about the
+ // thread's current AppDomain should be correct
+ VMPTR_AppDomain vmAppDomain = pDAC->GetCurrentAppDomain(m_vmThreadToken);
+
+ _ASSERTE(GetAppDomain()->GetADToken() == vmAppDomain);
+#endif // _DEBUG
+
+ if (!vmObjHandle.IsNull())
+ {
+ ICorDebugReferenceValue * pRefValue = NULL;
+ IfFailThrow(CordbReferenceValue::BuildFromGCHandle(GetAppDomain(), vmObjHandle, &pRefValue));
+ *ppNotificationObject = pRefValue;
+ }
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+/*
+ *
+ * SetRemapIP
+ *
+ * This routine communicate the EnC remap IP to the LS by writing it to process memory using
+ * the pointer that was set in the thread. If the address is null, then we haven't seen
+ * a RemapOpportunity call for this frame/function combo yet, so invalid to Remap the function.
+ *
+ * Parameters:
+ * offset - the IL offset to set the IP to
+ *
+ * Return Value:
+ * S_OK or CORDBG_E_NO_REMAP_BREAKPIONT.
+ *
+ */
+HRESULT CordbThread::SetRemapIP(SIZE_T offset)
+{
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ // This is only set when we're prepared to do a remap
+ if (! m_EnCRemapFunctionIP)
+ {
+ return CORDBG_E_NO_REMAP_BREAKPIONT;
+ }
+
+ // Write the value of the remap offset into the left side
+ HRESULT hr = GetProcess()->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(m_EnCRemapFunctionIP), &offset);
+
+ // Prevent SetRemapIP from being called twice for the same RemapOpportunity
+ // If we don't get any calls to RemapFunction, this member will be cleared in
+ // code:CordbThread::MarkStackFramesDirty when Continue is called
+ m_EnCRemapFunctionIP = NULL;
+
+ return hr;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// This routine is the interface function for ICorDebugThread2::GetConnectionID.
+//
+// Arguments:
+// pdwConnectionId - return connection id set on the thread. Can return INVALID_CONNECTION_ID
+//
+// Return Value:
+// HRESULT indicating success or failure
+//
+HRESULT CordbThread::GetConnectionID(CONNID * pConnectionID)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // now retrieve the connection id
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (pConnectionID == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ *pConnectionID = pDAC->GetConnectionID(m_vmThreadToken);
+
+ if (*pConnectionID == INVALID_CONNECTION_ID)
+ {
+ hr = S_FALSE;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+} // CordbThread::GetConnectionID
+
+//---------------------------------------------------------------------------------------
+//
+// This routine is the interface function for ICorDebugThread2::GetTaskID.
+//
+// Arguments:
+// pTaskId - return task id set on the thread. Can return INVALID_TASK_ID
+//
+// Return Value:
+// HRESULT indicating success or failure
+//
+HRESULT CordbThread::GetTaskID(TASKID * pTaskID)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // now retrieve the task id
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (pTaskID == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ *pTaskID = this->GetTaskID();
+
+ if (*pTaskID == INVALID_TASK_ID)
+ {
+ hr = S_FALSE;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+} // CordbThread::GetTaskID
+
+//---------------------------------------------------------------------------------------
+// Get the task ID for this thread
+//
+// return:
+// task id set on the thread. Can return INVALID_TASK_ID
+//
+TASKID CordbThread::GetTaskID()
+{
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ return pDAC->GetTaskID(m_vmThreadToken);
+}
+
+
+
+//---------------------------------------------------------------------------------------
+//
+// This routine is the interface function for ICorDebugThread2::GetVolatileOSThreadID.
+//
+// Arguments:
+// pdwTid - return os thread id
+//
+// Return Value:
+// HRESULT indicating success or failure
+//
+// Notes:
+// Compare with code:CordbThread::GetID
+HRESULT CordbThread::GetVolatileOSThreadID(DWORD * pdwTID)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // now retrieve the OS thread ID
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (pdwTID == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ *pdwTID = pDAC->TryGetVolatileOSThreadID(m_vmThreadToken);
+
+ if (*pdwTID == 0)
+ {
+ hr = S_FALSE; // Switched out
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+} // CordbThread::GetOSThreadID
+
+//---------------------------------------------------------------------------------------
+// Get the thread's volatile OS ID. (this is fiber aware)
+//
+// Returns:
+// Thread's current OS id. For fibers / "logical threads", This may change as a thread executes.
+// Throws if the managed thread currently is not mapped to an OS thread (ie, not scheduled)
+//
+DWORD CordbThread::GetVolatileOSThreadID()
+{
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ DWORD dwThreadID = pDAC->TryGetVolatileOSThreadID(m_vmThreadToken);
+
+ if (dwThreadID == 0)
+ {
+ ThrowHR(CORDBG_E_THREAD_NOT_SCHEDULED);
+ }
+ return dwThreadID;
+}
+
+// ----------------------------------------------------------------------------
+// CordbThread::ClearStackFrameCache
+//
+// Description:
+// Clear the cache of stack frames maintained by the CordbThread.
+//
+// Notes:
+// We are doing an InternalRelease() here to match the InternalAddRef() in code:CordbThread::RefreshStack.
+//
+
+void CordbThread::ClearStackFrameCache()
+{
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ for (int i = 0; i < m_stackFrames.Count(); i++)
+ {
+ (*m_stackFrames.Get(i))->Neuter();
+ (*m_stackFrames.Get(i))->InternalRelease();
+ }
+ m_stackFrames.Clear();
+}
+
+// ----------------------------------------------------------------------------
+// EnumerateBlockingObjectsCallback
+//
+// Description:
+// A small helper used by CordbThread::GetBlockingObjects. This callback adds the enumerated items
+// to a list
+//
+// Arguments:
+// blockingObject - the object to add to the list
+// pUserData - the list to add it to
+
+VOID EnumerateBlockingObjectsCallback(DacBlockingObject blockingObject, CALLBACK_DATA pUserData)
+{
+ CQuickArrayList<DacBlockingObject>* pDacBlockingObjs = (CQuickArrayList<DacBlockingObject>*)pUserData;
+ pDacBlockingObjs->Push(blockingObject);
+}
+
+// ----------------------------------------------------------------------------
+// CordbThread::GetBlockingObjects
+//
+// Description:
+// Returns a list of objects that a thread is blocking on by using Monitor.Enter and
+// Monitor.Wait
+//
+// Arguments:
+// ppBlockingObjectEnum - on return this is an enumerator for the list of blocking objects
+//
+// Return:
+// S_OK on success or an appropriate failing HRESULT
+
+HRESULT CordbThread::GetBlockingObjects(ICorDebugBlockingObjectEnum **ppBlockingObjectEnum)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ VALIDATE_POINTER_TO_OBJECT(ppBlockingObjectEnum, ICorDebugBlockingObjectEnum **);
+
+ HRESULT hr = S_OK;
+ CorDebugBlockingObject* blockingObjs = NULL;
+ EX_TRY
+ {
+ CQuickArrayList<DacBlockingObject> dacBlockingObjects;
+ IDacDbiInterface* pDac = GetProcess()->GetDAC();
+ pDac->EnumerateBlockingObjects(m_vmThreadToken,
+ (IDacDbiInterface::FP_BLOCKINGOBJECT_ENUMERATION_CALLBACK) EnumerateBlockingObjectsCallback,
+ (CALLBACK_DATA) &dacBlockingObjects);
+ blockingObjs = new CorDebugBlockingObject[dacBlockingObjects.Size()];
+ for(SIZE_T i = 0 ; i < dacBlockingObjects.Size(); i++)
+ {
+ // ICorDebug API needs to flip the direction of the list from the way DAC stores it
+ SIZE_T dacObjIndex = dacBlockingObjects.Size()-i-1;
+ switch(dacBlockingObjects[dacObjIndex].blockingReason)
+ {
+ case DacBlockReason_MonitorCriticalSection:
+ blockingObjs[i].blockingReason = BLOCKING_MONITOR_CRITICAL_SECTION;
+ break;
+ case DacBlockReason_MonitorEvent:
+ blockingObjs[i].blockingReason = BLOCKING_MONITOR_EVENT;
+ break;
+ default:
+ _ASSERTE(!"Should not get here");
+ ThrowHR(E_FAIL);
+ break;
+ }
+ blockingObjs[i].dwTimeout = dacBlockingObjects[dacObjIndex].dwTimeout;
+ CordbAppDomain* pAppDomain;
+ {
+ RSLockHolder holder(GetProcess()->GetProcessLock());
+ pAppDomain = GetProcess()->LookupOrCreateAppDomain(dacBlockingObjects[dacObjIndex].vmAppDomain);
+ }
+ blockingObjs[i].pBlockingObject = CordbValue::CreateHeapValue(pAppDomain,
+ dacBlockingObjects[dacObjIndex].vmBlockingObject);
+ }
+
+ CordbBlockingObjectEnumerator* objEnum = new CordbBlockingObjectEnumerator(GetProcess(),
+ blockingObjs,
+ (DWORD)dacBlockingObjects.Size());
+ GetProcess()->GetContinueNeuterList()->Add(GetProcess(), objEnum);
+ hr = objEnum->QueryInterface(__uuidof(ICorDebugBlockingObjectEnum), (void**)ppBlockingObjectEnum);
+ _ASSERTE(SUCCEEDED(hr));
+ }
+ EX_CATCH_HRESULT(hr);
+ delete [] blockingObjs;
+ return hr;
+}
+
+// ----------------------------------------------------------------------------
+// CordbThread::SetCreateEventQueued
+void CordbThread::SetCreateEventQueued()
+{
+ m_fCreationEventQueued = true;
+}
+
+// ----------------------------------------------------------------------------
+// CordbThread::CreateEventWasQueued
+bool CordbThread::CreateEventWasQueued()
+{
+ return m_fCreationEventQueued;
+}
+
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+/* ------------------------------------------------------------------------- *
+ * Unmanaged Thread classes
+ * ------------------------------------------------------------------------- */
+
+CordbUnmanagedThread::CordbUnmanagedThread(CordbProcess *pProcess, DWORD dwThreadId, HANDLE hThread, void *lpThreadLocalBase)
+ : CordbBase(pProcess, dwThreadId, enumCordbUnmanagedThread),
+ m_handle(hThread),
+ m_threadLocalBase(lpThreadLocalBase),
+ m_pTLSArray(NULL),
+ m_pTLSExtendedArray(NULL),
+ m_state(CUTS_None),
+ m_originalHandler(NULL),
+#ifdef DBG_TARGET_X86
+ m_pSavedLeafSeh(NULL),
+#endif
+ m_stackBase(0),
+ m_stackLimit(0),
+ m_continueCountCached(0)
+{
+ m_pLeftSideContext.Set(NULL);
+
+ IBEvent()->m_state = CUES_None;
+ IBEvent()->m_next = NULL;
+ IBEvent()->m_owner = this;
+
+ IBEvent2()->m_state = CUES_None;
+ IBEvent2()->m_next = NULL;
+ IBEvent2()->m_owner = this;
+
+ OOBEvent()->m_state = CUES_None;
+ OOBEvent()->m_next = NULL;
+ OOBEvent()->m_owner = this;
+
+ m_pPatchSkipAddress = NULL;
+
+ this->GetStackRange(NULL, NULL);
+}
+
+CordbUnmanagedThread::~CordbUnmanagedThread()
+{
+ // CordbUnmanagedThread objects will:
+ // - never send IPC events.
+ // - never be exposed to the public. (we assert external-ref is always == 0)
+ // - always manipulated on W32ET (where we can't do IPC stuff)
+
+ UnsafeNeuterDeadObject();
+
+ _ASSERTE(this->IsNeutered());
+
+ // by the time the thread is deleted, it shouldn't have any outstanding debug events.
+
+ // Actually, the thread could get deleted while we have an outstanding IB debug event. We could get the IB event, hijack that thread,
+ // and then since the process is continued, something could go off and kill the hijacked thread.
+ // If the event is still in the process's queued list, and it still refers back to a thread, then we'll AV when we try to access the event
+ // (or continue it).
+ CONSISTENCY_CHECK_MSGF(!HasIBEvent(), ("Deleting thread w/ outstanding IB event:this=%p,event-code=%d\n", this, IBEvent()->m_currentDebugEvent.dwDebugEventCode));
+
+ CONSISTENCY_CHECK_MSGF(!HasOOBEvent(), ("Deleting thread w/ outstanding OOB event:this=%p,event-code=%d\n", this, OOBEvent()->m_currentDebugEvent.dwDebugEventCode));
+}
+
+#define WINNT_TLS_OFFSET_X86 0xe10 // TLS[0] at fs:[WINNT_TLS_OFFSET]
+#define WINNT_TLS_OFFSET_AMD64 0x1480
+#define WINNT_TLS_OFFSET_ARM 0xe10
+#define WINNT5_TLSEXPANSIONPTR_OFFSET_X86 0xf94 // TLS[64] at [fs:[WINNT5_TLSEXPANSIONPTR_OFFSET]]
+#define WINNT5_TLSEXPANSIONPTR_OFFSET_AMD64 0x1780
+#define WINNT5_TLSEXPANSIONPTR_OFFSET_ARM 0xf94
+
+HRESULT CordbUnmanagedThread::LoadTLSArrayPtr(void)
+{
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+
+ // Just simple math on NT with a small tls index.
+ // The TLS slots for 0-63 are embedded in the TIB.
+#if defined(DBG_TARGET_X86)
+ m_pTLSArray = (BYTE*) m_threadLocalBase + WINNT_TLS_OFFSET_X86;
+#elif defined(DBG_TARGET_AMD64)
+ m_pTLSArray = (BYTE*) m_threadLocalBase + WINNT_TLS_OFFSET_AMD64;
+#elif defined(DBG_TARGET_ARM)
+ m_pTLSArray = (BYTE*) m_threadLocalBase + WINNT_TLS_OFFSET_ARM;
+#elif defined(DBG_TARGET_ARM64)
+ m_pTLSArray = (BYTE*) m_threadLocalBase + WINNT_TLS_OFFSET_ARM64;
+#else
+ PORTABILITY_ASSERT("Implement OOP TLS on your platform");
+#endif
+
+ // Extended slot is lazily initialized, so check every time.
+ if (m_pTLSExtendedArray == NULL)
+ {
+ // On NT 5 you can have TLS index's greater than 63, so we
+ // have to grab the ptr to the TLS expansion array first,
+ // then use that as the base to index off of. This will
+ // never move once we find it for a given thread, so we
+ // cache it here so we don't always have to perform two
+ // ReadProcessMemory's.
+#if defined(DBG_TARGET_X86)
+ void *ppTLSArray = (BYTE*) m_threadLocalBase + WINNT5_TLSEXPANSIONPTR_OFFSET_X86;
+#elif defined(DBG_TARGET_AMD64)
+ void *ppTLSArray = (BYTE*) m_threadLocalBase + WINNT5_TLSEXPANSIONPTR_OFFSET_AMD64;
+#elif defined(DBG_TARGET_ARM)
+ void *ppTLSArray = (BYTE*) m_threadLocalBase + WINNT5_TLSEXPANSIONPTR_OFFSET_ARM;
+#elif defined(DBG_TARGET_ARM64)
+ void *ppTLSArray = (BYTE*) m_threadLocalBase + WINNT5_TLSEXPANSIONPTR_OFFSET_ARM64;
+#else
+ PORTABILITY_ASSERT("Implement OOP TLS on your platform");
+#endif
+
+ hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(ppTLSArray), &m_pTLSExtendedArray);
+ }
+
+
+ return hr;
+}
+
+/*
+VOID CordbUnmanagedThread::VerifyFSChain()
+{
+#if defined(DBG_TARGET_X86)
+ DT_CONTEXT temp;
+ temp.ContextFlags = DT_CONTEXT_FULL;
+ DbiGetThreadContext(m_handle, &temp);
+ LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: 0x%x fs=0x%x TIB=0x%x\n",
+ m_id, temp.SegFs, m_threadLocalBase));
+ REMOTE_PTR pExceptionRegRecordPtr;
+ HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(m_threadLocalBase), &pExceptionRegRecordPtr);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: ERROR 0x%x failed to read fs:0 value: computed addr=0x%p err=%x\n",
+ m_id, m_threadLocalBase, hr));
+ _ASSERTE(FALSE);
+ return;
+ }
+ while(pExceptionRegRecordPtr != EXCEPTION_CHAIN_END && pExceptionRegRecordPtr != NULL)
+ {
+ REMOTE_PTR prev;
+ REMOTE_PTR handler;
+ hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pExceptionRegRecordPtr), &prev);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: ERROR 0x%x failed to read prev value: computed addr=0x%p err=%x\n",
+ m_id, pExceptionRegRecordPtr, hr));
+ return;
+ }
+ hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS( (VOID*)((DWORD)pExceptionRegRecordPtr+4) ), &handler);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: ERROR 0x%x failed to read handler value: computed addr=0x%p err=%x\n",
+ m_id, (DWORD)pExceptionRegRecordPtr+4, hr));
+ return;
+ }
+ LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: OK 0x%x record=0x%x prev=0x%x handler=0x%x\n",
+ m_id, pExceptionRegRecordPtr, prev, handler));
+ if(handler == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: ERROR 0x%x NULL handler found\n", m_id));
+ _ASSERTE(FALSE);
+ return;
+ }
+ if(prev == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: ERROR 0x%x NULL prev found\n", m_id));
+ _ASSERTE(FALSE);
+ return;
+ }
+ if(prev == pExceptionRegRecordPtr)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: ERROR 0x%x cyclic prev found\n", m_id));
+ _ASSERTE(FALSE);
+ return;
+ }
+ pExceptionRegRecordPtr = prev;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: OK 0x%x\n", m_id));
+#endif
+ return;
+}*/
+
+#ifdef DBG_TARGET_X86
+HRESULT CordbUnmanagedThread::SaveCurrentLeafSeh()
+{
+ _ASSERTE(m_pSavedLeafSeh == NULL);
+ REMOTE_PTR pExceptionRegRecordPtr;
+ HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(m_threadLocalBase), &pExceptionRegRecordPtr);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::SCLS: failed to read fs:0 value: computed addr=0x%p err=%x\n", m_threadLocalBase, hr));
+ return hr;
+ }
+ m_pSavedLeafSeh = pExceptionRegRecordPtr;
+ return S_OK;
+}
+
+HRESULT CordbUnmanagedThread::RestoreLeafSeh()
+{
+ _ASSERTE(m_pSavedLeafSeh != NULL);
+ HRESULT hr = GetProcess()->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(m_threadLocalBase), &m_pSavedLeafSeh);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::RLS: failed to write fs:0 value: computed addr=0x%p err=%x\n", m_threadLocalBase, hr));
+ return hr;
+ }
+ m_pSavedLeafSeh = NULL;
+ return S_OK;
+}
+#endif
+
+// Read the contents from the LS's Predefined TLS block.
+// This is an auxillary TLS storage array-of-void*, indexed off the TLS.
+// pRead is optional. This makes sense when '0' is a valid default value.
+// 1) On success (block exists in LS, we can read it),
+// return value of data in the slot, *pRead = true
+// 2) On failure to read block (block doens't exist yet, any other failure)
+// return value == 0 (assumed default, *pRead = false
+REMOTE_PTR CordbUnmanagedThread::GetPreDefTlsSlot(SIZE_T slot, bool * pRead)
+{
+#ifdef FEATURE_IMPLICIT_TLS
+ REMOTE_PTR pBlock = (REMOTE_PTR) GetEETlsDataBlock();
+#else
+ DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets);
+ REMOTE_PTR pBlock = (REMOTE_PTR) GetTlsSlot(pRO->m_TLSIndexOfPredefs);
+#endif
+
+ REMOTE_PTR data = 0;
+
+ // We don't have a maximum size, but we know it's less than ~200. This assert
+ // will catch if we're just passsing Garbage.
+ _ASSERTE(slot < 200);
+
+ bool dummy;
+ if (pRead == NULL)
+ {
+ pRead = &dummy;
+ }
+
+ if (pBlock != NULL)
+ {
+ REMOTE_PTR p = ((BYTE*) pBlock) + slot * sizeof(data);
+
+ // Now read the "special" status out of the PreDef block.
+ HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(p), &data);
+
+ // The predef block should be valid at this point, so the ReadProcessMemory ought to work.
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ if (SUCCEEDED(hr))
+ {
+ *pRead = true;
+ return data;
+ }
+ }
+
+ *pRead = false;
+ return 0;
+}
+
+#ifndef FEATURE_IMPLICIT_TLS
+
+// Read the contents from a LS threads's TLS slot.
+DWORD_PTR CordbUnmanagedThread::GetTlsSlot(SIZE_T slot)
+{
+ DWORD_PTR ret = 0;
+
+ // Compute the address of the necessary TLS value.
+ if (FAILED(LoadTLSArrayPtr()))
+ {
+ return NULL;
+ }
+
+
+ void * pBase = NULL;
+ SIZE_T slotAdjusted = slot;
+
+ if (slot < TLS_MINIMUM_AVAILABLE)
+ {
+ pBase = m_pTLSArray;
+ }
+ else if (slot < TLS_MINIMUM_AVAILABLE + TLS_EXPANSION_SLOTS)
+ {
+ pBase = m_pTLSExtendedArray;
+ slotAdjusted -= TLS_MINIMUM_AVAILABLE;
+
+ // Expansion slot is lazily allocated. If we're trying to read from it, but hasn't been allocated,
+ // then the TLS slot is still the default value, which is 0 (NULL).
+ if (pBase == NULL)
+ {
+ return NULL;
+ }
+ }
+ else
+ {
+ // Slot is out of range. Shouldn't happen unless debuggee is corrupted.
+ _ASSERTE(!"Invalid TLS slot");
+ return NULL;
+ }
+
+ void *pEEThreadTLS = (BYTE*) pBase + (slotAdjusted * sizeof(void*));
+
+
+ // Read the thread's TLS value.
+ HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pEEThreadTLS), &ret);
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::GEETV: failed to read TLS value: computed addr=0x%p index=%d, err=%x\n",
+ pEEThreadTLS, slot, hr));
+
+ return NULL;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000000, "CUT::GEETV: EE Thread TLS value is 0x%p for thread 0x%x, slot 0x%x\n", ret, m_id, slot));
+
+ return ret;
+}
+
+// This does a WriteProcessMemory to write to the debuggee's TLS slot allotted to EEThread
+//
+// Arguments:
+// EETlsValue - the value to write to the remote TLS slot.
+//
+// Notes:
+// The TLS slot is m_TLSIndex.
+//
+// This is very brittle because the OS can lazily allocates storage for TLS slots.
+// In order to gaurantee the storage is available, it must have been written to by the debuggee.
+// For managed threads, that's easy because the Thread* is already written to the slot.
+// But for pure native threads where GetThread() == NULL, the storage may not yet be allocated.
+//
+// The saving grace is that the debuggee's hijack filters will force the TLS to be allocated before it
+// sends a flare.
+//
+// Therefore, this function can only be called:
+// 1) on a managed thread
+// 2) on a native thread after that thread has been hijacked and sent a flare.
+//
+// This is brittle reasoning, but so is the rest of interop-debugging.
+//
+HRESULT CordbUnmanagedThread::SetEEThreadValue(REMOTE_PTR EETlsValue)
+{
+ FAIL_IF_NEUTERED(this);
+
+ // Compute the address of the necessary TLS value.
+ DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets);
+
+ // Compute the address of the necessary TLS value.
+ HRESULT hr = LoadTLSArrayPtr();
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+
+ DWORD slot = (DWORD) pRO->m_TLSIndex;
+
+ void * pBase = NULL;
+ SIZE_T slotAdjusted = slot;
+ if (slot < TLS_MINIMUM_AVAILABLE)
+ {
+ pBase = m_pTLSArray;
+ }
+ else if (slot < TLS_MINIMUM_AVAILABLE+TLS_EXPANSION_SLOTS)
+ {
+ pBase = m_pTLSExtendedArray;
+ slotAdjusted -= TLS_MINIMUM_AVAILABLE;
+
+ // Expansion slot is lazily allocated. If we're trying to read from it, but hasn't been allocated,
+ // then the TLS slot is still the default value, which is 0.
+ if (pBase == NULL)
+ {
+ // See reasoning in header for why this should succeed.
+ _ASSERTE(!"Can't set to expansion slots because they haven't been allocated");
+ return E_FAIL;
+ }
+ }
+ else
+ {
+ // Slot is out of range. Shouldn't happen unless debuggee is corrupted.
+ _ASSERTE(!"Invalid TLS slot");
+ return E_INVALIDARG;
+ }
+
+
+ void *pEEThreadTLS = (BYTE*) pBase + (slotAdjusted * sizeof(void*));
+
+
+ // Write the thread's TLS value.
+ hr = GetProcess()->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(pEEThreadTLS), &EETlsValue);
+
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::SEETV: failed to set TLS value: "
+ "computed addr=0x%p index=%d, err=%x\n",
+ pEEThreadTLS, pRO->m_TLSIndex, hr));
+
+ return hr;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000000,
+ "CUT::SEETV: EE Thread TLS value is now 0x%p for thread 0x%x\n",
+ EETlsValue, m_id));
+
+ return S_OK;
+}
+#else // FEATURE_IMPLICIT_TLS
+
+#ifdef DBG_TARGET_X86
+#define WINNT_OFFSETOF__TEB__ThreadLocalStoragePointer 0x2c
+#elif defined(DBG_TARGET_AMD64)
+#define WINNT_OFFSETOF__TEB__ThreadLocalStoragePointer 0x58
+#elif defined(DBG_TARGET_ARM)
+#define WINNT_OFFSETOF__TEB__ThreadLocalStoragePointer 0x2c
+#elif defined(DBG_TARGET_ARM64)
+#define WINNT_OFFSETOF__TEB__ThreadLocalStoragePointer 0x58
+#endif
+
+// sets the value of gCurrentThreadInfo.m_pThread
+HRESULT CordbUnmanagedThread::SetEEThreadValue(REMOTE_PTR EETlsValue)
+{
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ REMOTE_PTR EEThreadAddr = (BYTE*) GetClrModuleTlsDataAddress() + OFFSETOF__TLS__tls_CurrentThread;
+ if(EEThreadAddr == NULL)
+ return E_FAIL;
+
+ // Write the thread's TLS value.
+ hr = GetProcess()->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(EEThreadAddr), &EETlsValue);
+
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::SEETV: failed to set TLS value: "
+ "computed addr=0x%p index=%d, err=%x\n",
+ EEThreadAddr, GetProcess()->m_runtimeOffsets.m_TLSIndex, hr));
+
+ return hr;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000000,
+ "CUT::SEETV: EE Thread TLS value is now 0x%p for thread 0x%x\n",
+ EETlsValue, m_id));
+
+ return S_OK;
+
+}
+
+// gets the value of gCurrentThreadInfo.m_pThread
+DWORD_PTR CordbUnmanagedThread::GetEEThreadValue()
+{
+ DWORD_PTR ret = NULL;
+
+ REMOTE_PTR EEThreadAddr = (BYTE*) GetClrModuleTlsDataAddress() + OFFSETOF__TLS__tls_CurrentThread;
+ if(EEThreadAddr == NULL)
+ return NULL;
+
+ // Read the thread's TLS value.
+ HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(EEThreadAddr), &ret);
+
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::GEETV: failed to get TLS value: "
+ "computed addr=0x%p index=%d, err=%x\n",
+ EEThreadAddr, GetProcess()->m_runtimeOffsets.m_TLSIndex, hr));
+
+ return NULL;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000000,
+ "CUT::GEETV: EE Thread TLS value is 0x%p for thread 0x%x\n",
+ ret, m_id));
+
+ return ret;
+}
+
+// returns the remote address of gCurrentThreadInfo
+REMOTE_PTR CordbUnmanagedThread::GetClrModuleTlsDataAddress()
+{
+ HRESULT hr = S_OK;
+
+ REMOTE_PTR tlsArrayAddr;
+ hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS((BYTE*)m_threadLocalBase + WINNT_OFFSETOF__TEB__ThreadLocalStoragePointer), &tlsArrayAddr);
+ if (FAILED(hr))
+ {
+ return NULL;
+ }
+
+ if (tlsArrayAddr == NULL)
+ {
+ _ASSERTE(!"ThreadLocalStoragePointer is NULL");
+ return NULL;
+ }
+
+ DWORD slot = (DWORD)(GetProcess()->m_runtimeOffsets.m_TLSIndex);
+
+ REMOTE_PTR clrModuleTlsDataAddr;
+ hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS((BYTE*)tlsArrayAddr + slot * sizeof(void*)), &clrModuleTlsDataAddr);
+ if (FAILED(hr))
+ {
+ return NULL;
+ }
+
+ if (clrModuleTlsDataAddr == NULL)
+ {
+ _ASSERTE(!"No clr module data present at _tls_index for this thread");
+ return NULL;
+ }
+
+ return clrModuleTlsDataAddr;
+}
+
+// gets the value of gCurrentThreadInfo.m_EETlsData
+REMOTE_PTR CordbUnmanagedThread::GetEETlsDataBlock()
+{
+ REMOTE_PTR ret;
+
+ REMOTE_PTR blockAddr = (BYTE*) GetClrModuleTlsDataAddress() + OFFSETOF__TLS__tls_EETlsData;
+
+
+ HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(blockAddr), &ret);
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::GEETDB: failed to read EETlsData address: computed addr=0x%p offset=%d, err=%x\n",
+ blockAddr, OFFSETOF__TLS__tls_EETlsData, hr));
+
+ return NULL;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000000, "CUT::GEETDB: EETlsData address value is 0x%p for thread 0x%x\n", ret, m_id));
+
+ return ret;
+}
+
+#endif // FEATURE_IMPLICIT_TLS
+
+/*
+ * CacheEEDebuggerWord
+ *
+ * NOTE: This routine is inappropriately named at this time because we dont
+ * actually cache any values. This is because we dont have a way to invalidate
+ * the cache between purely-native continues.
+ *
+ * This routine grabs two pieces of information from the target process via
+ * ReadProcessMemory. First, if the runtime does not have a thread object for
+ * this thread it grabs the debugger's value from the TLS slot. If there is a
+ * runtime thread object, then it saves that away and grabs the debugger's value
+ * from the thread object.
+ *
+ * Parameters:
+ * None.
+ *
+ * Returns:
+ * None. If it fails, then the Get/Set functions will fail.
+ */
+void CordbUnmanagedThread::CacheEEDebuggerWord()
+{
+ LOG((LF_CORDB, LL_INFO1000, "CacheEEDW: Entered\n"));
+
+#ifdef FEATURE_IMPLICIT_TLS
+ REMOTE_PTR value = (REMOTE_PTR)GetEEThreadValue();
+#else
+ REMOTE_PTR value = (REMOTE_PTR)GetTlsSlot(GetProcess()->m_runtimeOffsets.m_TLSIndex);
+#endif
+
+ if ((((DWORD)value) & 0x1) == 1)
+ {
+ m_pEEThread = NULL;
+ m_pdwTlsValue = (REMOTE_PTR)((BYTE*)value - 0x1);
+ m_fValidTlsData = TRUE;
+ }
+ else if (value != NULL)
+ {
+ m_pEEThread = value;
+
+ // Compute the address of the debugger word #2.
+ void *pEEDebuggerWord = (BYTE*)m_pEEThread + GetProcess()->m_runtimeOffsets.m_EEThreadDebuggerWordOffset;
+
+ // Update the word.
+ HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pEEDebuggerWord), &m_pdwTlsValue);
+ m_fValidTlsData = SUCCEEDED(hr);
+
+ if (!m_fValidTlsData)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "EEDW: failed to read debugger word: 0x%08x + 0x%x = 0x%p, err=%d\n",
+ m_pEEThread, GetProcess()->m_runtimeOffsets.m_EEThreadDebuggerWordOffset, pEEDebuggerWord, GetLastError()));
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CacheEEDW: Debugger word is 0x%p\n", m_pdwTlsValue));
+ }
+ }
+ else
+ {
+ m_fValidTlsData = TRUE;
+ m_pEEThread = NULL;
+ m_pdwTlsValue = NULL;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "CacheEEDW: Exited\n"));
+}
+
+/*
+ * GetEEDebuggerWord
+ *
+ * This routine returns the value read from the thread
+ *
+ * Parameters:
+ * pValue - Location to store value.
+ *
+ * Returns:
+ * E_INVALIDARG, E_FAIL, S_OK
+ */
+HRESULT CordbUnmanagedThread::GetEEDebuggerWord(REMOTE_PTR *pValue)
+{
+ if (pValue == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ CacheEEDebuggerWord();
+
+ if (!m_fValidTlsData)
+ {
+ *pValue = NULL;
+ return E_FAIL;
+ }
+
+ *pValue = m_pdwTlsValue;
+
+ return S_OK;
+}
+
+// SetEEDebuggerWord
+//
+// This routine writes the value to the thread
+//
+// Parameters:
+// pValue - Value to write.
+//
+// Returns:
+// HRESULT failure code or S_OK
+//
+// Notes:
+// This function is very dangerous. See code:CordbUnmanagedThread::SetEETlsValue for why.
+HRESULT CordbUnmanagedThread::SetEEDebuggerWord(REMOTE_PTR value)
+{
+ LOG((LF_CORDB, LL_INFO1000, "CUT::SEEDW: Entered - value is 0x%p\n", value));
+
+ CacheEEDebuggerWord();
+
+ if (!m_fValidTlsData)
+ {
+ return E_FAIL;
+ }
+
+ m_pdwTlsValue = value;
+
+ //
+ // If the thread is NULL, bit-or on a 1 and store that.
+ //
+ if (m_pEEThread == NULL)
+ {
+ REMOTE_PTR pdwTemp = m_pdwTlsValue;
+
+ if (pdwTemp != 0)
+ {
+ // actually we add 1, but we only use it for pointers which are
+ // 8 byte aligned so it is the same thing
+ _ASSERTE( ((UINT_PTR)pdwTemp & 0x1) == 0);
+ pdwTemp = (REMOTE_PTR) ((BYTE*)pdwTemp + 0x01);
+ }
+ // This will write to the TLS slot. It's only safe to do this after a Flare has been sent from the
+ // LS (since that's what guarantees the slot is allocated).
+ return SetEEThreadValue(pdwTemp);
+ }
+ else
+ {
+ // Compute the address of the debugger word #2.
+ void *pEEDebuggerWord = (BYTE*)m_pEEThread + GetProcess()->m_runtimeOffsets.m_EEThreadDebuggerWordOffset;
+
+ // Update the word.
+ HRESULT hr = GetProcess()->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(pEEDebuggerWord), &m_pdwTlsValue);
+
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::SEETDW: failed to write debugger word: 0x%08x + 0x%x = 0x%08x, err=%x\n",
+ m_pEEThread, GetProcess()->m_runtimeOffsets.m_EEThreadDebuggerWordOffset, pEEDebuggerWord, hr));
+
+ return hr;
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "CUT::SEEDW: Exited\n"));
+ return S_OK;
+}
+
+/*
+ * GetEEThreadPtr
+ *
+ * This routine returns the value read from the thread
+ *
+ * Parameters:
+ * ppEEThread - Location to store value.
+ *
+ * Returns:
+ * E_INVALIDARG, E_FAIL, S_OK
+ */
+HRESULT CordbUnmanagedThread::GetEEThreadPtr(REMOTE_PTR *ppEEThread)
+{
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ if (ppEEThread == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ CacheEEDebuggerWord();
+
+ if (!m_fValidTlsData)
+ {
+ *ppEEThread = NULL;
+ return E_FAIL;
+ }
+
+ *ppEEThread = m_pEEThread;
+
+ return S_OK;
+}
+
+
+
+void CordbUnmanagedThread::GetEEState(bool *threadStepping, bool *specialManagedException)
+{
+ REMOTE_PTR pEEThread;
+
+ HRESULT hr = GetEEThreadPtr(&pEEThread);
+
+ _ASSERTE(SUCCEEDED(hr));
+ _ASSERTE(pEEThread != NULL);
+
+ *threadStepping = false;
+ *specialManagedException = false;
+
+ // Compute the address of the thread's state
+ DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets);
+ void *pEEThreadStateNC = (BYTE*) pEEThread + pRO->m_EEThreadStateNCOffset;
+
+ // Grab the thread state out of the EE Thread.
+ DWORD EEThreadStateNC;
+ hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pEEThreadStateNC), &EEThreadStateNC);
+
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::GEETS: failed to read thread state NC: 0x%p + 0x%x = 0x%p, err=%d\n",
+ pEEThread, pRO->m_EEThreadStateNCOffset, pEEThreadStateNC, GetLastError()));
+
+ return;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000000, "CUT::GEETS: EE Thread state NC is 0x%08x\n", EEThreadStateNC));
+
+ // Looks like we've got the state of the thread.
+ *threadStepping = ((EEThreadStateNC & pRO->m_EEThreadSteppingStateMask) != 0);
+ *specialManagedException = ((EEThreadStateNC & pRO->m_EEIsManagedExceptionStateMask) != 0);
+
+ return;
+}
+
+// Currently, the EE manually tracks its "can't-stop" regions. This retrieves that manual tracking value.
+// @todo - This should eventually become deprecated since the Entire EE will be a can't-stop region.
+bool CordbUnmanagedThread::GetEEThreadCantStopHelper()
+{
+ // Note: any failure to read memory is okay for this method. We simply say that the thread is not is a can't stop
+ // state, and that's okay.
+
+ REMOTE_PTR pEEThread;
+
+ HRESULT hr = GetEEThreadPtr(&pEEThread);
+
+ _ASSERTE(SUCCEEDED(hr));
+ _ASSERTE(pEEThread != NULL);
+
+ // Compute the address of the thread's debugger word #1
+ DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets);
+ void *pEEThreadCantStop = (BYTE*) pEEThread + pRO->m_EEThreadCantStopOffset;
+
+ // Grab the debugger word #1 out of the EE Thread.
+ DWORD EEThreadCantStop;
+ hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pEEThreadCantStop), &EEThreadCantStop);
+
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::GEETS: failed to read thread cant stop: 0x%08x + 0x%x = 0x%08x, err=%d\n",
+ pEEThread, pRO->m_EEThreadCantStopOffset, pEEThreadCantStop, GetLastError()));
+
+ return false;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000000, "CUT::GEETS: EE Thread cant stop is 0x%08x\n", EEThreadCantStop));
+
+ // Looks like we've got it.
+ if (EEThreadCantStop != 0)
+ return true;
+ else
+ return false;
+}
+
+
+// Is the thread in a "can't stop" region?
+// "Can't-Stop" regions include anything that's "inside" the runtime; ie, the runtime has some
+// synchronization mechanism that will halt this thread, and so we don't need to suspend it.
+// The interop debugger should leave anything in a can't-stop region alone and just let the runtime
+// handle it.
+bool CordbUnmanagedThread::IsCantStop()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ // Definition of a can't stop region:
+ // - Any "Special" thread that doesn't have an EE Thread (includes the real Helper Thread,
+ // Concurrent GC thread, ThreadPool thread, etc).
+ // - Any thread in Cooperative code.
+ // - Any thread w/ a can't-stop count > 0.
+ // - Any thread holding a "Debugger" Crst. (This is actually a subset of the
+ // can't-stop count b/c Enter/Leave adjust that count).
+ // - Any generic, first chance or RaiseException hijacked thread
+
+ // If the runtime isn't init yet, not a can't-stop.
+ // We don't even have the DCB yet.
+ if (!GetProcess()->m_initialized)
+ {
+ return false;
+ }
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ if(IsRaiseExceptionHijacked())
+ {
+ return true;
+ }
+
+ REMOTE_PTR pEEThread;
+ HRESULT hr = this->GetEEThreadPtr(&pEEThread);
+
+ if (FAILED(hr))
+ {
+ _ASSERTE(!"Failed to EEThreadPtr in IsCantStop");
+ return true;
+ }
+
+ DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets);
+
+ // @todo- remove this and use the CantStop index below.
+ // Is this a "special" thread?
+ // Any thread that can take CLR locks w/o having an EE Thread object should
+ // be marked as special. These threads are in "can't-stop" regions b/c if we suspend
+ // them, they may be holding a lock that blocks the helper thread.
+ // The helper thread is marked as "special".
+ {
+ SIZE_T idx = pRO->m_TLSIsSpecialIndex;
+ REMOTE_PTR special = GetPreDefTlsSlot(idx, NULL);
+
+ // If it's a special thread
+ if ((special != 0) && (pEEThread == NULL))
+ {
+ return true;
+ }
+ }
+
+ // Check for CantStop regions off the FLS.
+ // This is the biggest way to describe can't-stop regions when we're in preemptive mode
+ // (or when we don't have a thread object).
+ // If a LS thread takes a debugger lock, it will increment the Can't-Stop count.
+ {
+ SIZE_T idx = pRO->m_TLSCantStopIndex;
+ REMOTE_PTR count = (REMOTE_PTR) GetPreDefTlsSlot(idx, NULL);
+
+ // Just a sanity check here. There's nothing special about 1000, but if the
+ // stop-count gets this big, 99% chance it's:
+ // - we're accessing the wrong memory (an issue)
+ // - someone on the LS is leaking stop-counts. (an issue).
+ _ASSERTE(count < (REMOTE_PTR)1000);
+
+ if (count > 0)
+ {
+ LOG((LF_CORDB, LL_INFO1000000, "Thread 0x%x is can't-stop b/c count=%d\n", m_id, count));
+ return true;
+ }
+ }
+
+ EX_TRY
+ {
+ GetProcess()->UpdateRightSideDCB();
+ }
+ EX_CATCH
+ {
+ _ASSERTE(!"IsCantStop: Failed updating debugger control block");
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+
+ // Helper's canary thread is always can't-stop.
+ if (this->m_id == GetProcess()->GetDCB()->m_CanaryThreadId)
+ {
+ return true;
+ }
+
+ // Check helper thread / or anyone pretending to be the helper thread.
+ if ((this->m_id == GetProcess()->GetDCB()->m_helperThreadId) ||
+ (this->m_id == GetProcess()->GetDCB()->m_temporaryHelperThreadId) ||
+ (this->m_id == GetProcess()->m_helperThreadId))
+ {
+ return true;
+ }
+
+ if (IsGenericHijacked() || IsFirstChanceHijacked())
+ return true;
+
+ // If this isn't a EE thread (and not the helper thread, and not hijacked), then it's ok to stop.
+ if (pEEThread == NULL)
+ return false;
+
+ // This checks for an explicit "can't" stop region.
+ // Eventually, these explicit regions should become a complete subset of the other checks.
+ if (GetEEThreadCantStopHelper())
+ return true;
+
+
+ // If we're in cooperative mode (either managed code or parts inside the runtime), then don't stop.
+ // Note we could remove this since the check is made in side of the DAC request below,
+ // but it's faster to look here.
+ if (GetEEPGCDisabled())
+ return true;
+
+ return false;
+}
+
+bool CordbUnmanagedThread::GetEEPGCDisabled()
+{
+ // Note: any failure to read memory is okay for this method. We simply say that the thread has PGC disabled, which
+ // is always the worst case scenario.
+
+ REMOTE_PTR pEEThread;
+
+ HRESULT hr = GetEEThreadPtr(&pEEThread);
+
+ _ASSERTE(SUCCEEDED(hr));
+
+ // Compute the address of the thread's PGC disabled word
+ DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets);
+ void *pEEThreadPGCDisabled = (BYTE*) pEEThread + pRO->m_EEThreadPGCDisabledOffset;
+
+ // Grab the PGC disabled word out of the EE Thread.
+ DWORD EEThreadPGCDisabled;
+ hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pEEThreadPGCDisabled), &EEThreadPGCDisabled);
+
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::GEETS: failed to read thread PGC Disabled: 0x%p + 0x%x = 0x%p, err=%d\n",
+ pEEThread, pRO->m_EEThreadPGCDisabledOffset, pEEThreadPGCDisabled, GetLastError()));
+
+ return true;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000000, "CUT::GEETS: EE Thread PGC Disabled is 0x%08x\n", EEThreadPGCDisabled));
+
+ // Looks like we've got it.
+ if (EEThreadPGCDisabled == pRO->m_EEThreadPGCDisabledValue)
+ return true;
+ else
+ return false;
+}
+
+bool CordbUnmanagedThread::GetEEFrame()
+{
+ REMOTE_PTR pEEThread;
+
+ HRESULT hr = GetEEThreadPtr(&pEEThread);
+
+ _ASSERTE(SUCCEEDED(hr));
+ _ASSERTE(pEEThread != NULL);
+
+ // Compute the address of the thread's frame ptr
+ DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets);
+ void *pEEThreadFrame = (BYTE*) pEEThread + pRO->m_EEThreadFrameOffset;
+
+ // Grab the thread's frame out of the EE Thread.
+ DWORD EEThreadFrame;
+ hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pEEThreadFrame), &EEThreadFrame);
+
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::GEETF: failed to read thread frame: 0x%p + 0x%x = 0x%p, err=%d\n",
+ pEEThread, pRO->m_EEThreadFrameOffset, pEEThreadFrame, GetLastError()));
+
+ return false;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000000, "CUT::GEETF: EE Thread's frame is 0x%08x\n", EEThreadFrame));
+
+ // Looks like we've got the frame of the thread.
+ if (EEThreadFrame != pRO->m_EEMaxFrameValue)
+ return true;
+ else
+ return false;
+}
+
+// Gets the thread context as if the thread were unhijacked, regardless
+// of whether it really is
+HRESULT CordbUnmanagedThread::GetThreadContext(DT_CONTEXT* pContext)
+{
+ // While hijacked there are 3 potential contexts we could be resuming back to
+ // 1) A context provided in SetThreadContext that we defered applying
+ // 2) The LS copy of the context on the stack being modified in the handler
+ // 3) The original context present when the hijack was started
+ //
+ // Both #1 and #3 are stored in the GetHijackCtx() space so of course you can't
+ // have them both. You have have #1 if IsContextSet() is true, otherwise it holds #3
+ //
+ // GenericHijack, FirstChanceHijackForSync, and RaiseExceptionHijack use #1 if available
+ // and fallback to #3 if not. In other words they use GetHijackCtx() regardless of which thing it holds
+ // M2UHandoff uses #1 if available and then falls back to #2.
+ //
+ // The reasoning here is that the first three hijacks are intended to be transparent. Since
+ // the debugger shouldn't know they are occuring then it shouldn't see changes potentially
+ // made on the LS. The M2UHandoff is not transparent, it has to update the context in order
+ // to get clear of a bp.
+ //
+ // If not hijacked call the normal Win32 function.
+
+ HRESULT hr = S_OK;
+
+ LOG((LF_CORDB, LL_INFO10000, "CUT::GTC: thread=0x%p, flags=0x%x.\n", this, pContext->ContextFlags));
+
+ if(IsContextSet() || IsGenericHijacked() || (IsFirstChanceHijacked() && IsBlockingForSync())
+ || IsRaiseExceptionHijacked())
+ {
+ _ASSERTE(IsFirstChanceHijacked() || IsGenericHijacked() || IsRaiseExceptionHijacked());
+ LOG((LF_CORDB, LL_INFO10000, "CUT::GTC: hijackCtx case IsContextSet=%d IsGenericHijacked=%d"
+ "HijackedForSync=%d RaiseExceptionHijacked=%d.\n",
+ IsContextSet(), IsGenericHijacked(), IsBlockingForSync(), IsRaiseExceptionHijacked()));
+ LOG((LF_CORDB, LL_INFO10000, "CUT::GTC: hijackCtx is:\n"));
+ LogContext(GetHijackCtx());
+ CORDbgCopyThreadContext(pContext, GetHijackCtx());
+ }
+ // use the LS for M2UHandoff
+ else if (IsFirstChanceHijacked() && !IsBlockingForSync())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CUT::GTC: getting LS context for first chance hijack, addr=0x%08x.\n",
+ m_pLeftSideContext.UnsafeGet()));
+
+ // Read the context into a temp context then copy to the out param.
+ DT_CONTEXT tempContext = { 0 };
+
+ hr = GetProcess()->SafeReadThreadContext(m_pLeftSideContext, &tempContext);
+
+ if (SUCCEEDED(hr))
+ CORDbgCopyThreadContext(pContext, &tempContext);
+ }
+ // no hijack in place so just call straight through
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CUT::GTC: getting context from win32.\n"));
+
+ BOOL succ = DbiGetThreadContext(m_handle, pContext);
+
+ if (!succ)
+ hr = HRESULT_FROM_GetLastError();
+ }
+
+ if(IsSSFlagHidden())
+ {
+ UnsetSSFlag(pContext);
+ }
+ LogContext(pContext);
+
+ return hr;
+}
+
+// Sets the thread context as if the thread were unhijacked, regardless
+// of whether it really is. See GetThreadContext above for more details
+// on this abstraction
+HRESULT CordbUnmanagedThread::SetThreadContext(DT_CONTEXT* pContext)
+{
+ HRESULT hr = S_OK;
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "CUT::STC: thread=0x%p, flags=0x%x.\n", this, pContext->ContextFlags));
+
+ LogContext(pContext);
+
+ // If the thread is first chance hijacked, then write the context into the remote process. If the thread is generic
+ // hijacked, then update the copy of the context that we already have. Otherwise call the normal Win32 function.
+
+ if (IsGenericHijacked() || IsFirstChanceHijacked() || IsRaiseExceptionHijacked())
+ {
+ if(IsGenericHijacked())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CUT::STC: setting context from generic/2nd chance hijack.\n"));
+ }
+ else if(IsFirstChanceHijacked())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CUT::STC: setting context from 1st chance hijack.\n"));
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CUT::STC: setting context from RaiseException hijack.\n"));
+ }
+ SetState(CUTS_HasContextSet);
+ CORDbgCopyThreadContext(GetHijackCtx(), pContext);
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CUT::STC: setting context from win32.\n"));
+
+ // If the user is also setting the SS flag then we no longer have to hide it
+ if(IsSSFlagEnabled(pContext))
+ {
+ ClearState(CUTS_IsSSFlagHidden);
+ }
+ // if the user is turning off the SS flag but we still want it on then leave it on
+ // but hidden
+ if(!IsSSFlagEnabled(pContext) && IsSSFlagNeeded())
+ {
+ SetState(CUTS_IsSSFlagHidden);
+ SetSSFlag(pContext);
+ }
+
+ BOOL succ = DbiSetThreadContext(m_handle, pContext);
+
+ if (!succ)
+ {
+ hr = HRESULT_FROM_GetLastError();
+ }
+ }
+
+ return hr;
+}
+
+// Turns on the stepping flag internally and tracks whether or not the flag
+// should also be seen by the user
+VOID CordbUnmanagedThread::BeginStepping()
+{
+ _ASSERTE(!IsGenericHijacked() && !IsFirstChanceHijacked());
+ _ASSERTE(!IsSSFlagNeeded());
+ _ASSERTE(!IsSSFlagHidden());
+
+ DT_CONTEXT tempContext;
+ tempContext.ContextFlags = DT_CONTEXT_FULL;
+ BOOL succ = DbiGetThreadContext(m_handle, &tempContext);
+ _ASSERTE(succ);
+
+ if(!IsSSFlagEnabled(&tempContext))
+ {
+ SetSSFlag(&tempContext);
+ SetState(CUTS_IsSSFlagHidden);
+ }
+ SetState(CUTS_IsSSFlagNeeded);
+
+ succ = DbiSetThreadContext(m_handle, &tempContext);
+ _ASSERTE(succ);
+}
+
+// Turns off the stepping flag internally. If the user was also not using it then
+// the flag is turned off on the context
+VOID CordbUnmanagedThread::EndStepping()
+{
+ _ASSERTE(!IsGenericHijacked() && !IsFirstChanceHijacked());
+ _ASSERTE(IsSSFlagNeeded());
+
+ DT_CONTEXT tempContext;
+ tempContext.ContextFlags = DT_CONTEXT_FULL;
+ BOOL succ = DbiGetThreadContext(m_handle, &tempContext);
+ _ASSERTE(succ);
+
+ if(IsSSFlagHidden())
+ {
+ UnsetSSFlag(&tempContext);
+ ClearState(CUTS_IsSSFlagHidden);
+ }
+ ClearState(CUTS_IsSSFlagNeeded);
+
+ succ = DbiSetThreadContext(m_handle, &tempContext);
+ _ASSERTE(succ);
+}
+
+
+// Writes some details of the given context into the debugger log
+VOID CordbUnmanagedThread::LogContext(DT_CONTEXT* pContext)
+{
+#if defined(DBG_TARGET_X86)
+ LOG((LF_CORDB, LL_INFO10000,
+ "CUT::LC: Eip=0x%08x, Esp=0x%08x, Eflags=0x%08x\n", pContext->Eip, pContext->Esp,
+ pContext->EFlags));
+#elif defined(DBG_TARGET_AMD64)
+ LOG((LF_CORDB, LL_INFO10000,
+ "CUT::LC: Rip=" FMT_ADDR ", Rsp=" FMT_ADDR ", Eflags=0x%08x\n",
+ DBG_ADDR(pContext->Rip),
+ DBG_ADDR(pContext->Rsp),
+ pContext->EFlags)); // EFlags is still 32bits on AMD64
+#else // DBG_TARGET_X86
+ PORTABILITY_ASSERT("LogContext needs a PC and stack pointer.");
+#endif // DBG_TARGET_X86
+}
+
+// Hijacks this thread using the FirstChanceSuspend hijack
+HRESULT CordbUnmanagedThread::SetupFirstChanceHijackForSync()
+{
+ HRESULT hr = S_OK;
+
+ CONSISTENCY_CHECK(!IsBlockingForSync()); // Shouldn't double hijack
+ CONSISTENCY_CHECK(!IsCantStop()); // must be in stoppable-region.
+ _ASSERTE(HasIBEvent());
+
+ // We used to hijack for real here but now we have a vectored exception handler that will always be
+ // triggered. So we don't have hijack in the sense that we overwrite the thread's IP. However we still
+ // set the flag so that when we receive the HijackStartedSignal from the LS we know that this thread
+ // should block in there rather than continuing.
+ //hr = SetupFirstChanceHijack(EHijackReason::kFirstChanceSuspend, &(IBEvent()->m_currentDebugEvent.u.Exception.ExceptionRecord));
+
+ _ASSERTE(!IsFirstChanceHijacked());
+ _ASSERTE(!IsGenericHijacked());
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ // We'd better not be hijacking in a can't stop region!
+ // This also means we can't hijack in coopeative (since that's a can't-stop)
+ _ASSERTE(!IsCantStop());
+
+ // we should not be stepping into hijacks
+ _ASSERTE(!IsSSFlagHidden());
+ _ASSERTE(!IsSSFlagNeeded());
+ _ASSERTE(!IsContextSet());
+
+ // snapshot the current context so we can start spoofing it
+ LOG((LF_CORDB, LL_INFO10000, "CUT::SFCHFS: hijackCtx started as:\n"));
+ LogContext(GetHijackCtx());
+
+ // Save the thread's full context.
+ DT_CONTEXT context;
+ context.ContextFlags = DT_CONTEXT_FULL;
+ BOOL succ = DbiGetThreadContext(m_handle, &context);
+ _ASSERTE(succ);
+ // for debugging when GetThreadContext fails
+ if(!succ)
+ {
+ DWORD error = GetLastError();
+ LOG((LF_CORDB, LL_ERROR, "CUT::SFCHFS: DbiGetThreadContext error=0x%x\n", error));
+ }
+
+ GetHijackCtx()->ContextFlags = DT_CONTEXT_FULL;
+ CORDbgCopyThreadContext(GetHijackCtx(), &context);
+ LOG((LF_CORDB, LL_INFO10000, "CUT::SFCHFS: thread=0x%x Hijacking for sync. Original context is:\n", this));
+ LogContext(GetHijackCtx());
+
+ // We're hijacking now...
+ SetState(CUTS_FirstChanceHijacked);
+ GetProcess()->m_state |= CordbProcess::PS_HIJACKS_IN_PLACE;
+
+ // We'll decrement this once the hijack returns
+ GetProcess()->m_cFirstChanceHijackedThreads++;
+ this->SetState(CUTS_BlockingForSync);
+
+ // we don't want to single step into the vectored exception handler
+ // we will restore the SS flag after returning from the hijack
+ if(IsSSFlagEnabled(&context))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CUT::SFCHFS: thread=0x%x Clearing SS flag\n", this));
+ UnsetSSFlag(&context);
+ succ = DbiSetThreadContext(m_handle, &context);
+ _ASSERTE(succ);
+ }
+
+
+
+ // There's a bizarre race where the thread was suspended right as the thread was about to dispatch a
+ // debug event. We still get the debug event, and then may try to hijack. Resume the thread so that
+ // it can run to the hijack.
+ if (this->IsSuspended())
+ {
+ LOG((LF_CORDB, LL_ERROR, "CUT::SFCHFS: thread was suspended... resuming\n"));
+ DWORD success = ResumeThread(this->m_handle);
+
+ if (success == 0xFFFFFFFF)
+ {
+ // Since we suspended it, we should be able to resume it in this window.
+ CONSISTENCY_CHECK_MSGF(false, ("Failed to resume thread: tid=0x%x!", this->m_id));
+ }
+ else
+ {
+ this->ClearState(CUTS_Suspended);
+ }
+ }
+
+ return hr;
+
+}
+
+HRESULT CordbUnmanagedThread::SetupFirstChanceHijack(EHijackReason::EHijackReason reason, const EXCEPTION_RECORD * pExceptionRecord)
+{
+ _ASSERTE(!IsFirstChanceHijacked());
+ _ASSERTE(!IsGenericHijacked());
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ // We'd better not be hijacking in a can't stop region!
+ // This also means we can't hijack in coopeative (since that's a can't-stop)
+ _ASSERTE(!IsCantStop());
+
+ // we should not be stepping into hijacks
+ _ASSERTE(!IsSSFlagHidden());
+ _ASSERTE(!IsSSFlagNeeded());
+
+ // There's a bizarre race where the thread was suspended right as the thread was about to dispatch a
+ // debug event. We still get the debug event, and then may try to hijack. Resume the thread so that
+ // it can run to the hijack.
+ if (this->IsSuspended())
+ {
+ DWORD succ = ResumeThread(this->m_handle);
+
+ if (succ == 0xFFFFFFFF)
+ {
+ // Since we suspended it, we should be able to resume it in this window.
+ CONSISTENCY_CHECK_MSGF(false, ("Failed to resume thread: tid=0x%x!", this->m_id));
+ }
+ else
+ {
+ this->ClearState(CUTS_Suspended);
+ }
+ }
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+// We save off the SEH handler on X86 to make sure we restore it properly after the hijack is complete
+// The hijacks don't return normally and the SEH chain might have handlers added that don't get removed by default
+#ifdef DBG_TARGET_X86
+ hr = SaveCurrentLeafSeh();
+ if(FAILED(hr))
+ ThrowHR(hr);
+#endif
+ CORDB_ADDRESS LSContextAddr;
+ GetProcess()->GetDAC()->Hijack(VMPTR_Thread::NullPtr(),
+ GetOSTid(),
+ pExceptionRecord,
+ (CONTEXT*) GetHijackCtx(),
+ sizeof(CONTEXT),
+ reason,
+ NULL,
+ &LSContextAddr);
+ LOG((LF_CORDB, LL_INFO10000, "CUT::SFCH: pLeftSideContext=0x%p\n", LSContextAddr));
+ m_pLeftSideContext.Set(CORDB_ADDRESS_TO_PTR(LSContextAddr));
+ }
+ EX_CATCH_HRESULT(hr);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CUT::SFCH: Error setting up hijack context hr=0x%x\n", hr));
+ return hr;
+ }
+
+
+ // We're hijacked now...
+ SetState(CUTS_FirstChanceHijacked);
+ GetProcess()->m_state |= CordbProcess::PS_HIJACKS_IN_PLACE;
+
+ // We'll decrement this once the hijack returns
+ GetProcess()->m_cFirstChanceHijackedThreads++;
+
+ return S_OK;
+}
+
+HRESULT CordbUnmanagedThread::SetupGenericHijack(DWORD eventCode, const EXCEPTION_RECORD * pRecord)
+{
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ _ASSERTE(eventCode == EXCEPTION_DEBUG_EVENT);
+
+ _ASSERTE(!IsFirstChanceHijacked());
+ _ASSERTE(!IsGenericHijacked());
+ _ASSERTE(!IsContextSet());
+
+ // Save the thread's full context.
+ GetHijackCtx()->ContextFlags = DT_CONTEXT_FULL;
+
+ BOOL succ = DbiGetThreadContext(m_handle, GetHijackCtx());
+
+ if (!succ)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::SGH: couldn't get thread context: %d\n", GetLastError()));
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+
+#if defined(DBG_TARGET_AMD64)
+
+ // On X86 Debugger::GenericHijackFunc() ensures the stack is walkable
+ // by simply using the EBP chain, therefore we can execute the hijack
+ // by setting the thread's context EIP to point to this function.
+ // On X64, however, we first attempt to set up a "proper" hijack, with
+ // a function that allows the OS to unwind the stack (ExceptionHijack).
+ // If this fails we'll use the same method as on X86, even though the
+ // stack will become un-walkable
+
+ ULONG32 dwThreadId = GetOSTid();
+ CordbThread * pThread = GetProcess()->TryLookupOrCreateThreadByVolatileOSId(dwThreadId);
+
+ // For threads in the thread store we set up the full size
+ // hijack, otherwise we fallback to hijacking by SetIP.
+ if (pThread != NULL)
+ {
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Note that the data-target is not atomic, and we have no rollback mechanism.
+ // We have to do several writes. If the data-target fails the writes half-way through the
+ // target will be inconsistent.
+ GetProcess()->GetDAC()->Hijack(
+ pThread->m_vmThreadToken,
+ dwThreadId,
+ pRecord,
+ (CONTEXT*) GetHijackCtx(),
+ sizeof(CONTEXT),
+ EHijackReason::kGenericHijack,
+ NULL,
+ NULL);
+ }
+ EX_CATCH_HRESULT(hr);
+ if (SUCCEEDED(hr))
+ {
+ // Remember that we've hijacked the thread.
+ SetState(CUTS_GenericHijacked);
+
+ return S_OK;
+ }
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CUT::SGH: Error setting up hijack context hr=0x%x\n", hr);
+ // fallthrough (above hijack might have failed due to stack overflow, for example)
+
+ }
+ // else (non-threadstore threads) fallthrough
+
+#endif // DBG_TARGET_AMD64
+
+ // Remember that we've hijacked the guy.
+ SetState(CUTS_GenericHijacked);
+
+ LOG((LF_CORDB, LL_INFO1000000, "CUT::SGH: Current IP is 0x%08x\n", CORDbgGetIP(GetHijackCtx())));
+
+ DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets);
+
+ // Wack the IP over to our generic hijack function.
+ LPVOID holdIP = CORDbgGetIP(GetHijackCtx());
+ CORDbgSetIP(GetHijackCtx(), pRO->m_genericHijackFuncAddr);
+
+ LOG((LF_CORDB, LL_INFO1000000, "CUT::SGH: New IP is 0x%08x\n", CORDbgGetIP(GetHijackCtx())));
+
+ // We should never single step into the hijack
+ BOOL isSSFlagOn = IsSSFlagEnabled(GetHijackCtx());
+ if(isSSFlagOn)
+ {
+ UnsetSSFlag(GetHijackCtx());
+ }
+
+ succ = DbiSetThreadContext(m_handle, GetHijackCtx());
+
+ if (!succ)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::SGH: couldn't set thread context: %d\n", GetLastError()));
+
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+
+ // Put the original IP back into the local context copy for later.
+ CORDbgSetIP(GetHijackCtx(), holdIP);
+ // Set the original SS flag into the local context copy for later
+ if(isSSFlagOn)
+ {
+ SetSSFlag(GetHijackCtx());
+ }
+ return S_OK;
+}
+
+HRESULT CordbUnmanagedThread::FixupFromGenericHijack()
+{
+ LOG((LF_CORDB, LL_INFO1000, "CUT::FFGH: fixing up from generic hijack. Eip=0x%p, Esp=0x%p\n",
+ CORDbgGetIP(GetHijackCtx()), CORDbgGetSP(GetHijackCtx())));
+
+ // We're no longer hijacked
+ _ASSERTE(IsGenericHijacked());
+ ClearState(CUTS_GenericHijacked);
+
+ // Clear the exception so we do a DBG_CONTINUE with the original context. Note: we only do generic hijacks on
+ // in-band events.
+ IBEvent()->SetState(CUES_ExceptionCleared);
+
+ // Using the context we saved when the event came in originally or the new context if set by user,
+ // reset the thread as if it were never hijacked.
+ BOOL succ = DbiSetThreadContext(m_handle, GetHijackCtx());
+ // if the user set the context it has been applied now
+ ClearState(CUTS_HasContextSet);
+
+ if (!succ)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::FFGH: couldn't set thread context: %d\n", GetLastError()));
+
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+
+ return S_OK;
+}
+
+DT_CONTEXT * CordbUnmanagedThread::GetHijackCtx()
+{
+ return &m_context;
+}
+
+
+// Enable Single-Step (and bump the eip back one)
+// This can only be called after a bp. (because we assume that we executed a bp when we adjust the eip).
+HRESULT CordbUnmanagedThread::EnableSSAfterBP()
+{
+ DT_CONTEXT c;
+ c.ContextFlags = DT_CONTEXT_FULL;
+
+ BOOL succ = DbiGetThreadContext(m_handle, &c);
+
+ if (!succ)
+ return HRESULT_FROM_WIN32(GetLastError());
+
+ SetSSFlag(&c);
+
+ // Backup IP to point to the instruction we need to execute. Continuing from a breakpoint exception
+ // continues execution at the instruction after the breakpoint, but we need to continue where the
+ // breakpoint was.
+ CORDbgAdjustPCForBreakInstruction(&c);
+
+ succ = DbiSetThreadContext(m_handle, &c);
+
+ if (!succ)
+ {
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+
+ return S_OK;
+}
+
+//
+// FixupAfterOOBException automatically gets the debuggee past an OOB exception event. These are only BP or SS
+// events. For SS, we just clear it, assuming that the only reason the thread was stepped in such place was to get it
+// off of a BP. For a BP, we clear and backup the IP by one, and turn the trace flag on under the assumption that the
+// only thing a debugger is allowed to do with an OOB BP exception is to get us off of it.
+//
+HRESULT CordbUnmanagedThread::FixupAfterOOBException(CordbUnmanagedEvent *ue)
+{
+ // We really should only be doing things to single steps and breakpoint exceptions.
+ if (ue->m_currentDebugEvent.dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
+ {
+ DWORD ec = ue->m_currentDebugEvent.u.Exception.ExceptionRecord.ExceptionCode;
+
+ if ((ec == STATUS_BREAKPOINT) || (ec == STATUS_SINGLE_STEP))
+ {
+ // Automatically clear the exception.
+ ue->SetState(CUES_ExceptionCleared);
+
+ // Don't bother about toggling the single-step flag. OOB BPs should only be called
+ // for raw int3 instructions, so no need to rewind and reexecute.
+ }
+ }
+
+ return S_OK;
+}
+
+
+//-----------------------------------------------------------------------------
+// Setup to skip an native breakpoint
+//-----------------------------------------------------------------------------
+void CordbUnmanagedThread::SetupForSkipBreakpoint(NativePatch * pNativePatch)
+{
+ _ASSERTE(pNativePatch != NULL);
+ _ASSERTE(!IsSkippingNativePatch());
+ _ASSERTE(m_pPatchSkipAddress == NULL);
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ SetState(CUTS_SkippingNativePatch);
+
+#ifdef _DEBUG
+ // For debugging, provide a way that Cordbg devs can see if we're silently skipping BPs.
+ static DWORD fTrapOnSkip = -1;
+ if (fTrapOnSkip == -1)
+ fTrapOnSkip = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgTrapOnSkip);
+
+ if (fTrapOnSkip)
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("The CLR is skipping a native BP at %p on thread 0x%x (%d)."
+ "\nYou're getting this notification in debug builds b/c you have com+ var 'DbgTrapOnSkip' enabled.",
+ pNativePatch->pAddress, this->m_id, this->m_id));
+
+ // We skipped this BP b/c IsCantStop was true. For debugging convenience, call IsCantStop here
+ // (in case we break at the assert above and want to trace why we're in a CS region)
+ bool fCantStop = this->IsCantStop();
+ LOG((LF_CORDB, LL_INFO1000, "In Can'tStopRegion = %d\n", fCantStop));
+
+ // Refresh the reg key
+ fTrapOnSkip = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgTrapOnSkip);
+ }
+#endif
+#if defined(DBG_TARGET_X86)
+ STRESS_LOG2(LF_CORDB, LL_INFO100, "CUT::SetupSkip. adddr=%p. Opcode=%x\n", pNativePatch->pAddress, (DWORD) pNativePatch->opcode);
+#endif
+
+ // Replace the BP w/ the opcode.
+ RemoveRemotePatch(GetProcess(), pNativePatch->pAddress, pNativePatch->opcode);
+
+ // Enable the SS flag & Adjust IP.
+ HRESULT hr = this->EnableSSAfterBP();
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+
+
+ // Now we return,
+ // Process continues, LS will single step past BP, and fire a SS exception.
+ // When we get the SS, we res
+
+
+ // We need to remember this so we can make sure we fixup at the proper address.
+ // The address of a ss exception is the instruction we finish on, not where
+ // we originally placed the BP. Since instructions can be variable length,
+ // we can't work backwards.
+ m_pPatchSkipAddress = pNativePatch->pAddress;
+}
+
+//-----------------------------------------------------------------------------
+// Second half of skipping a native bp.
+// Note we pass the address in b/c our caller has (from the debug_evet), and
+// we don't want to waste storage to remember it ourselves.
+//-----------------------------------------------------------------------------
+void CordbUnmanagedThread::FixupForSkipBreakpoint()
+{
+ _ASSERTE(m_pPatchSkipAddress != NULL);
+ _ASSERTE(IsSkippingNativePatch());
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ ClearState(CUTS_SkippingNativePatch);
+
+ // Only reapply the int3 if it hasn't been removed yet.
+ if (GetProcess()->GetNativePatch(m_pPatchSkipAddress) != NULL)
+ {
+ ApplyRemotePatch(GetProcess(), m_pPatchSkipAddress);
+ STRESS_LOG1(LF_CORDB, LL_INFO100, "CUT::FixupSetupSkip. adddr=%p\n", m_pPatchSkipAddress);
+ }
+ else
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO100, "CUT::FixupSetupSkip. Patch removed. Not-readding. adddr=%p\n", m_pPatchSkipAddress);
+ }
+
+ m_pPatchSkipAddress = NULL;
+}
+
+inline TADDR GetSP(DT_CONTEXT* context)
+{
+#if defined(DBG_TARGET_X86)
+ return (TADDR)context->Esp;
+#elif defined(DBG_TARGET_AMD64)
+ return (TADDR)context->Rsp;
+#elif defined(DBG_TARGET_ARM) || defined(DBG_TARGET_ARM64)
+ return (TADDR)context->Sp;
+#else
+ _ASSERTE(!"nyi for platform");
+#endif
+}
+
+BOOL CordbUnmanagedThread::GetStackRange(CORDB_ADDRESS *pBase, CORDB_ADDRESS *pLimit)
+{
+#if !defined(FEATURE_DBGIPC_TRANSPORT)
+
+ if (m_stackBase == 0 && m_stackLimit == 0)
+ {
+ HANDLE hProc;
+ DT_CONTEXT tempContext;
+ MEMORY_BASIC_INFORMATION mbi;
+
+ tempContext.ContextFlags = DT_CONTEXT_FULL;
+ if (SUCCEEDED(GetProcess()->GetHandle(&hProc)) &&
+ SUCCEEDED(GetThreadContext(&tempContext)) &&
+ ::VirtualQueryEx(hProc, (LPCVOID)GetSP(&tempContext), &mbi, sizeof(mbi)) != 0)
+ {
+ // the lowest stack address is the AllocationBase
+ TADDR limit = PTR_TO_TADDR(mbi.AllocationBase);
+
+ // Now, on to find the stack base:
+ // Closest to the AllocationBase we might have a MEM_RESERVED block
+ // for all the as yet unallocated pages...
+ TADDR regionBase = limit;
+ if (::VirtualQueryEx(hProc, (LPCVOID) regionBase, &mbi, sizeof(mbi)) == 0
+ || mbi.Type != MEM_PRIVATE)
+ goto Exit;
+
+ if (mbi.State == MEM_RESERVE)
+ regionBase += mbi.RegionSize;
+
+ // Next we might have a few guard pages
+ if (::VirtualQueryEx(hProc, (LPCVOID) regionBase, &mbi, sizeof(mbi)) == 0
+ || mbi.Type != MEM_PRIVATE)
+ goto Exit;
+
+ if (mbi.State == MEM_COMMIT && (mbi.Protect & PAGE_GUARD) != 0)
+ regionBase += mbi.RegionSize;
+
+ // And finally the "regular" stack region
+ if (::VirtualQueryEx(hProc, (LPCVOID) regionBase, &mbi, sizeof(mbi)) == 0
+ || mbi.Type != MEM_PRIVATE)
+ goto Exit;
+
+ if (mbi.State == MEM_COMMIT && (mbi.Protect & PAGE_READWRITE) != 0)
+ regionBase += mbi.RegionSize;
+
+ if (limit == regionBase)
+ goto Exit;
+
+ m_stackLimit = limit;
+ m_stackBase = regionBase;
+ }
+ }
+
+Exit:
+ if (pBase != NULL)
+ *pBase = m_stackBase;
+ if (pLimit != NULL)
+ *pLimit = m_stackLimit;
+
+ return (m_stackBase != 0 || m_stackLimit != 0);
+
+#else
+
+ if (pBase != NULL)
+ *pBase = 0;
+ if (pLimit != NULL)
+ *pLimit = 0;
+
+ return FALSE;
+
+#endif // FEATURE_DBGIPC_TRANSPORT
+}
+
+//-----------------------------------------------------------------------------
+// Returns the thread context to the state it was in when it last entered RaiseException
+// This allows the thread to retrigger an exception caused by RaiseException
+//-----------------------------------------------------------------------------
+void CordbUnmanagedThread::HijackToRaiseException()
+{
+ LOG((LF_CORDB, LL_INFO1000, "CP::HTRE: hijacking to RaiseException\n"));
+ _ASSERTE(HasRaiseExceptionEntryCtx());
+ _ASSERTE(!IsRaiseExceptionHijacked());
+ _ASSERTE(!IsGenericHijacked());
+ _ASSERTE(!IsFirstChanceHijacked());
+ _ASSERTE(!IsContextSet());
+
+ BOOL succ = DbiGetThreadContext(m_handle, GetHijackCtx());
+ _ASSERTE(succ);
+ succ = DbiSetThreadContext(m_handle, &m_raiseExceptionEntryContext);
+ _ASSERTE(succ);
+ SetState(CUTS_IsRaiseExceptionHijacked);
+}
+
+//----------------------------------------------------------------------------
+// Returns the context to its unhijacked state.
+//----------------------------------------------------------------------------
+void CordbUnmanagedThread::RestoreFromRaiseExceptionHijack()
+{
+ LOG((LF_CORDB, LL_INFO1000, "CP::RFREH: ending RaiseException hijack\n"));
+ _ASSERTE(IsRaiseExceptionHijacked());
+
+ DT_CONTEXT restoreContext;
+ restoreContext.ContextFlags = DT_CONTEXT_FULL;
+ HRESULT hr = GetThreadContext(&restoreContext);
+ _ASSERTE(SUCCEEDED(hr));
+
+ ClearState(CUTS_IsRaiseExceptionHijacked);
+ hr = SetThreadContext(&restoreContext);
+ _ASSERTE(SUCCEEDED(hr));
+}
+
+//-----------------------------------------------------------------------------
+// Attempts to store the state of a thread currently entering RaiseException
+// This grabs both a full context and enough state to determine what exception
+// RaiseException should be raising. If any of the state can not be retrieved
+// then this entrance to RaiseException is silently ignored
+//-----------------------------------------------------------------------------
+void CordbUnmanagedThread::SaveRaiseExceptionEntryContext()
+{
+ _ASSERTE(FALSE); // should be unused now
+ LOG((LF_CORDB, LL_INFO1000, "CP::SREEC: saving raise exception context.\n"));
+ _ASSERTE(!HasRaiseExceptionEntryCtx());
+ _ASSERTE(!IsRaiseExceptionHijacked());
+ HRESULT hr = S_OK;
+ DT_CONTEXT context;
+ context.ContextFlags = DT_CONTEXT_FULL;
+ DbiGetThreadContext(m_handle, &context);
+ // if the flag is set, unset it
+ // we don't want to be single stepping through RaiseException the second time
+ // sending out OOB SS events. Ultimately we will rethrow the exception which would
+ // cleared the SS flag anyways.
+ UnsetSSFlag(&context);
+ memcpy(&m_raiseExceptionEntryContext, &context, sizeof(DT_CONTEXT));
+
+ // calculate the exception that we would expect to come from this invocation of RaiseException
+ REMOTE_PTR pExceptionInformation = NULL;
+#if defined(DBG_TARGET_AMD64)
+ m_raiseExceptionExceptionCode = (DWORD)m_raiseExceptionEntryContext.Rcx;
+ m_raiseExceptionExceptionFlags = (DWORD)m_raiseExceptionEntryContext.Rdx;
+ m_raiseExceptionNumberParameters = (DWORD)m_raiseExceptionEntryContext.R8;
+ pExceptionInformation = (REMOTE_PTR)m_raiseExceptionEntryContext.R9;
+#elif defined(DBG_TARGET_X86)
+ hr = m_pProcess->SafeReadStruct(PTR_TO_CORDB_ADDRESS((BYTE*)m_raiseExceptionEntryContext.Esp+4), &m_raiseExceptionExceptionCode);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::SREEC: failed to read exception code.\n"));
+ return;
+ }
+ hr = m_pProcess->SafeReadStruct(PTR_TO_CORDB_ADDRESS((BYTE*)m_raiseExceptionEntryContext.Esp+8), &m_raiseExceptionExceptionFlags);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::SREEC: failed to read exception flags.\n"));
+ return;
+ }
+ hr = m_pProcess->SafeReadStruct(PTR_TO_CORDB_ADDRESS((BYTE*)m_raiseExceptionEntryContext.Esp+12), &m_raiseExceptionNumberParameters);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::SREEC: failed to read number of parameters.\n"));
+ return;
+ }
+ hr = m_pProcess->SafeReadStruct(PTR_TO_CORDB_ADDRESS((BYTE*)m_raiseExceptionEntryContext.Esp+16), &pExceptionInformation);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::SREEC: failed to read exception information pointer.\n"));
+ return;
+ }
+#elif
+ _ASSERTE(!"Implement this for your platform");
+ return;
+#endif
+ LOG((LF_CORDB, LL_INFO1000, "CP::SREEC: RaiseException parameters are 0x%x 0x%x 0x%x 0x%p.\n",
+ m_raiseExceptionExceptionCode, m_raiseExceptionExceptionFlags,
+ m_raiseExceptionNumberParameters, pExceptionInformation));
+ TargetBuffer exceptionInfoTargetBuffer(pExceptionInformation, sizeof(REMOTE_PTR)*m_raiseExceptionNumberParameters);
+ EX_TRY
+ {
+ m_pProcess->SafeReadBuffer(exceptionInfoTargetBuffer, (BYTE*)m_raiseExceptionExceptionInformation);
+ }
+ EX_CATCH_HRESULT(hr);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::SREEC: failed to read exception information.\n"));
+ return;
+ }
+
+ // If everything was succesful then set this flag, otherwise none of the above data is considered valid
+ SetState(CUTS_HasRaiseExceptionEntryCtx);
+ return;
+}
+
+//-----------------------------------------------------------------------------
+// Clears all the state saved in SaveRaiseExceptionContext and returns the thread
+// to the state as if RaiseException has yet to be called. This is typically called
+// after an exception retriggers or after determining that the exception never will
+// retrigger.
+//-----------------------------------------------------------------------------
+void CordbUnmanagedThread::ClearRaiseExceptionEntryContext()
+{
+ _ASSERTE(FALSE); // should be unused now
+ LOG((LF_CORDB, LL_INFO1000, "CP::CREEC: clearing raise exception context.\n"));
+ _ASSERTE(HasRaiseExceptionEntryCtx());
+ ClearState(CUTS_HasRaiseExceptionEntryCtx);
+}
+
+//-----------------------------------------------------------------------------
+// Uses a heuristic to determine if the given exception record is likely to be the exception
+// raised by the last invocation of RaiseException on this thread. The current heuristic compares
+// ExceptionCode, ExceptionFlags, and all ExceptionInformation.
+//-----------------------------------------------------------------------------
+BOOL CordbUnmanagedThread::IsExceptionFromLastRaiseException(const EXCEPTION_RECORD* pExceptionRecord)
+{
+ _ASSERTE(FALSE); // should be unused now
+ if(!HasRaiseExceptionEntryCtx())
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::IEFLRE: not a match - no previous raise context\n"));
+ return FALSE;
+ }
+
+ if (pExceptionRecord->ExceptionCode != m_raiseExceptionExceptionCode)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::IEFLRE: not a match - exception codes differ 0x%x 0x%x\n",
+ pExceptionRecord->ExceptionCode, m_raiseExceptionExceptionCode));
+ return FALSE;
+ }
+
+ if (pExceptionRecord->ExceptionFlags != m_raiseExceptionExceptionFlags)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::IEFLRE: not a match - exception flags differ 0x%x 0x%x\n",
+ pExceptionRecord->ExceptionFlags, m_raiseExceptionExceptionFlags));
+ return FALSE;
+ }
+
+ if (pExceptionRecord->NumberParameters != m_raiseExceptionNumberParameters)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::IEFLRE: not a match - number parameters differ 0x%x 0x%x\n",
+ pExceptionRecord->NumberParameters, m_raiseExceptionNumberParameters));
+ return FALSE;
+ }
+
+ for(DWORD i = 0; i < pExceptionRecord->NumberParameters; i++)
+ {
+ if(m_raiseExceptionExceptionInformation[i] != pExceptionRecord->ExceptionInformation[i])
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::IEFLRE: not a match - param %d differs 0x%x 0x%x\n",
+ i, pExceptionRecord->ExceptionInformation[i], m_raiseExceptionExceptionInformation[i]));
+ return FALSE;
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::IEFLRE: match\n"));
+ return TRUE;
+}
+
+
+//-----------------------------------------------------------------------------
+// Inject an int3 at the given remote address
+//-----------------------------------------------------------------------------
+
+// This flavor is assuming our caller already knows the opcode.
+HRESULT ApplyRemotePatch(CordbProcess * pProcess, const void * pRemoteAddress)
+{
+#if defined(DBG_TARGET_X86) || defined(DBG_TARGET_AMD64)
+ const BYTE patch = CORDbg_BREAK_INSTRUCTION;
+ HRESULT hr = pProcess->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(pRemoteAddress), &patch);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+#else
+ PORTABILITY_ASSERT("NYI: ApplyRemotePatch for this platform");
+#endif
+ return S_OK;
+}
+
+
+// Get the opcode that we're replacing.
+HRESULT ApplyRemotePatch(CordbProcess * pProcess, const void * pRemoteAddress, PRD_TYPE * pOpcode)
+{
+#if defined(DBG_TARGET_X86) || defined(DBG_TARGET_AMD64)
+ // Read out opcode. 1 byte on x86
+ BYTE opcode;
+
+ HRESULT hr = pProcess->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pRemoteAddress), &opcode);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ *pOpcode = (PRD_TYPE) opcode;
+#else
+ PORTABILITY_ASSERT("NYI: ApplyRemotePatch for this platform");
+#endif
+ ApplyRemotePatch(pProcess, pRemoteAddress);
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Remove the int3 from the remote address
+//-----------------------------------------------------------------------------
+HRESULT RemoveRemotePatch(CordbProcess * pProcess, const void * pRemoteAddress, PRD_TYPE opcode)
+{
+#if defined(DBG_TARGET_X86) || defined(DBG_TARGET_AMD64)
+ // Replace the BP w/ the opcode.
+ BYTE opcode2 = (BYTE) opcode;
+
+ pProcess->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(pRemoteAddress), &opcode2);
+
+ // This may fail because the module has been unloaded. In which case, the patch is also
+ // gone so it makes sense to return success.
+#else
+ PORTABILITY_ASSERT("NYI: RemoveRemotePatch for this platform");
+#endif
+ return S_OK;
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+//---------------------------------------------------------------------------------------
+//
+// Simple helper to return the SP value stored in a DebuggerREGDISPLAY.
+//
+// Arguments:
+// pDRD - the DebuggerREGDISPLAY in question
+//
+// Return Value:
+// the SP value
+//
+
+inline CORDB_ADDRESS GetSPFromDebuggerREGDISPLAY(DebuggerREGDISPLAY* pDRD)
+{
+ return pDRD->SP;
+}
+
+
+HRESULT CordbContext::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugContext)
+ *pInterface = static_cast<ICorDebugContext*>(this);
+ else if (id == IID_IUnknown)
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugContext*>(this));
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+
+/* ------------------------------------------------------------------------- *
+ * Frame class
+ * ------------------------------------------------------------------------- */
+
+
+// This is just used as a proxy object to pass a FramePointer around.
+CordbFrame::CordbFrame(CordbProcess * pProcess, FramePointer fp)
+ : CordbBase(pProcess, 0, enumCordbFrame),
+ m_fp(fp)
+{
+ UnsafeNeuterDeadObject(); // mark as neutered.
+}
+
+
+CordbFrame::CordbFrame(CordbThread * pThread,
+ FramePointer fp,
+ SIZE_T ip,
+ CordbAppDomain * pCurrentAppDomain)
+ : CordbBase(pThread->GetProcess(), 0, enumCordbFrame),
+ m_ip(ip),
+ m_pThread(pThread),
+ m_currentAppDomain(pCurrentAppDomain),
+ m_fp(fp)
+{
+#ifdef _DEBUG
+ // For debugging purposes, track what Continue session these frames were created in.
+ m_DbgContinueCounter = GetProcess()->m_continueCounter;
+#endif
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ m_pThread->GetRefreshStackNeuterList()->Add(GetProcess(), this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+}
+
+
+CordbFrame::~CordbFrame()
+{
+ _ASSERTE(IsNeutered());
+}
+
+// Neutered by DerivedClasses
+void CordbFrame::Neuter()
+{
+ CordbBase::Neuter();
+}
+
+
+HRESULT CordbFrame::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugFrame)
+ *pInterface = static_cast<ICorDebugFrame*>(this);
+ else if (id == IID_IUnknown)
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugFrame*>(this));
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+// ----------------------------------------------------------------------------
+// CordbFrame::GetChain
+//
+// Description:
+// Return the owning chain. Since chains have been deprecated in Arrowhead,
+// this function returns E_NOTIMPL unless there is a shim.
+//
+// Arguments:
+// * ppChain - out parameter; return the owning chain
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if ppChain is NULL.
+// Return CORDBG_E_OBJECT_NEUTERED if the CordbFrame is neutered.
+// Return E_NOTIMPL if there is no shim.
+// Return E_FAIL if failed to find the chain
+//
+
+HRESULT CordbFrame::GetChain(ICorDebugChain **ppChain)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ ValidateOrThrow(ppChain);
+ *ppChain = NULL;
+
+ if (GetProcess()->GetShim() != NULL)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0(GetProcess(), GET_PUBLIC_LOCK_HOLDER());
+ ShimStackWalk * pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(m_pThread);
+ pSSW->GetChainForFrame(static_cast<ICorDebugFrame *>(this), ppChain);
+
+ if (*ppChain == NULL)
+ hr = E_FAIL;
+ }
+ else
+ {
+ // This is the Arrowhead case, where ICDChain has been deprecated.
+ hr = E_NOTIMPL;
+ }
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+
+// Return the stack range taken up by this frame.
+// Note that this is not implemented in the base CordbFrame class.
+// Instead, this is implemented by the derived classes.
+// The start of the stack range is the leafmost boundary, and the end is the rootmost boundary.
+//
+// Notes: see code:#GetStackRange
+HRESULT CordbFrame::GetStackRange(CORDB_ADDRESS *pStart, CORDB_ADDRESS *pEnd)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ ValidateOrThrow(pStart);
+ ValidateOrThrow(pEnd);
+
+ hr = E_NOTIMPL;
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+// Return the ICorDebugFunction associated with this frame.
+// There is one ICorDebugFunction for each EnC version of a method.
+HRESULT CordbFrame::GetFunction(ICorDebugFunction **ppFunction)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ ValidateOrThrow(ppFunction);
+
+ CordbFunction * pFunc = this->GetFunction();
+
+ if (pFunc == NULL)
+ {
+ ThrowHR(CORDBG_E_CODE_NOT_AVAILABLE);
+ }
+
+ // @dbgtodo LCG methods, IL stubs, dynamic language debugging
+ // Don't return an ICDFunction if we are dealing with a dynamic method.
+ // The dynamic debugging feature crew needs to decide exactly what to hand out for dynamic methods.
+ if (pFunc->GetMetadataToken() == mdMethodDefNil)
+ {
+ ThrowHR(CORDBG_E_CODE_NOT_AVAILABLE);
+ }
+
+ *ppFunction = static_cast<ICorDebugFunction *>(pFunc);
+ pFunc->ExternalAddRef();
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+// Return the token of the ICorDebugFunction associated with this frame.
+// There is one ICorDebugFunction for each EnC version of a method.
+HRESULT CordbFrame::GetFunctionToken(mdMethodDef *pToken)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ ValidateOrThrow(pToken);
+
+ CordbFunction * pFunc = GetFunction();
+ if (pFunc == NULL)
+ {
+ hr = CORDBG_E_CODE_NOT_AVAILABLE;
+ }
+ else
+ {
+ *pToken = pFunc->GetMetadataToken();
+ }
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+// ----------------------------------------------------------------------------
+// CordbFrame::GetCaller
+//
+// Description:
+// Return the caller of this frame. The caller is closer to the root.
+// This function has been deprecated in Arrowhead, and so it returns E_NOTIMPL unless there is a shim.
+//
+// Arguments:
+// * ppFrame - out parameter; return the caller frame
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if ppFrame is NULL.
+// Return CORDBG_E_OBJECT_NEUTERED if the CordbFrame is neutered.
+// Return E_NOTIMPL if there is no shim.
+//
+
+HRESULT CordbFrame::GetCaller(ICorDebugFrame **ppFrame)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ ValidateOrThrow(ppFrame);
+
+ *ppFrame = NULL;
+
+ if (GetProcess()->GetShim() != NULL)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0(GetProcess(), GET_PUBLIC_LOCK_HOLDER());
+ ShimStackWalk * pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(m_pThread);
+ pSSW->GetCallerForFrame(this, ppFrame);
+ }
+ else
+ {
+ *ppFrame = NULL;
+ hr = E_NOTIMPL;
+ }
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+// ----------------------------------------------------------------------------
+// CordbFrame::GetCallee
+//
+// Description:
+// Return the callee of this frame. The callee is closer to the leaf.
+// This function has been deprecated in Arrowhead, and so it returns E_NOTIMPL unless there is a shim.
+//
+// Arguments:
+// * ppFrame - out parameter; return the callee frame
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if ppFrame is NULL.
+// Return CORDBG_E_OBJECT_NEUTERED if the CordbFrame is neutered.
+// Return E_NOTIMPL if there is no shim.
+//
+
+HRESULT CordbFrame::GetCallee(ICorDebugFrame **ppFrame)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ ValidateOrThrow(ppFrame);
+
+ *ppFrame = NULL;
+
+ if (GetProcess()->GetShim() != NULL)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0(GetProcess(), GET_PUBLIC_LOCK_HOLDER());
+ ShimStackWalk * pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(m_pThread);
+ pSSW->GetCalleeForFrame(static_cast<ICorDebugFrame *>(this), ppFrame);
+ }
+ else
+ {
+ *ppFrame = NULL;
+ hr = E_NOTIMPL;
+ }
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+// Create a stepper on the frame.
+HRESULT CordbFrame::CreateStepper(ICorDebugStepper **ppStepper)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ VALIDATE_POINTER_TO_OBJECT(ppStepper, ICorDebugStepper **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ RSInitHolder<CordbStepper> pStepper(new CordbStepper(m_pThread, this));
+ pStepper.TransferOwnershipExternal(ppStepper);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Given a frame pointer, determine if it is in the stack range owned by the frame.
+//
+// Arguments:
+// fp - frame pointer to check
+//
+// Return Value:
+// whether the specified frame pointer is in the stack range or not
+//
+
+bool CordbFrame::IsContainedInFrame(FramePointer fp)
+{
+ CORDB_ADDRESS stackStart;
+ CORDB_ADDRESS stackEnd;
+
+ // get the stack range
+ HRESULT hr;
+ hr = GetStackRange(&stackStart, &stackEnd);
+ _ASSERTE(SUCCEEDED(hr));
+
+ CORDB_ADDRESS sp = PTR_TO_CORDB_ADDRESS(fp.GetSPValue());
+
+ if ((stackStart <= sp) && (sp <= stackEnd))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Given an ICorDebugFrame interface pointer, return a pointer to the base class CordbFrame.
+//
+// Arguments:
+// pFrame - the ICorDebugFrame interface pointer
+//
+// Return Value:
+// the CordbFrame pointer corresponding to the specified interface pointer
+//
+// Note:
+// This is currently only used for continuable exceptions.
+//
+
+// static
+CordbFrame* CordbFrame::GetCordbFrameFromInterface(ICorDebugFrame *pFrame)
+{
+ CordbFrame* pTargetFrame = NULL;
+
+ if (pFrame != NULL)
+ {
+ // test for CordbNativeFrame
+ RSExtSmartPtr<ICorDebugNativeFrame> pNativeFrame;
+ pFrame->QueryInterface(IID_ICorDebugNativeFrame, (void**)&pNativeFrame);
+ if (pNativeFrame != NULL)
+ {
+ pTargetFrame = static_cast<CordbFrame*>(static_cast<CordbNativeFrame*>(pNativeFrame.GetValue()));
+ }
+ else
+ {
+ // test for CordbJITILFrame
+ RSExtSmartPtr<ICorDebugILFrame> pILFrame;
+ pFrame->QueryInterface(IID_ICorDebugILFrame, (void**)&pILFrame);
+ if (pILFrame != NULL)
+ {
+ pTargetFrame = (static_cast<CordbJITILFrame*>(pILFrame.GetValue()))->m_nativeFrame;
+ }
+ else
+ {
+ // test for CordbInternalFrame
+ RSExtSmartPtr<ICorDebugInternalFrame> pInternalFrame;
+ pFrame->QueryInterface(IID_ICorDebugInternalFrame, (void**)&pInternalFrame);
+ if (pInternalFrame != NULL)
+ {
+ pTargetFrame = static_cast<CordbFrame*>(static_cast<CordbInternalFrame*>(pInternalFrame.GetValue()));
+ }
+ else
+ {
+ // when all else fails, this is just a CordbFrame
+ pTargetFrame = static_cast<CordbFrame*>(pFrame);
+ }
+ }
+ }
+ }
+ return pTargetFrame;
+}
+
+
+/* ------------------------------------------------------------------------- *
+
+ * Value Enumerator class
+ *
+ * Used by CordbJITILFrame for EnumLocalVars & EnumArgs.
+ * NOTE NOTE NOTE WE ASSUME that the 'frame' argument is actually the
+ * CordbJITILFrame's native frame member variable.
+ * ------------------------------------------------------------------------- */
+
+CordbValueEnum::CordbValueEnum(CordbNativeFrame *frame, ValueEnumMode mode) :
+ CordbBase(frame->GetProcess(), 0)
+{
+ _ASSERTE( frame != NULL );
+ _ASSERTE( mode == LOCAL_VARS_ORIGINAL_IL || mode == LOCAL_VARS_REJIT_IL || mode == ARGS);
+
+ m_frame = frame;
+ m_mode = mode;
+ m_iCurrent = 0;
+ m_iMax = 0;
+}
+
+/*
+ * CordbValueEnum::Init
+ *
+ * Initialize a CordbValueEnum object. Must be called after allocating the object and before using it. If Init
+ * fails, then destroy the object and release the memory.
+ *
+ * Parameters:
+ * none.
+ *
+ * Returns:
+ * HRESULT for success or failure.
+ *
+ */
+HRESULT CordbValueEnum::Init()
+{
+ HRESULT hr = S_OK;
+ CordbNativeFrame *nil = m_frame;
+ CordbJITILFrame *jil = nil->m_JITILFrame;
+
+ switch (m_mode)
+ {
+ case ARGS:
+ {
+ // Get the function signature
+ CordbFunction *func = m_frame->GetFunction();
+ ULONG methodArgCount;
+
+ IfFailRet(func->GetSig(NULL, &methodArgCount, NULL));
+
+ // Grab the argument count for the size of the enumeration.
+ m_iMax = methodArgCount;
+ if (jil->m_fVarArgFnx && !jil->m_sigParserCached.IsNull())
+ {
+ m_iMax = jil->m_allArgsCount;
+ }
+ break;
+ }
+ case LOCAL_VARS_ORIGINAL_IL:
+ {
+ // Get the locals signature.
+ ULONG localsCount;
+ IfFailRet(jil->GetOriginalILCode()->GetLocalVarSig(NULL, &localsCount));
+
+ // Grab the number of locals for the size of the enumeration.
+ m_iMax = localsCount;
+ break;
+ }
+ case LOCAL_VARS_REJIT_IL:
+ {
+ // Get the locals signature.
+ ULONG localsCount;
+ CordbReJitILCode* pCode = jil->GetReJitILCode();
+ if (pCode == NULL)
+ {
+ m_iMax = 0;
+ }
+ else
+ {
+ IfFailRet(pCode->GetLocalVarSig(NULL, &localsCount));
+
+ // Grab the number of locals for the size of the enumeration.
+ m_iMax = localsCount;
+ }
+ break;
+ }
+ }
+ // Everything worked okay, so add this object to the neuter list for objects that are tied to the stack trace.
+ EX_TRY
+ {
+ m_frame->m_pThread->GetRefreshStackNeuterList()->Add(GetProcess(), this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+
+ return hr;
+}
+
+CordbValueEnum::~CordbValueEnum()
+{
+ _ASSERTE(this->IsNeutered());
+ _ASSERTE(m_frame == NULL);
+}
+
+void CordbValueEnum::Neuter()
+{
+ m_frame = NULL;
+ CordbBase::Neuter();
+}
+
+
+
+HRESULT CordbValueEnum::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugEnum)
+ *pInterface = static_cast<ICorDebugEnum*>(this);
+ else if (id == IID_ICorDebugValueEnum)
+ *pInterface = static_cast<ICorDebugValueEnum*>(this);
+ else if (id == IID_IUnknown)
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugValueEnum*>(this));
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbValueEnum::Skip(ULONG celt)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = E_FAIL;
+ if ( (m_iCurrent+celt) < m_iMax ||
+ celt == 0)
+ {
+ m_iCurrent += celt;
+ hr = S_OK;
+ }
+
+ return hr;
+}
+
+HRESULT CordbValueEnum::Reset()
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ m_iCurrent = 0;
+ return S_OK;
+}
+
+HRESULT CordbValueEnum::Clone(ICorDebugEnum **ppEnum)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorDebugEnum **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ *ppEnum = NULL;
+ RSInitHolder<CordbValueEnum> pCVE(new CordbValueEnum(m_frame, m_mode));
+
+ // Initialize the new enum
+ hr = pCVE->Init();
+ IfFailThrow(hr);
+
+ pCVE.TransferOwnershipExternal(ppEnum);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbValueEnum::GetCount(ULONG *pcelt)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT(pcelt, ULONG *);
+
+ if( pcelt == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ (*pcelt) = m_iMax;
+ return S_OK;
+}
+
+//
+// In the event of failure, the current pointer will be left at
+// one element past the troublesome element. Thus, if one were
+// to repeatedly ask for one element to iterate through the
+// array, you would iterate exactly m_iMax times, regardless
+// of individual failures.
+HRESULT CordbValueEnum::Next(ULONG celt, ICorDebugValue *values[], ULONG *pceltFetched)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(values, ICorDebugValue *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched, ULONG *);
+
+ if ((pceltFetched == NULL) && (celt != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ if (celt == 0)
+ {
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = 0;
+ }
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+
+ int iMax = min( m_iMax, m_iCurrent+celt);
+ int i;
+ for (i = m_iCurrent; i< iMax;i++)
+ {
+ switch ( m_mode )
+ {
+ case ARGS:
+ {
+ hr = m_frame->m_JITILFrame->GetArgument( i, &(values[i-m_iCurrent]) );
+ break;
+ }
+ case LOCAL_VARS_ORIGINAL_IL:
+ {
+ hr = m_frame->m_JITILFrame->GetLocalVariableEx(ILCODE_ORIGINAL_IL, i, &(values[i-m_iCurrent]) );
+ break;
+ }
+ case LOCAL_VARS_REJIT_IL:
+ {
+ hr = m_frame->m_JITILFrame->GetLocalVariableEx(ILCODE_REJIT_IL, i, &(values[i - m_iCurrent]));
+ break;
+ }
+ }
+ if ( FAILED( hr ) )
+ {
+ break;
+ }
+ }
+
+ int count = (i - m_iCurrent);
+
+ if ( FAILED( hr ) )
+ {
+ //
+ // we failed: +1 pushes us past troublesome element
+ //
+ m_iCurrent += 1 + count;
+ }
+ else
+ {
+ m_iCurrent += count;
+ }
+
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = count;
+ }
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+
+ //
+ // If we reached the end of the enumeration, but not the end
+ // of the number of requested items, we return S_FALSE.
+ //
+ if (((ULONG)count) < celt)
+ {
+ return S_FALSE;
+ }
+
+ return hr;
+}
+
+
+//-----------------------------------------------------------------------------
+// CordbInternalFrame
+//-----------------------------------------------------------------------------
+CordbInternalFrame::CordbInternalFrame(CordbThread * pThread,
+ FramePointer fp,
+ CordbAppDomain * pCurrentAppDomain,
+ const DebuggerIPCE_STRData * pData)
+ : CordbFrame(pThread, fp, 0, pCurrentAppDomain)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ m_eFrameType = pData->stubFrame.frameType;
+ m_funcMetadataToken = pData->stubFrame.funcMetadataToken;
+ m_vmMethodDesc = pData->stubFrame.vmMethodDesc;
+
+ // Some internal frames may not have a Function associated w/ them.
+ if (!IsNilToken(m_funcMetadataToken))
+ {
+ // Find the module of the function. Note that this module isn't necessarily in the same domain as our frame.
+ // FuncEval frames can point to methods they are going to invoke in another domain.
+ CordbModule * pModule = NULL;
+ pModule = GetProcess()->LookupOrCreateModule(pData->stubFrame.vmDomainFile);
+ _ASSERTE(pModule != NULL);
+
+ //
+ if( pModule != NULL )
+ {
+ _ASSERTE( (pModule->GetAppDomain() == pCurrentAppDomain) || (m_eFrameType == STUBFRAME_FUNC_EVAL) );
+
+
+
+ mdMethodDef token = pData->stubFrame.funcMetadataToken;
+
+ // @dbgtodo synchronization - push this up.
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ // CordbInternalFrame could handle a null function.
+ // But if we fail to lookup, things are not in a good state anyways.
+ CordbFunction * pFunction = pModule->LookupOrCreateFunctionLatestVersion(token);
+ m_function.Assign(pFunction);
+
+ }
+ }
+}
+
+CordbInternalFrame::CordbInternalFrame(CordbThread * pThread,
+ FramePointer fp,
+ CordbAppDomain * pCurrentAppDomain,
+ CorDebugInternalFrameType frameType,
+ mdMethodDef funcMetadataToken,
+ CordbFunction * pFunction,
+ VMPTR_MethodDesc vmMethodDesc)
+ : CordbFrame(pThread, fp, 0, pCurrentAppDomain)
+{
+ m_eFrameType = frameType;
+ m_funcMetadataToken = funcMetadataToken;
+ m_function.Assign(pFunction);
+ m_vmMethodDesc = vmMethodDesc;
+}
+
+HRESULT CordbInternalFrame::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugFrame)
+ {
+ *pInterface = static_cast<ICorDebugFrame*>(static_cast<ICorDebugInternalFrame*>(this));
+ }
+ else if (id == IID_ICorDebugInternalFrame)
+ {
+ *pInterface = static_cast<ICorDebugInternalFrame*>(this);
+ }
+ else if (id == IID_ICorDebugInternalFrame2)
+ {
+ *pInterface = static_cast<ICorDebugInternalFrame2*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugInternalFrame*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+void CordbInternalFrame::Neuter()
+{
+ m_function.Clear();
+ CordbFrame::Neuter();
+}
+
+
+// ----------------------------------------------------------------------------
+// CordbInternalFrame::GetStackRange
+//
+// Description:
+// Return the stack range owned by this frame.
+// The start of the stack range is the leafmost boundary, and the end is the rootmost boundary.
+//
+// Arguments:
+// * pStart - out parameter; return the leaf end of the frame
+// * pEnd - out parameter; return the root end of the frame
+//
+// Return Value:
+// Return S_OK on success.
+//
+// Notes:
+// #GetStackRange
+// This is a virtual function and so there are multiple implementations for different types of frames.
+// It's very important to note that GetStackRange() can work when even after the frame is neutered.
+// Debuggers may rely on this to map old frames up to new frames across Continue() calls.
+//
+
+HRESULT CordbInternalFrame::GetStackRange(CORDB_ADDRESS *pStart,
+ CORDB_ADDRESS *pEnd)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+
+ // Callers explicit require GetStackRange() to be callable when neutered so that they
+ // can line up ICorDebugFrame objects across continues. We only return stack ranges
+ // here and don't access any special data.
+ OK_IF_NEUTERED(this);
+
+ if (GetProcess()->GetShim() != NULL)
+ {
+ CORDB_ADDRESS pFramePointer = PTR_TO_CORDB_ADDRESS(GetFramePointer().GetSPValue());
+ if (pStart)
+ {
+ *pStart = pFramePointer;
+ }
+ if (pEnd)
+ {
+ *pEnd = pFramePointer;
+ }
+ return S_OK;
+ }
+ else
+ {
+ if (pStart != NULL)
+ {
+ *pStart = NULL;
+ }
+ if (pEnd != NULL)
+ {
+ *pEnd = NULL;
+ }
+ return E_NOTIMPL;
+ }
+}
+
+
+// This may return NULL if there's no Method associated w/ this Frame.
+// For FuncEval frames, the function returned might also be in a different AppDomain
+// than the frame itself.
+CordbFunction * CordbInternalFrame::GetFunction()
+{
+ return m_function;
+}
+
+// Accessor for the shim private hook code:CordbThread::ConvertFrameForILMethodWithoutMetadata.
+// Refer to that function for comments on the return value, the argument, etc.
+BOOL CordbInternalFrame::ConvertInternalFrameForILMethodWithoutMetadata(
+ ICorDebugInternalFrame2 ** ppInternalFrame2)
+{
+ _ASSERTE(ppInternalFrame2 != NULL);
+ *ppInternalFrame2 = NULL;
+
+ // The only internal frame conversion we need to perform is from STUBFRAME_JIT_COMPILATION to
+ // STUBFRAME_LIGTHWEIGHT_FUNCTION.
+ if (m_eFrameType != STUBFRAME_JIT_COMPILATION)
+ {
+ return FALSE;
+ }
+
+ // Check whether the internal frame has an associated MethodDesc.
+ // Currently, the only STUBFRAME_JIT_COMPILATION frame with a NULL MethodDesc is ComPrestubMethodFrame,
+ // which is not exposed in Whidbey. So convert it according to rule #2 below.
+ if (m_vmMethodDesc.IsNull())
+ {
+ return TRUE;
+ }
+
+ // Retrieve the type of the method associated with the STUBFRAME_JIT_COMPILATION.
+ IDacDbiInterface::DynamicMethodType type = GetProcess()->GetDAC()->IsILStubOrLCGMethod(m_vmMethodDesc);
+
+ // Here are the conversion rules:
+ // 1) For a normal managed method, we don't convert, and we return FALSE.
+ // 2) For an IL stub, we convert to NULL, and we return TRUE.
+ // 3) For a dynamic method, we convert to a STUBFRAME_LIGHTWEIGHT_FUNCTION, and we return TRUE.
+ if (type == IDacDbiInterface::kNone)
+ {
+ return FALSE;
+ }
+ else if (type == IDacDbiInterface::kILStub)
+ {
+ return TRUE;
+ }
+ else if (type == IDacDbiInterface::kLCGMethod)
+ {
+ // Here we are basically cloning another CordbInternalFrame.
+ RSInitHolder<CordbInternalFrame> pInternalFrame(new CordbInternalFrame(m_pThread,
+ m_fp,
+ m_currentAppDomain,
+ STUBFRAME_LIGHTWEIGHT_FUNCTION,
+ m_funcMetadataToken,
+ m_function.GetValue(),
+ m_vmMethodDesc));
+ pInternalFrame.TransferOwnershipExternal(ppInternalFrame2);
+ return TRUE;
+ }
+
+ UNREACHABLE();
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Returns the address of an internal frame. The address is a stack pointer, even on IA64.
+//
+// Arguments:
+// pAddress - out parameter; return the frame marker address
+//
+// Return Value:
+// S_OK on success.
+// E_INVALIDARG if pAddress is NULL.
+//
+
+HRESULT CordbInternalFrame::GetAddress(CORDB_ADDRESS * pAddress)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (pAddress == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ *pAddress = PTR_TO_CORDB_ADDRESS(GetFramePointer().GetSPValue());
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Refer to the comment for code:CordbInternalFrame::IsCloserToLeaf
+//
+
+BOOL CordbInternalFrame::IsCloserToLeafWorker(ICorDebugFrame * pFrameToCompare)
+{
+ // Get the address of the "this" internal frame.
+ CORDB_ADDRESS thisFrameAddr = PTR_TO_CORDB_ADDRESS(this->GetFramePointer().GetSPValue());
+
+ // Note that a QI on ICorDebugJITILFrame for ICorDebugNativeFrame will work.
+ RSExtSmartPtr<ICorDebugNativeFrame> pNativeFrame;
+ pFrameToCompare->QueryInterface(IID_ICorDebugNativeFrame, (void **)&pNativeFrame);
+ if (pNativeFrame != NULL)
+ {
+ // The frame to compare is a CordbNativeFrame.
+ CordbNativeFrame * pCNativeFrame = static_cast<CordbNativeFrame *>(pNativeFrame.GetValue());
+
+ // Compare the address of the "this" internal frame to the SP of the stack frame.
+ // We can't compare frame pointers because the frame pointer means different things on
+ // different platforms.
+ CORDB_ADDRESS stackFrameSP = GetSPFromDebuggerREGDISPLAY(&(pCNativeFrame->m_rd));
+ return (thisFrameAddr < stackFrameSP);
+ }
+
+ RSExtSmartPtr<ICorDebugRuntimeUnwindableFrame> pRUFrame;
+ pFrameToCompare->QueryInterface(IID_ICorDebugRuntimeUnwindableFrame, (void **)&pRUFrame);
+ if (pRUFrame != NULL)
+ {
+ // The frame to compare is a CordbRuntimeUnwindableFrame.
+ CordbRuntimeUnwindableFrame * pCRUFrame =
+ static_cast<CordbRuntimeUnwindableFrame *>(pRUFrame.GetValue());
+
+ DT_CONTEXT * pResumeContext = const_cast<DT_CONTEXT *>(pCRUFrame->GetContext());
+ CORDB_ADDRESS stackFrameSP = PTR_TO_CORDB_ADDRESS(CORDbgGetSP(pResumeContext));
+ return (thisFrameAddr < stackFrameSP);
+ }
+
+ RSExtSmartPtr<ICorDebugInternalFrame> pInternalFrame;
+ pFrameToCompare->QueryInterface(IID_ICorDebugInternalFrame, (void **)&pInternalFrame);
+ if (pInternalFrame != NULL)
+ {
+ // The frame to compare is a CordbInternalFrame.
+ CordbInternalFrame * pCInternalFrame =
+ static_cast<CordbInternalFrame *>(pInternalFrame.GetValue());
+
+ CORDB_ADDRESS frameAddr = PTR_TO_CORDB_ADDRESS(pCInternalFrame->GetFramePointer().GetSPValue());
+ return (thisFrameAddr < frameAddr);
+ }
+
+ // What does this mean? This is unexpected.
+ _ASSERTE(!"CIF::ICTLW - Unexpected frame type.\n");
+ ThrowHR(E_FAIL);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Checks whether the "this" internal frame is closer to the leaf than the specified ICDFrame.
+// If the specified ICDFrame represents a stack frame, then we compare the address of the "this"
+// internal frame against the SP of the stack frame.
+//
+// Arguments:
+// pFrameToCompare - the ICDFrame to compare against
+// pIsCloser - out parameter; returns TRUE if the "this" internal frame is closer to the leaf
+//
+// Return Value:
+// S_OK on success.
+// E_INVALIDARG if pFrameToCompare or pIsCloser is NULL.
+// E_FAIL if pFrameToCompare is bogus.
+//
+// Notes:
+// This function doesn't deal with the backing store at all.
+//
+
+HRESULT CordbInternalFrame::IsCloserToLeaf(ICorDebugFrame * pFrameToCompare,
+ BOOL * pIsCloser)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this);
+ {
+ ValidateOrThrow(pFrameToCompare);
+ ValidateOrThrow(pIsCloser);
+
+ *pIsCloser = IsCloserToLeafWorker(pFrameToCompare);
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+
+CordbRuntimeUnwindableFrame::CordbRuntimeUnwindableFrame(CordbThread * pThread,
+ FramePointer fp,
+ CordbAppDomain * pCurrentAppDomain,
+ DT_CONTEXT * pContext)
+ : CordbFrame(pThread, fp, 0, pCurrentAppDomain),
+ m_context(*pContext)
+{
+}
+
+void CordbRuntimeUnwindableFrame::Neuter()
+{
+ CordbFrame::Neuter();
+}
+
+HRESULT CordbRuntimeUnwindableFrame::QueryInterface(REFIID id, void ** ppInterface)
+{
+ if (id == IID_ICorDebugFrame)
+ {
+ *ppInterface = static_cast<ICorDebugFrame *>(static_cast<ICorDebugRuntimeUnwindableFrame *>(this));
+ }
+ else if (id == IID_ICorDebugRuntimeUnwindableFrame)
+ {
+ *ppInterface = static_cast<ICorDebugRuntimeUnwindableFrame *>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *ppInterface = static_cast<IUnknown *>(static_cast<ICorDebugRuntimeUnwindableFrame *>(this));
+ }
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Returns the CONTEXT corresponding to this CordbRuntimeUnwindableFrame.
+//
+// Return Value:
+// Return a pointer to the CONTEXT.
+//
+
+const DT_CONTEXT * CordbRuntimeUnwindableFrame::GetContext() const
+{
+ return &m_context;
+}
+
+
+// default constructor to make the compiler happy
+CordbMiscFrame::CordbMiscFrame()
+{
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+ this->parentIP = 0;
+ this->fpParentOrSelf = LEAF_MOST_FRAME;
+ this->fIsFilterFunclet = false;
+#endif // _WIN64
+}
+
+// the real constructor which stores the funclet-related information in the CordbMiscFrame
+CordbMiscFrame::CordbMiscFrame(DebuggerIPCE_JITFuncData * pJITFuncData)
+{
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+ this->parentIP = pJITFuncData->parentNativeOffset;
+ this->fpParentOrSelf = pJITFuncData->fpParentOrSelf;
+ this->fIsFilterFunclet = (pJITFuncData->fIsFilterFrame == TRUE);
+#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM
+}
+
+/* ------------------------------------------------------------------------- *
+ * Native Frame class
+ * ------------------------------------------------------------------------- */
+
+
+CordbNativeFrame::CordbNativeFrame(CordbThread * pThread,
+ FramePointer fp,
+ CordbNativeCode * pNativeCode,
+ SIZE_T ip,
+ DebuggerREGDISPLAY * pDRD,
+ TADDR taAmbientESP,
+ bool fQuicklyUnwound,
+ CordbAppDomain * pCurrentAppDomain,
+ CordbMiscFrame * pMisc /*= NULL*/,
+ DT_CONTEXT * pContext /*= NULL*/)
+ : CordbFrame(pThread, fp, ip, pCurrentAppDomain),
+ m_rd(*pDRD),
+ m_quicklyUnwound(fQuicklyUnwound),
+ m_JITILFrame(NULL),
+ m_nativeCode(pNativeCode), // implicit InternalAddRef
+ m_taAmbientESP(taAmbientESP)
+{
+ m_misc = *pMisc;
+
+ // Only new CordbNativeFrames created by the new stackwalk contain a CONTEXT.
+ _ASSERTE(pContext != NULL);
+ m_context = *pContext;
+}
+
+/*
+ A list of which resources owned by this object are accounted for.
+
+ RESOLVED:
+ CordbJITILFrame* m_JITILFrame; // Neutered
+*/
+
+CordbNativeFrame::~CordbNativeFrame()
+{
+ _ASSERTE(IsNeutered());
+}
+
+// Neutered by CordbThread::CleanupStack
+void CordbNativeFrame::Neuter()
+{
+ // Neuter may be called multiple times so be sure to set ptrs to NULL so that we don't
+ // double release them.
+ if (IsNeutered())
+ {
+ return;
+ }
+
+ m_nativeCode.Clear();
+
+ if (m_JITILFrame != NULL)
+ {
+ m_JITILFrame->Neuter();
+ m_JITILFrame.Clear();
+ }
+
+ CordbFrame::Neuter();
+}
+
+// CordbNativeFrame::QueryInterface
+//
+// Description
+// interface query for this COM object
+//
+// NOTE: the COM object associated with this CordbNativeFrame may consist of
+// two C++ objects (the CordbNativeFrame and the CordbJITILFrame).
+//
+// Parameters
+// id the GUID associated with the requested interface
+// pInterface [out] the interface pointer
+//
+// Returns
+// HRESULT
+// S_OK If this CordbJITILFrame supports the interface
+// E_NOINTERFACE If this object does not support the interface
+//
+// Exceptions
+// None
+//
+//
+HRESULT CordbNativeFrame::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugFrame)
+ {
+ *pInterface = static_cast<ICorDebugFrame*>(static_cast<ICorDebugNativeFrame*>(this));
+ }
+ else if (id == IID_ICorDebugNativeFrame)
+ {
+ *pInterface = static_cast<ICorDebugNativeFrame*>(this);
+ }
+ else if (id == IID_ICorDebugNativeFrame2)
+ {
+ *pInterface = static_cast<ICorDebugNativeFrame2*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugNativeFrame*>(this));
+ }
+ else
+ {
+ // might be searching for an IL Frame. delegate that search to the
+ // JITILFrame
+ if (m_JITILFrame != NULL)
+ {
+ return m_JITILFrame->QueryInterfaceInternal(id, pInterface);
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+// Return the CordbNativeCode object associated with this native frame.
+// This is just a wrapper around the real helper.
+HRESULT CordbNativeFrame::GetCode(ICorDebugCode **ppCode)
+{
+ PUBLIC_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppCode, ICorDebugCode **);
+ FAIL_IF_NEUTERED(this);
+
+ CordbNativeCode * pCode = GetNativeCode();
+ *ppCode = static_cast<ICorDebugCode*> (pCode);
+ pCode->ExternalAddRef();
+
+ return S_OK;;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Returns the CONTEXT corresponding to this CordbNativeFrame.
+//
+// Return Value:
+// Return a pointer to the CONTEXT.
+//
+
+const DT_CONTEXT * CordbNativeFrame::GetContext() const
+{
+ return &m_context;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This is an internal helper to get the CordbNativeCode object associated with this native frame.
+//
+// Return Value:
+// the associated CordbNativeCode object
+//
+
+CordbNativeCode * CordbNativeFrame::GetNativeCode()
+{
+ return this->m_nativeCode;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This is an internal helper to get the CordbFunction object associated with this native frame.
+//
+// Return Value:
+// the associated CordbFunction object
+//
+
+CordbFunction *CordbNativeFrame::GetFunction()
+{
+ return this->m_nativeCode->GetFunction();
+}
+
+
+
+// Return the native offset.
+HRESULT CordbNativeFrame::GetIP(ULONG32 *pnOffset)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pnOffset, ULONG32 *);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ *pnOffset = (ULONG32)m_ip;
+
+ return S_OK;
+}
+
+ULONG32 CordbNativeFrame::GetIPOffset()
+{
+ return (ULONG32)m_ip;
+}
+
+TADDR CordbNativeFrame::GetReturnRegisterValue()
+{
+#if defined(DBG_TARGET_X86)
+ return (TADDR)m_context.Eax;
+#elif defined(DBG_TARGET_AMD64)
+ return (TADDR)m_context.Rax;
+#elif defined(DBG_TARGET_ARM)
+ return (TADDR)m_context.R0;
+#elif defined(DBG_TARGET_ARM64)
+ return (TADDR)m_context.X0;
+#else
+ _ASSERTE(!"nyi for platform");
+ return 0;
+#endif
+}
+
+// Determine if we can set IP at this point. The specified offset is the native offset.
+HRESULT CordbNativeFrame::CanSetIP(ULONG32 nOffset)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (!IsLeafFrame())
+ {
+ ThrowHR(CORDBG_E_SET_IP_NOT_ALLOWED_ON_NONLEAF_FRAME);
+ }
+
+ hr = m_pThread->SetIP(SetIP_fCanSetIPOnly,
+ m_nativeCode,
+ nOffset,
+ SetIP_fNative );
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+// Try to set the IP to the specified offset. The specified offset is the native offset.
+HRESULT CordbNativeFrame::SetIP(ULONG32 nOffset)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (!IsLeafFrame())
+ {
+ ThrowHR(CORDBG_E_SET_IP_NOT_ALLOWED_ON_NONLEAF_FRAME);
+ }
+
+ hr = m_pThread->SetIP(SetIP_fSetIP,
+ m_nativeCode,
+ nOffset,
+ SetIP_fNative );
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+
+// Given a (register,offset) description of a stack location, compute
+// the real memory address for it.
+// This will also handle ambient SP values (which are encoded with regNum == REGNUM_AMBIENT_SP).
+CORDB_ADDRESS CordbNativeFrame::GetLSStackAddress(
+ ICorDebugInfo::RegNum regNum,
+ signed offset)
+{
+ UINT_PTR *pRegAddr;
+
+ CORDB_ADDRESS pRemoteValue;
+
+ if (regNum != DBG_TARGET_REGNUM_AMBIENT_SP)
+ {
+ // Even if we're inside a funclet, variables (in both x64 and ARM) are still
+ // relative to the frame pointer or stack pointer, which are accurate in the
+ // funclet, after the funclet prolog; the frame pointer is re-established in the
+ // funclet prolog using the PSP. Thus, we just look up the frame pointer in the
+ // current native frame.
+
+ pRegAddr = this->GetAddressOfRegister(
+ ConvertRegNumToCorDebugRegister(regNum));
+
+ // This should never be null as long as regNum is a member of the RegNum enum.
+ // If it is, an AV dereferencing a null-pointer in retail builds, or an assert in debug
+ // builds is exactly the behavior we want.
+ PREFIX_ASSUME(pRegAddr != NULL);
+
+ pRemoteValue = PTR_TO_CORDB_ADDRESS(*pRegAddr + offset);
+ }
+ else
+ {
+ // Use the ambient ESP. At this point we're decoding an ambient-sp var, so
+ // we should definitely have an ambient-sp. If this is null, then the jit
+ // likely gave us an inconsistent data.
+ TADDR taAmbient = this->GetAmbientESP();
+ _ASSERTE(taAmbient != NULL);
+
+ pRemoteValue = PTR_TO_CORDB_ADDRESS(taAmbient + offset);
+ }
+
+ return pRemoteValue;
+}
+
+
+// ----------------------------------------------------------------------------
+// CordbNativeFrame::GetStackRange
+//
+// Description:
+// Return the stack range owned by this native frame.
+// The start of the stack range is the leafmost boundary, and the end is the rootmost boundary.
+//
+// Arguments:
+// * pStart - out parameter; return the leaf end of the frame
+// * pEnd - out parameter; return the root end of the frame
+//
+// Return Value:
+// Return S_OK on success.
+//
+// Notes: see code:#GetStackRange
+
+HRESULT CordbNativeFrame::GetStackRange(CORDB_ADDRESS *pStart,
+ CORDB_ADDRESS *pEnd)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+
+ // Callers explicit require GetStackRange() to be callable when neutered so that they
+ // can line up ICorDebugFrame objects across continues. We only return stack ranges
+ // here and don't access any special data.
+ OK_IF_NEUTERED(this);
+
+ if (GetProcess()->GetShim() != NULL)
+ {
+ if (pStart)
+ {
+ // From register set.
+ *pStart = GetSPFromDebuggerREGDISPLAY(&m_rd);
+ }
+
+ if (pEnd)
+ {
+ // The rootmost boundary is the frame pointer.
+ // <NOTE>
+ // This is not true on AMD64, on which we use the stack pointer as the frame pointer.
+ // </NOTE>
+ *pEnd = PTR_TO_CORDB_ADDRESS(GetFramePointer().GetSPValue());
+ }
+ return S_OK;
+ }
+ else
+ {
+ if (pStart != NULL)
+ {
+ *pStart = NULL;
+ }
+ if (pEnd != NULL)
+ {
+ *pEnd = NULL;
+ }
+ return E_NOTIMPL;
+ }
+}
+
+// Return the register set of the native frame.
+HRESULT CordbNativeFrame::GetRegisterSet(ICorDebugRegisterSet **ppRegisters)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT(ppRegisters, ICorDebugRegisterSet **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // allocate a new CordbRegisterSet object
+ RSInitHolder<CordbRegisterSet> pRegisterSet(new CordbRegisterSet(&m_rd,
+ m_pThread,
+ IsLeafFrame(),
+ m_quicklyUnwound));
+
+ pRegisterSet.TransferOwnershipExternal(ppRegisters);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Checks whether the frame is a child frame or not.
+//
+// Arguments:
+// pIsChild - out parameter; returns whether the frame is a child frame
+//
+// Return Value:
+// S_OK on success.
+// E_INVALIDARG if the out parmater is NULL.
+//
+
+HRESULT CordbNativeFrame::IsChild(BOOL * pIsChild)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ if (pIsChild == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+ else
+ {
+ *pIsChild = ((this->IsFunclet() && !this->IsFilterFunclet()) ? TRUE : FALSE);
+ }
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Given an ICDNativeFrame2, check whether it is the parent frame of the current frame.
+//
+// Arguments:
+// pPotentialParentFrame - the ICDNativeFrame2 to check
+// pIsParent - out paramter; returns whether the specified frame is indeed the parent frame
+//
+// Return Value:
+// S_OK on success.
+// CORDBG_E_NOT_CHILD_FRAME if the current frame is not a child frame.
+// E_INVALIDARG if either of the incoming argument is NULL.
+// E_FAIL on other failures.
+//
+
+HRESULT CordbNativeFrame::IsMatchingParentFrame(ICorDebugNativeFrame2 * pPotentialParentFrame,
+ BOOL * pIsParent)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pPotentialParentFrame, ICorDebugNativeFrame2 *);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if ((pPotentialParentFrame == NULL) || (pIsParent == NULL))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ *pIsParent = FALSE;
+
+ if (!this->IsFunclet())
+ {
+ ThrowHR(CORDBG_E_NOT_CHILD_FRAME);
+ }
+
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+ CordbNativeFrame * pFrameToCheck = static_cast<CordbNativeFrame *>(pPotentialParentFrame);
+ if (pFrameToCheck->IsFunclet())
+ {
+ *pIsParent = FALSE;
+ }
+ else
+ {
+ FramePointer fpParent = this->m_misc.fpParentOrSelf;
+ FramePointer fpToCheck = pFrameToCheck->m_misc.fpParentOrSelf;
+
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ *pIsParent = pDAC->IsMatchingParentFrame(fpToCheck, fpParent);
+ }
+#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Return the stack parameter size of the current frame. Since this information is only used on x86,
+// we return S_FALSE and a size of 0 on WIN64 platforms.
+//
+// Arguments:
+// pSize - out parameter; return the size of the stack parameter
+//
+// Return Value:
+// S_OK on success.
+// S_FALSE on WIN64 platforms.
+// E_INVALIDARG if pSize is NULL.
+//
+// Notes:
+// Always return S_FALSE on WIN64.
+//
+
+HRESULT CordbNativeFrame::GetStackParameterSize(ULONG32 * pSize)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (pSize == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+#if defined(_TARGET_X86_)
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ *pSize = pDAC->GetStackParameterSize(PTR_TO_CORDB_ADDRESS(CORDbgGetIP(&m_context)));
+#else // !_TARGET_X86_
+ hr = S_FALSE;
+ *pSize = 0;
+#endif // _TARGET_X86_
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+//
+// GetAddressOfRegister returns the address of the given register in the
+// frame's current register display (eg, a local address). This is usually used to build a
+// ICorDebugValue from.
+//
+UINT_PTR * CordbNativeFrame::GetAddressOfRegister(CorDebugRegister regNum) const
+{
+ UINT_PTR* ret = NULL;
+
+ switch (regNum)
+ {
+ case REGISTER_STACK_POINTER:
+ ret = (UINT_PTR*)GetSPAddress(&m_rd);
+ break;
+
+#if !defined(DBG_TARGET_AMD64) && !defined(DBG_TARGET_ARM) // @ARMTODO
+ case REGISTER_FRAME_POINTER:
+ ret = (UINT_PTR*)GetFPAddress(&m_rd);
+ break;
+#endif
+
+#if defined(DBG_TARGET_X86)
+ case REGISTER_X86_EAX:
+ ret = (UINT_PTR*)&m_rd.Eax;
+ break;
+
+ case REGISTER_X86_ECX:
+ ret = (UINT_PTR*)&m_rd.Ecx;
+ break;
+
+ case REGISTER_X86_EDX:
+ ret = (UINT_PTR*)&m_rd.Edx;
+ break;
+
+ case REGISTER_X86_EBX:
+ ret = (UINT_PTR*)&m_rd.Ebx;
+ break;
+
+ case REGISTER_X86_ESI:
+ ret = (UINT_PTR*)&m_rd.Esi;
+ break;
+
+ case REGISTER_X86_EDI:
+ ret = (UINT_PTR*)&m_rd.Edi;
+ break;
+
+#elif defined(DBG_TARGET_AMD64)
+ case REGISTER_AMD64_RBP:
+ ret = (UINT_PTR*)&m_rd.Rbp;
+ break;
+
+ case REGISTER_AMD64_RAX:
+ ret = (UINT_PTR*)&m_rd.Rax;
+ break;
+
+ case REGISTER_AMD64_RCX:
+ ret = (UINT_PTR*)&m_rd.Rcx;
+ break;
+
+ case REGISTER_AMD64_RDX:
+ ret = (UINT_PTR*)&m_rd.Rdx;
+ break;
+
+ case REGISTER_AMD64_RBX:
+ ret = (UINT_PTR*)&m_rd.Rbx;
+ break;
+
+ case REGISTER_AMD64_RSI:
+ ret = (UINT_PTR*)&m_rd.Rsi;
+ break;
+
+ case REGISTER_AMD64_RDI:
+ ret = (UINT_PTR*)&m_rd.Rdi;
+ break;
+
+ case REGISTER_AMD64_R8:
+ ret = (UINT_PTR*)&m_rd.R8;
+ break;
+
+ case REGISTER_AMD64_R9:
+ ret = (UINT_PTR*)&m_rd.R9;
+ break;
+
+ case REGISTER_AMD64_R10:
+ ret = (UINT_PTR*)&m_rd.R10;
+ break;
+
+ case REGISTER_AMD64_R11:
+ ret = (UINT_PTR*)&m_rd.R11;
+ break;
+
+ case REGISTER_AMD64_R12:
+ ret = (UINT_PTR*)&m_rd.R12;
+ break;
+
+ case REGISTER_AMD64_R13:
+ ret = (UINT_PTR*)&m_rd.R13;
+ break;
+
+ case REGISTER_AMD64_R14:
+ ret = (UINT_PTR*)&m_rd.R14;
+ break;
+
+ case REGISTER_AMD64_R15:
+ ret = (UINT_PTR*)&m_rd.R15;
+ break;
+#elif defined(DBG_TARGET_ARM)
+ case REGISTER_ARM_R0:
+ ret = (UINT_PTR*)&m_rd.R0;
+ break;
+
+ case REGISTER_ARM_R1:
+ ret = (UINT_PTR*)&m_rd.R1;
+ break;
+
+ case REGISTER_ARM_R2:
+ ret = (UINT_PTR*)&m_rd.R2;
+ break;
+
+ case REGISTER_ARM_R3:
+ ret = (UINT_PTR*)&m_rd.R3;
+ break;
+
+ case REGISTER_ARM_R4:
+ ret = (UINT_PTR*)&m_rd.R4;
+ break;
+
+ case REGISTER_ARM_R5:
+ ret = (UINT_PTR*)&m_rd.R5;
+ break;
+
+ case REGISTER_ARM_R6:
+ ret = (UINT_PTR*)&m_rd.R6;
+ break;
+
+ case REGISTER_ARM_R7:
+ ret = (UINT_PTR*)&m_rd.R7;
+ break;
+
+ case REGISTER_ARM_R8:
+ ret = (UINT_PTR*)&m_rd.R8;
+ break;
+
+ case REGISTER_ARM_R9:
+ ret = (UINT_PTR*)&m_rd.R9;
+ break;
+
+ case REGISTER_ARM_R10:
+ ret = (UINT_PTR*)&m_rd.R10;
+ break;
+
+ case REGISTER_ARM_R11:
+ ret = (UINT_PTR*)&m_rd.R11;
+ break;
+
+ case REGISTER_ARM_R12:
+ ret = (UINT_PTR*)&m_rd.R12;
+ break;
+
+ case REGISTER_ARM_LR:
+ ret = (UINT_PTR*)&m_rd.LR;
+ break;
+
+ case REGISTER_ARM_PC:
+ ret = (UINT_PTR*)&m_rd.PC;
+ break;
+#elif defined(DBG_TARGET_ARM64)
+ case REGISTER_ARM64_X0:
+ case REGISTER_ARM64_X1:
+ case REGISTER_ARM64_X2:
+ case REGISTER_ARM64_X3:
+ case REGISTER_ARM64_X4:
+ case REGISTER_ARM64_X5:
+ case REGISTER_ARM64_X6:
+ case REGISTER_ARM64_X7:
+ case REGISTER_ARM64_X8:
+ case REGISTER_ARM64_X9:
+ case REGISTER_ARM64_X10:
+ case REGISTER_ARM64_X11:
+ case REGISTER_ARM64_X12:
+ case REGISTER_ARM64_X13:
+ case REGISTER_ARM64_X14:
+ case REGISTER_ARM64_X15:
+ case REGISTER_ARM64_X16:
+ case REGISTER_ARM64_X17:
+ case REGISTER_ARM64_X18:
+ case REGISTER_ARM64_X19:
+ case REGISTER_ARM64_X20:
+ case REGISTER_ARM64_X21:
+ case REGISTER_ARM64_X22:
+ case REGISTER_ARM64_X23:
+ case REGISTER_ARM64_X24:
+ case REGISTER_ARM64_X25:
+ case REGISTER_ARM64_X26:
+ case REGISTER_ARM64_X27:
+ case REGISTER_ARM64_X28:
+ ret = (UINT_PTR*)&m_rd.X[regNum - REGISTER_ARM64_X0];
+ break;
+
+ case REGISTER_ARM64_LR:
+ ret = (UINT_PTR*)&m_rd.LR;
+ break;
+
+ case REGISTER_ARM64_PC:
+ ret = (UINT_PTR*)&m_rd.PC;
+ break;
+#endif
+
+ default:
+ _ASSERT(!"Invalid register number!");
+ }
+
+ return ret;
+}
+
+//
+// GetLeftSideAddressOfRegister returns the Left Side address of the given register in the frames current register
+// display.
+//
+CORDB_ADDRESS CordbNativeFrame::GetLeftSideAddressOfRegister(CorDebugRegister regNum) const
+{
+#if !defined(USE_REMOTE_REGISTER_ADDRESS)
+ // Use marker values as the register address. This is to implement the funceval breaking change.
+ //
+ if (IsLeafFrame())
+ {
+ return kLeafFrameRegAddr;
+ }
+ else
+ {
+ return kNonLeafFrameRegAddr;
+ }
+
+#else // USE_REMOTE_REGISTER_ADDRESS
+ void* ret = 0;
+
+ switch (regNum)
+ {
+
+#if !defined(DBG_TARGET_AMD64)
+ case REGISTER_FRAME_POINTER:
+ ret = m_rd.pFP;
+ break;
+#endif
+
+#if defined(DBG_TARGET_X86)
+ case REGISTER_X86_EAX:
+ ret = m_rd.pEax;
+ break;
+
+ case REGISTER_X86_ECX:
+ ret = m_rd.pEcx;
+ break;
+
+ case REGISTER_X86_EDX:
+ ret = m_rd.pEdx;
+ break;
+
+ case REGISTER_X86_EBX:
+ ret = m_rd.pEbx;
+ break;
+
+ case REGISTER_X86_ESI:
+ ret = m_rd.pEsi;
+ break;
+
+ case REGISTER_X86_EDI:
+ ret = m_rd.pEdi;
+ break;
+
+#elif defined(DBG_TARGET_AMD64)
+ case REGISTER_AMD64_RBP:
+ ret = m_rd.pRbp;
+ break;
+
+ case REGISTER_AMD64_RAX:
+ ret = m_rd.pRax;
+ break;
+
+ case REGISTER_AMD64_RCX:
+ ret = m_rd.pRcx;
+ break;
+
+ case REGISTER_AMD64_RDX:
+ ret = m_rd.pRdx;
+ break;
+
+ case REGISTER_AMD64_RBX:
+ ret = m_rd.pRbx;
+ break;
+
+ case REGISTER_AMD64_RSI:
+ ret = m_rd.pRsi;
+ break;
+
+ case REGISTER_AMD64_RDI:
+ ret = m_rd.pRdi;
+ break;
+
+ case REGISTER_AMD64_R8:
+ ret = m_rd.pR8;
+ break;
+
+ case REGISTER_AMD64_R9:
+ ret = m_rd.pR9;
+ break;
+
+ case REGISTER_AMD64_R10:
+ ret = m_rd.pR10;
+ break;
+
+ case REGISTER_AMD64_R11:
+ ret = m_rd.pR11;
+ break;
+
+ case REGISTER_AMD64_R12:
+ ret = m_rd.pR12;
+ break;
+
+ case REGISTER_AMD64_R13:
+ ret = m_rd.pR13;
+ break;
+
+ case REGISTER_AMD64_R14:
+ ret = m_rd.pR14;
+ break;
+
+ case REGISTER_AMD64_R15:
+ ret = m_rd.pR15;
+ break;
+#endif
+ default:
+ _ASSERT(!"Invalid register number!");
+ }
+
+ return PTR_TO_CORDB_ADDRESS(ret);
+#endif // !USE_REMOTE_REGISTER_ADDRESS
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Given the native variable information of a variable, return its value.
+//
+// Arguments:
+// pNativeVarInfo - the variable information of the variable to be retrieved
+//
+// Returns:
+// Return the specified value.
+// Throw on error.
+//
+// Assumption:
+// This function assumes that the value is either in a register or on the stack
+// (i.e. VLT_REG or VLT_STK).
+//
+// Notes:
+// Eventually we should make this more general-purpose.
+//
+
+SIZE_T CordbNativeFrame::GetRegisterOrStackValue(const ICorDebugInfo::NativeVarInfo * pNativeVarInfo)
+{
+ SIZE_T uResult;
+
+ if (pNativeVarInfo->loc.vlType == ICorDebugInfo::VLT_REG)
+ {
+ CorDebugRegister reg = ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlReg.vlrReg);
+ uResult = *(reinterpret_cast<SIZE_T *>(GetAddressOfRegister(reg)));
+ }
+ else if (pNativeVarInfo->loc.vlType == ICorDebugInfo::VLT_STK)
+ {
+ CORDB_ADDRESS remoteAddr = GetLSStackAddress(pNativeVarInfo->loc.vlStk.vlsBaseReg,
+ pNativeVarInfo->loc.vlStk.vlsOffset);
+
+ HRESULT hr = GetProcess()->SafeReadStruct(remoteAddr, &uResult);
+ IfFailThrow(hr);
+ }
+ else
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ return uResult;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Looks in a register and retrieves the value as a specific type, returning it
+// as an ICorDebugValue.
+//
+// Arguments:
+// reg - The register to use.
+// cbSigBlob - The number of bytes in the signature given.
+// pvSigBlob - A signature stream that describes the type of the value in the register.
+// ppValue - OUT: Space to store the resulting ICorDebugValue
+//
+// Returns:
+// S_OK on success, else an error code.
+//
+HRESULT CordbNativeFrame::GetLocalRegisterValue(CorDebugRegister reg,
+ ULONG cbSigBlob,
+ PCCOR_SIGNATURE pvSigBlob,
+ ICorDebugValue ** ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pvSigBlob, BYTE, cbSigBlob, true, false);
+
+ CordbType * pType;
+
+ SigParser sigParser(pvSigBlob, cbSigBlob);
+
+ Instantiation emptyInst;
+
+ HRESULT hr = CordbType::SigToType(m_JITILFrame->GetModule(), &sigParser, &emptyInst, &pType);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ return GetLocalRegisterValue(reg, pType, ppValue);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Looks in two registers and retrieves the value as a specific type, returning it
+// as an ICorDebugValue.
+//
+// Arguments:
+// highWordReg - The register to use for the high word.
+// lowWordReg - The register to use for the low word.
+// cbSigBlob - The number of bytes in the signature given.
+// pvSigBlob - A signature stream that describes the type of the value in the register.
+// ppValue - OUT: Space to store the resulting ICorDebugValue
+//
+// Returns:
+// S_OK on success, else an error code.
+//
+HRESULT CordbNativeFrame::GetLocalDoubleRegisterValue(CorDebugRegister highWordReg,
+ CorDebugRegister lowWordReg,
+ ULONG cbSigBlob,
+ PCCOR_SIGNATURE pvSigBlob,
+ ICorDebugValue ** ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (cbSigBlob == 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ CordbType * pType;
+
+ SigParser sigParser(pvSigBlob, cbSigBlob);
+
+ Instantiation emptyInst;
+
+ HRESULT hr = CordbType::SigToType(m_JITILFrame->GetModule(), &sigParser, &emptyInst, &pType);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ return GetLocalDoubleRegisterValue(highWordReg, lowWordReg, pType, ppValue);
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Uses an address and retrieves the value as a specific type, returning it
+// as an ICorDebugValue.
+//
+// Arguments:
+// address - A local memory address.
+// cbSigBlob - The number of bytes in the signature given.
+// pvSigBlob - A signature stream that describes the type of the value in the register.
+// ppValue - OUT: Space to store the resulting ICorDebugValue
+//
+// Returns:
+// S_OK on success, else an error code.
+//
+HRESULT CordbNativeFrame::GetLocalMemoryValue(CORDB_ADDRESS address,
+ ULONG cbSigBlob,
+ PCCOR_SIGNATURE pvSigBlob,
+ ICorDebugValue ** ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pvSigBlob, BYTE, cbSigBlob, true, false);
+
+ CordbType * pType;
+
+ SigParser sigParser(pvSigBlob, cbSigBlob);
+
+ Instantiation emptyInst;
+
+ HRESULT hr = CordbType::SigToType(m_JITILFrame->GetModule(), &sigParser, &emptyInst, &pType);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ return GetLocalMemoryValue(address, pType, ppValue);
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Uses a register and an address, retrieving the value as a specific type, returning it
+// as an ICorDebugValue.
+//
+// Arguments:
+// highWordReg - Register to use as the high word.
+// lowWordAddress - A local memory address containing the low word.
+// cbSigBlob - The number of bytes in the signature given.
+// pvSigBlob - A signature stream that describes the type of the value in the register.
+// ppValue - OUT: Space to store the resulting ICorDebugValue
+//
+// Returns:
+// S_OK on success, else an error code.
+//
+HRESULT CordbNativeFrame::GetLocalRegisterMemoryValue(CorDebugRegister highWordReg,
+ CORDB_ADDRESS lowWordAddress,
+ ULONG cbSigBlob,
+ PCCOR_SIGNATURE pvSigBlob,
+ ICorDebugValue ** ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (cbSigBlob == 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pvSigBlob, BYTE, cbSigBlob, true, true);
+
+ CordbType * pType;
+
+ SigParser sigParser(pvSigBlob, cbSigBlob);
+
+ Instantiation emptyInst;
+
+ HRESULT hr = CordbType::SigToType(m_JITILFrame->GetModule(), &sigParser, &emptyInst, &pType);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ return GetLocalRegisterMemoryValue(highWordReg, lowWordAddress, pType, ppValue);
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Uses a register and an address, retrieving the value as a specific type, returning it
+// as an ICorDebugValue.
+//
+// Arguments:
+// highWordReg - A local memory address to use as the high word.
+// lowWordAddress - Register containing the low word.
+// cbSigBlob - The number of bytes in the signature given.
+// pvSigBlob - A signature stream that describes the type of the value in the register.
+// ppValue - OUT: Space to store the resulting ICorDebugValue
+//
+// Returns:
+// S_OK on success, else an error code.
+//
+HRESULT CordbNativeFrame::GetLocalMemoryRegisterValue(CORDB_ADDRESS highWordAddress,
+ CorDebugRegister lowWordRegister,
+ ULONG cbSigBlob,
+ PCCOR_SIGNATURE pvSigBlob,
+ ICorDebugValue ** ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (cbSigBlob == 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pvSigBlob, BYTE, cbSigBlob, true, true);
+
+ CordbType * pType;
+
+ SigParser sigParser(pvSigBlob, cbSigBlob);
+
+ Instantiation emptyInst;
+
+ HRESULT hr = CordbType::SigToType(m_JITILFrame->GetModule(), &sigParser, &emptyInst, &pType);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ return GetLocalMemoryRegisterValue(highWordAddress, lowWordRegister, pType, ppValue);
+}
+
+
+
+HRESULT CordbNativeFrame::GetLocalRegisterValue(CorDebugRegister reg,
+ CordbType * pType,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+#if defined(DBG_TARGET_X86) || defined(DBG_TARGET_WIN64)
+#if defined(DBG_TARGET_X86)
+ if ((reg >= REGISTER_X86_FPSTACK_0) && (reg <= REGISTER_X86_FPSTACK_7))
+#elif defined(DBG_TARGET_AMD64)
+ if ((reg >= REGISTER_AMD64_XMM0) && (reg <= REGISTER_AMD64_XMM15))
+#elif defined(DBG_TARGET_ARM64)
+ if ((reg >= REGISTER_ARM64_V0) && (reg <= REGISTER_ARM64_V31))
+#endif
+ {
+ return GetLocalFloatingPointValue(reg, pType, ppValue);
+ }
+#endif
+
+ // The address of the given register is the address of the value
+ // in this process. We have no remote address here.
+ void *pLocalValue = (void*)GetAddressOfRegister(reg);
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ // Provide the register info as we create the value. CreateValueByType will transfer ownership of this to
+ // the new instance of CordbValue.
+ EnregisteredValueHomeHolder pRemoteReg(new RegValueHome(this, reg));
+ EnregisteredValueHomeHolder * pRegHolder = pRemoteReg.GetAddr();
+
+ ICorDebugValue *pValue;
+ CordbValue::CreateValueByType(GetCurrentAppDomain(),
+ pType,
+ false,
+ EMPTY_BUFFER,
+ MemoryRange(pLocalValue, REG_SIZE),
+ pRegHolder,
+ &pValue); // throws
+
+ *ppValue = pValue;
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT CordbNativeFrame::GetLocalDoubleRegisterValue(
+ CorDebugRegister highWordReg,
+ CorDebugRegister lowWordReg,
+ CordbType * pType,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Provide the register info as we create the value. CreateValueByType will transfer ownership of this to
+ // the new instance of CordbValue.
+ EnregisteredValueHomeHolder pRemoteReg(new RegRegValueHome(this, highWordReg, lowWordReg));
+ EnregisteredValueHomeHolder * pRegHolder = pRemoteReg.GetAddr();
+
+ CordbValue::CreateValueByType(GetCurrentAppDomain(),
+ pType,
+ false,
+ EMPTY_BUFFER,
+ MemoryRange(NULL, 0),
+ pRegHolder,
+ ppValue); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+
+#ifdef _DEBUG
+ {
+ // sanity check object size
+ if (SUCCEEDED(hr))
+ {
+ ULONG32 objectSize;
+ hr = (*ppValue)->GetSize(&objectSize);
+ _ASSERTE(SUCCEEDED(hr));
+ //
+ // nickbe
+ // 10/31/2002 11:09:42
+ //
+ // This assert assumes that the JIT will only partially enregister
+ // objects that have a size equal to twice the size of a register.
+ //
+ _ASSERTE(objectSize == 2 * sizeof(void*));
+ }
+ }
+#endif
+ return hr;
+}
+
+HRESULT
+CordbNativeFrame::GetLocalMemoryValue(CORDB_ADDRESS address,
+ CordbType * pType,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ _ASSERTE(m_nativeCode->GetFunction() != NULL);
+ HRESULT hr = S_OK;
+
+ ICorDebugValue *pValue;
+ EX_TRY
+ {
+ CordbValue::CreateValueByType(GetCurrentAppDomain(),
+ pType,
+ false,
+ TargetBuffer(address, CordbValue::GetSizeForType(pType, kUnboxed)),
+ MemoryRange(NULL, 0),
+ NULL,
+ &pValue); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (SUCCEEDED(hr))
+ *ppValue = pValue;
+
+ return hr;
+}
+
+HRESULT
+CordbNativeFrame::GetLocalByRefMemoryValue(CORDB_ADDRESS address,
+ CordbType * pType,
+ ICorDebugValue **ppValue)
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ LPVOID actualAddress = NULL;
+ HRESULT hr = GetProcess()->SafeReadStruct(address, &actualAddress);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ return GetLocalMemoryValue(PTR_TO_CORDB_ADDRESS(actualAddress), pType, ppValue);
+}
+
+HRESULT
+CordbNativeFrame::GetLocalRegisterMemoryValue(CorDebugRegister highWordReg,
+ CORDB_ADDRESS lowWordAddress,
+ CordbType * pType,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Provide the register info as we create the value. CreateValueByType will transfer ownership of this to
+ // the new instance of CordbValue.
+ EnregisteredValueHomeHolder pRemoteReg(new RegMemValueHome(this,
+ highWordReg,
+ lowWordAddress));
+ EnregisteredValueHomeHolder * pRegHolder = pRemoteReg.GetAddr();
+
+ CordbValue::CreateValueByType(GetCurrentAppDomain(),
+ pType,
+ false,
+ EMPTY_BUFFER,
+ MemoryRange(NULL, 0),
+ pRegHolder,
+ ppValue); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+
+#ifdef _DEBUG
+ {
+ if (SUCCEEDED(hr))
+ {
+ ULONG32 objectSize;
+ hr = (*ppValue)->GetSize(&objectSize);
+ _ASSERTE(SUCCEEDED(hr));
+ // See the comment in CordbNativeFrame::GetLocalDoubleRegisterValue
+ // for more information on this assertion
+ _ASSERTE(objectSize == 2 * sizeof(void*));
+ }
+ }
+#endif
+ return hr;
+}
+
+HRESULT
+CordbNativeFrame::GetLocalMemoryRegisterValue(CORDB_ADDRESS highWordAddress,
+ CorDebugRegister lowWordRegister,
+ CordbType * pType,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Provide the register info as we create the value. CreateValueByType will transfer ownership of this to
+ // the new instance of CordbValue.
+ EnregisteredValueHomeHolder pRemoteReg(new MemRegValueHome(this,
+ lowWordRegister,
+ highWordAddress));
+ EnregisteredValueHomeHolder * pRegHolder = pRemoteReg.GetAddr();
+
+ CordbValue::CreateValueByType(GetCurrentAppDomain(),
+ pType,
+ false,
+ EMPTY_BUFFER,
+ MemoryRange(NULL, 0),
+ pRegHolder,
+ ppValue); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+
+#ifdef _DEBUG
+ {
+ if (SUCCEEDED(hr))
+ {
+ ULONG32 objectSize;
+ hr = (*ppValue)->GetSize(&objectSize);
+ _ASSERTE(SUCCEEDED(hr));
+ // See the comment in CordbNativeFrame::GetLocalDoubleRegisterValue
+ // for more information on this assertion
+ _ASSERTE(objectSize == 2 * sizeof(void*));
+ }
+ }
+#endif
+ return hr;
+}
+
+#if !defined(DBG_TARGET_ARM) // @ARMTODO
+HRESULT CordbNativeFrame::GetLocalFloatingPointValue(DWORD index,
+ CordbType * pType,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ HRESULT hr = S_OK;
+
+ CorElementType et = pType->m_elementType;
+
+ if ((et != ELEMENT_TYPE_R4) &&
+ (et != ELEMENT_TYPE_R8))
+ return E_INVALIDARG;
+
+#if defined(DBG_TARGET_AMD64)
+ if (!((index >= REGISTER_AMD64_XMM0) &&
+ (index <= REGISTER_AMD64_XMM15)))
+ return E_INVALIDARG;
+ index -= REGISTER_AMD64_XMM0;
+#elif defined(DBG_TARGET_ARM64)
+ if (!((index >= REGISTER_ARM64_V0) &&
+ (index <= REGISTER_ARM64_V31)))
+ return E_INVALIDARG;
+ index -= REGISTER_ARM64_V0;
+#else
+ if (!((index >= REGISTER_X86_FPSTACK_0) &&
+ (index <= REGISTER_X86_FPSTACK_7)))
+ return E_INVALIDARG;
+ index -= REGISTER_X86_FPSTACK_0;
+#endif
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+
+ // Make sure the thread's floating point stack state is loaded
+ // over from the left side.
+ //
+ CordbThread *pThread = m_pThread;
+
+ EX_TRY
+ {
+ if (!pThread->m_fFloatStateValid)
+ {
+ pThread->LoadFloatState();
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ if (SUCCEEDED(hr))
+ {
+#if !defined(DBG_TARGET_WIN64)
+ // This is needed on x86 because we are dealing with a stack.
+ index = pThread->m_floatStackTop - index;
+#endif
+
+ if (index >= (sizeof(pThread->m_floatValues) /
+ sizeof(pThread->m_floatValues[0])))
+ return E_INVALIDARG;
+
+#ifdef DBG_TARGET_X86
+ // A workaround (sort of) to get around the difference in format between
+ // a float value and a double value. We can't simply cast a double pointer to
+ // a float pointer. Instead, we have to cast the double itself to a float.
+ if (pType->m_elementType == ELEMENT_TYPE_R4)
+ *(float *)&(pThread->m_floatValues[index]) = (float)pThread->m_floatValues[index];
+#endif
+
+ ICorDebugValue* pValue;
+
+ EX_TRY
+ {
+ // Provide the register info as we create the value. CreateValueByType will transfer ownership of this to
+ // the new instance of CordbValue.
+ EnregisteredValueHomeHolder pRemoteReg(new FloatRegValueHome(this, index));
+ EnregisteredValueHomeHolder * pRegHolder = pRemoteReg.GetAddr();
+
+ CordbValue::CreateValueByType(GetCurrentAppDomain(),
+ pType,
+ false,
+ EMPTY_BUFFER,
+ MemoryRange(&(pThread->m_floatValues[index]), sizeof(double)),
+ pRegHolder,
+ &pValue); // throws
+
+ *ppValue = pValue;
+ }
+ EX_CATCH_HRESULT(hr);
+
+ }
+
+ return hr;
+}
+#endif // !DBG_TARGET_ARM @ARMTODO
+
+//---------------------------------------------------------------------------------------
+//
+// Quick accessor to tell if we're the leaf frame.
+//
+// Return Value:
+// whether we are the leaf frame or not
+//
+
+bool CordbNativeFrame::IsLeafFrame() const
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ // Should only be called by non-neutered stuff.
+ // Also, since we're not neutered, we know we have a Thread object, and we know it's state is current.
+ _ASSERTE(!this->IsNeutered());
+
+ // If the thread's state is sleeping, then there's no frame below us, but we're actually
+ // not the leaf frame.
+ // @todo- consider having Sleep / Wait / Join be an ICDInternalFrame.
+ _ASSERTE(m_pThread != NULL); // not neutered, so should have a thread
+ if (m_pThread->IsThreadWaitingOrSleeping())
+ {
+ return false;
+ }
+
+ if (!m_optfIsLeafFrame.HasValue())
+ {
+ if (GetProcess()->GetShim() != NULL)
+ {
+ // In V2, the definition of "leaf frame" is the leaf frame in the leaf chain in the stackwalk.
+ PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(GetProcess());
+ ShimStackWalk * pSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(m_pThread);
+
+ // check if there is any chain
+ if (pSW->GetChainCount() > 0)
+ {
+ // check if the leaf chain has any frame
+ if (pSW->GetChain(0)->GetLastFrameIndex() > 0)
+ {
+ CordbFrame * pCFrame = GetCordbFrameFromInterface(pSW->GetFrame(0));
+ CordbNativeFrame * pNFrame = pCFrame->GetAsNativeFrame();
+ if (pNFrame != NULL)
+ {
+ // check if the leaf frame in the leaf chain is "this"
+ if (CompareControlRegisters(GetContext(), pNFrame->GetContext()))
+ {
+ m_optfIsLeafFrame = TRUE;
+ }
+ }
+ }
+ }
+
+ if (!m_optfIsLeafFrame.HasValue())
+ {
+ m_optfIsLeafFrame = FALSE;
+ }
+ }
+ else
+ {
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ m_optfIsLeafFrame = (pDAC->IsLeafFrame(m_pThread->m_vmThreadToken, &m_context) == TRUE);
+ }
+ }
+ return m_optfIsLeafFrame.GetValue();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Get the offset used to determine if a variable is live in a particular method frame.
+//
+// Return Value:
+// the offset used for inspection purposes
+//
+// Notes:
+// On WIN64, variables used in funclets are always homed on the stack. Morever, the variable lifetime
+// information only covers the parent method. The idea is that the variables which are live in a funclet
+// will be the variables which are live in the parent method at the offset at which the exception occurs.
+// Thus, to determine if a variable is live in a funclet frame, we need to use the offset of the parent
+// method frame at which the exception occurs.
+//
+
+SIZE_T CordbNativeFrame::GetInspectionIP()
+{
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+ // On 64-bit, if this is a funclet, then return the offset of the parent method frame at which
+ // the exception occurs. Otherwise just return the normal offset.
+ return (IsFunclet() ? GetParentIP() : m_ip);
+#else
+ // Always return the normal offset on all other platforms.
+ return m_ip;
+#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Return whether this is a funclet method frame.
+//
+// Return Value:
+// whether this is a funclet method frame.
+//
+
+bool CordbNativeFrame::IsFunclet()
+{
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+ return (m_misc.parentIP != NULL);
+#else // !DBG_TARGET_WIN64 && !DBG_TARGET_ARM
+ return false;
+#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Return whether this is a filter funclet method frame.
+//
+// Return Value:
+// whether this is a filter funclet method frame.
+//
+
+bool CordbNativeFrame::IsFilterFunclet()
+{
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+ return (IsFunclet() && m_misc.fIsFilterFunclet);
+#else // !DBG_TARGET_WIN64 && !DBG_TARGET_ARM
+ return false;
+#endif // DBG_TARGET_WIN64
+}
+
+
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+//---------------------------------------------------------------------------------------
+//
+// Return the offset of the parent method frame at which the exception occurs.
+//
+// Return Value:
+// the offset of the parent method frame at which the exception occurs
+//
+
+SIZE_T CordbNativeFrame::GetParentIP()
+{
+ return m_misc.parentIP;
+}
+#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM
+
+// Accessor for the shim private hook code:CordbThread::ConvertFrameForILMethodWithoutMetadata.
+// Refer to that function for comments on the return value, the argument, etc.
+BOOL CordbNativeFrame::ConvertNativeFrameForILMethodWithoutMetadata(
+ ICorDebugInternalFrame2 ** ppInternalFrame2)
+{
+ _ASSERTE(ppInternalFrame2 != NULL);
+ *ppInternalFrame2 = NULL;
+
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ IDacDbiInterface::DynamicMethodType type =
+ pDAC->IsILStubOrLCGMethod(GetNativeCode()->GetVMNativeCodeMethodDescToken());
+
+ // Here are the conversion rules:
+ // 1) For a normal managed method, we don't convert, and we return FALSE.
+ // 2) For an IL stub, we convert to NULL, and we return TRUE.
+ // 3) For a dynamic method, we convert to a STUBFRAME_LIGHTWEIGHT_FUNCTION, and we return TRUE.
+ if (type == IDacDbiInterface::kNone)
+ {
+ return FALSE;
+ }
+ else if (type == IDacDbiInterface::kILStub)
+ {
+ return TRUE;
+ }
+ else if (type == IDacDbiInterface::kLCGMethod)
+ {
+ RSInitHolder<CordbInternalFrame> pInternalFrame(
+ new CordbInternalFrame(m_pThread,
+ m_fp,
+ m_currentAppDomain,
+ STUBFRAME_LIGHTWEIGHT_FUNCTION,
+ GetNativeCode()->GetMetadataToken(),
+ GetNativeCode()->GetFunction(),
+ GetNativeCode()->GetVMNativeCodeMethodDescToken()));
+
+ pInternalFrame.TransferOwnershipExternal(ppInternalFrame2);
+ return TRUE;
+ }
+
+ UNREACHABLE();
+}
+
+/* ------------------------------------------------------------------------- *
+ * JIT-IL Frame class
+ * ------------------------------------------------------------------------- */
+
+CordbJITILFrame::CordbJITILFrame(CordbNativeFrame * pNativeFrame,
+ CordbILCode * pCode,
+ UINT_PTR ip,
+ CorDebugMappingResult mapping,
+ GENERICS_TYPE_TOKEN exactGenericArgsToken,
+ DWORD dwExactGenericArgsTokenIndex,
+ bool fVarArgFnx,
+ CordbReJitILCode * pRejitCode)
+ : CordbBase(pNativeFrame->GetProcess(), 0, enumCordbJITILFrame),
+ m_nativeFrame(pNativeFrame),
+ m_ilCode(pCode),
+ m_ip(ip),
+ m_mapping(mapping),
+ m_fVarArgFnx(fVarArgFnx),
+ m_allArgsCount(0),
+ m_rgbSigParserBuf(NULL),
+ m_FirstArgAddr(NULL),
+ m_rgNVI(NULL),
+ m_genericArgs(),
+ m_genericArgsLoaded(false),
+ m_frameParamsToken(exactGenericArgsToken),
+ m_dwFrameParamsTokenIndex(dwExactGenericArgsTokenIndex),
+ m_pReJitCode(pRejitCode)
+{
+ // We'll initialize the SigParser in CordbJITILFrame::Init().
+ m_sigParserCached = SigParser(NULL, 0);
+ _ASSERTE(m_sigParserCached.IsNull());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ m_nativeFrame->m_pThread->GetRefreshStackNeuterList()->Add(GetProcess(), this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Initialize a CordbJITILFrame object. Must be called after allocating the object and before using it.
+// If Init fails, then destroy the object and release the memory.
+//
+// Return Value:
+// HRESULT for the operation
+//
+// Notes:
+// This is a nop if the function is not a vararg function.
+//
+
+HRESULT CordbJITILFrame::Init()
+{
+ // ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ HRESULT hr = S_OK;
+
+
+ EX_TRY
+ {
+ _ASSERTE(m_ilCode != NULL);
+
+ if (m_fVarArgFnx)
+ {
+ // First, we need to find the VASigCookie. Use the native var info to do so.
+ const ICorDebugInfo::NativeVarInfo * pNativeVarInfo = NULL;
+ CordbNativeFrame * pNativeFrame = this->m_nativeFrame;
+
+ pNativeFrame->m_nativeCode->LoadNativeInfo();
+ hr = pNativeFrame->m_nativeCode->ILVariableToNative((DWORD)ICorDebugInfo::VARARGS_HND_ILNUM,
+ pNativeFrame->GetInspectionIP(),
+ &pNativeVarInfo);
+ IfFailThrow(hr);
+
+ // Check for the case where the VASigCookie isn't pushed on the stack yet.
+ // This should only be a problem with optimized code.
+ if (pNativeVarInfo->loc.vlType != ICorDebugInfo::VLT_STK)
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ // Retrieve the target address.
+ CORDB_ADDRESS pRemoteValue = pNativeFrame->GetLSStackAddress(
+ pNativeVarInfo->loc.vlStk.vlsBaseReg,
+ pNativeVarInfo->loc.vlStk.vlsOffset);
+
+ CORDB_ADDRESS argBase;
+ // Now is the time to ask DacDbi to retrieve the information based on the VASigCookie.
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ TargetBuffer sigTargetBuf = pDAC->GetVarArgSig(pRemoteValue, &argBase);
+
+ // make sure we are not leaking any memory
+ _ASSERTE(m_rgbSigParserBuf == NULL);
+
+ m_rgbSigParserBuf = new BYTE[sigTargetBuf.cbSize];
+ GetProcess()->SafeReadBuffer(sigTargetBuf, m_rgbSigParserBuf);
+ m_sigParserCached = SigParser(m_rgbSigParserBuf, sigTargetBuf.cbSize);
+
+ // Note that we should never mutate the SigParser.
+ // Instead, make a copy and work with the copy instead.
+ if (!m_sigParserCached.IsNull())
+ {
+ SigParser sigParser = m_sigParserCached;
+
+ // get the actual count of arguments, including the var args
+ IfFailThrow(sigParser.SkipMethodHeaderSignature(&m_allArgsCount));
+
+ BOOL methodIsStatic;
+
+ m_ilCode->GetSig(NULL, NULL, &methodIsStatic); // throws
+
+ if (!methodIsStatic)
+ {
+ m_allArgsCount++; // skip the "this" object
+ }
+
+ // initialize the variable lifetime information
+ m_rgNVI = new ICorDebugInfo::NativeVarInfo[m_allArgsCount]; // throws
+
+ _ASSERTE(ICorDebugInfo::VLT_COUNT <= ICorDebugInfo::VLT_INVALID);
+
+ for (ULONG i = 0; i < m_allArgsCount; i++)
+ {
+ m_rgNVI[i].loc.vlType = ICorDebugInfo::VLT_INVALID;
+ }
+ }
+
+ // GetVarArgSig gets the address of the beginning of the arguments pushed for this frame.
+ // We'll need the address of the first argument, which will depend on its size and the
+ // calling convention, so we'll commpute that now that we have the SigParser.
+ CordbType * pArgType;
+ IfFailThrow(GetArgumentType(0, &pArgType));
+ ULONG32 argSize = 0;
+ IfFailThrow(pArgType->GetUnboxedObjectSize(&argSize));
+#if defined(_TARGET_X86_) // (STACK_GROWS_DOWN_ON_ARGS_WALK)
+ m_FirstArgAddr = argBase - argSize;
+#else // !_TARGET_X86_ (STACK_GROWS_UP_ON_ARGS_WALK)
+ AlignAddressForType(pArgType, argBase);
+ m_FirstArgAddr = argBase;
+#endif // !_TARGET_X86_ (STACK_GROWS_UP_ON_ARGS_WALK)
+ }
+
+ // The stackwalking code can't always successfully retrieve the generics type token.
+ // For example, on 64-bit, the JIT only encodes the generics type token location if
+ // a method has catch clause for a generic exception (e.g. "catch(MyException<string> e)").
+ if ((m_dwFrameParamsTokenIndex != (DWORD)ICorDebugInfo::MAX_ILNUM) && (m_frameParamsToken == NULL))
+ {
+ // All variables are unavailable in the prolog and the epilog.
+ // This includes the generics type token. Failing to get the token just means that
+ // we won't have full generics information. This should not be a disastrous failure.
+ //
+ // Currently, on X64, the JIT is reporting that the variables are live even in the epilog.
+ // That's why we need this check here. I need to follow up on this.
+ if ((m_mapping != MAPPING_PROLOG) && (m_mapping != MAPPING_EPILOG))
+ {
+ // Find the generics type token using the variable lifetime information.
+ const ICorDebugInfo::NativeVarInfo * pNativeVarInfo = NULL;
+ CordbNativeFrame * pNativeFrame = this->m_nativeFrame;
+
+ pNativeFrame->m_nativeCode->LoadNativeInfo();
+ HRESULT hrTmp = pNativeFrame->m_nativeCode->ILVariableToNative(m_dwFrameParamsTokenIndex,
+ pNativeFrame->GetInspectionIP(),
+ &pNativeVarInfo);
+
+ // It's not a disaster if we can't find the generics token, so don't throw an exception here.
+ // In fact, it's fairly common in retail code. Even if we can't find the generics token,
+ // we may still be able to look up the generics type information later by using the MethodDesc,
+ // the "this" object, etc. If not, we'll at least get the representative type information
+ // (e.g. Foo<T> instead of Foo<string>).
+ if (SUCCEEDED(hrTmp))
+ {
+ _ASSERTE(pNativeVarInfo != NULL);
+
+ // The generics type token should be stored either in a register or on the stack.
+ SIZE_T uRawToken = pNativeFrame->GetRegisterOrStackValue(pNativeVarInfo);
+
+ // Ask DAC to resolve the token for us. We really don't want to deal with all the logic here.
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ // On a minidump, we'll throw if we're missing the memory.
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ m_frameParamsToken = pDAC->ResolveExactGenericArgsToken(m_dwFrameParamsTokenIndex, uRawToken);
+ );
+ }
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+/*
+ A list of which resources owned by this object are accounted for.
+
+ UNKNOWN:
+ CordbNativeFrame* m_nativeFrame;
+ CordbILCode * m_ilCode;
+ CorDebugMappingResult m_mapping;
+ CORDB_ADDRESS m_FirstArgAddr;
+ ICorDebugInfo::NativeVarInfo * m_rgNVI; // Deleted in neuter
+ CordbClass **m_genericArgs;
+*/
+
+CordbJITILFrame::~CordbJITILFrame()
+{
+ _ASSERTE(IsNeutered());
+}
+
+// Neutered by CordbNativeFrame
+void CordbJITILFrame::Neuter()
+{
+ // Since neutering here calls Release directly, we don't want to double-release
+ // if neuter is called multiple times.
+ if (IsNeutered())
+ {
+ return;
+ }
+
+ // Frames include pointers across to other types that specify the
+ // representation instantiation - reduce the reference counts on these....
+ for (unsigned int i = 0; i < m_genericArgs.m_cInst; i++)
+ {
+ m_genericArgs.m_ppInst[i]->Release();
+ }
+
+ if (m_rgNVI != NULL)
+ {
+ delete [] m_rgNVI;
+ m_rgNVI = NULL;
+ }
+
+ if (m_rgbSigParserBuf != NULL)
+ {
+ delete [] m_rgbSigParserBuf;
+ m_rgbSigParserBuf = NULL;
+ }
+
+ m_pReJitCode.Clear();
+
+ // If this class ever inherits from the CordbFrame we'll need a call
+ // to CordbFrame::Neuter() here instead of to CordbBase::Neuter();
+ CordbBase::Neuter();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Load the generic type and method arguments and store them into the frame if possible.
+//
+// Return Value:
+// HRESULT for the operation
+//
+
+void CordbJITILFrame::LoadGenericArgs()
+{
+ THROW_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+
+ // The case where there are no type parameters, or the case where we've
+ // already feched the realInst, is easy.
+ if (m_genericArgsLoaded)
+ {
+ return;
+ }
+
+ _ASSERTE(m_nativeFrame->m_nativeCode != NULL);
+
+ if (!m_nativeFrame->m_nativeCode->IsInstantiatedGeneric())
+ {
+ m_genericArgs = Instantiation(0, NULL,0);
+ m_genericArgsLoaded = true;
+ return;
+ }
+
+ // Find the exact generic arguments for a frame that is executing
+ // a generic method. The left-side will fetch these from arguments
+ // given on the stack and/or from the IP.
+
+
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+
+ UINT32 cGenericClassTypeParams = 0;
+ DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> rgGenericTypeParams;
+
+ pDAC->GetMethodDescParams(GetCurrentAppDomain()->GetADToken(),
+ m_nativeFrame->GetNativeCode()->GetVMNativeCodeMethodDescToken(),
+ m_frameParamsToken,
+ &cGenericClassTypeParams,
+ &rgGenericTypeParams);
+
+ UINT32 cTotalGenericTypeParams = rgGenericTypeParams.Count();
+
+ // @dbgtodo reliability - This holder doesn't actually work in this case because it just deletes
+ // each element on error. The RS classes are all expected to be neutered before the destructor is called.
+ NewArrayHolder<CordbType *> ppGenericArgs(new CordbType *[cTotalGenericTypeParams]);
+
+ for (UINT32 i = 0; i < cTotalGenericTypeParams;i++)
+ {
+ // creates a CordbType object for the generic argument
+ HRESULT hr = CordbType::TypeDataToType(GetCurrentAppDomain(),
+ &(rgGenericTypeParams[i]),
+ &ppGenericArgs[i]);
+ IfFailThrow(hr);
+
+ // We add a ref as the instantiation will be stored away in the
+ // ref-counted data structure associated with the JITILFrame
+ ppGenericArgs[i]->AddRef();
+ }
+
+ // initialize the generics information
+ m_genericArgs = Instantiation(cTotalGenericTypeParams, ppGenericArgs, cGenericClassTypeParams);
+ m_genericArgsLoaded = true;
+
+ ppGenericArgs.SuppressRelease();
+}
+
+
+//
+// CordbJITILFrame::QueryInterface
+//
+// Description
+// Interface query for this COM object
+//
+// NOTE: the COM object associated with this CordbJITILFrame may consist of two
+// C++ objects (a CordbJITILFrame and its associated CordbNativeFrame)
+//
+// Parameters
+// id the GUID associated with the requested interface
+// pInterface [out] the interface pointer
+//
+// Returns
+// HRESULT
+// S_OK If this CordbJITILFrame supports the interface
+// E_NOINTERFACE If this object does not support the interface
+//
+// Exceptions
+// None
+//
+HRESULT CordbJITILFrame::QueryInterface(REFIID id, void **pInterface)
+{
+ if (NULL != m_nativeFrame)
+ {
+ // If the native frame does not support the requested interface, then
+ // the native fram is responsible for delegating the query back to this
+ // object through QueryInterfaceInternal(...)
+ return m_nativeFrame->QueryInterface(id, pInterface);
+ }
+
+ // no native frame. Check for interfaces common to CordbNativeFrame and
+ // CordbJITILFrame
+ if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugILFrame*>(this));
+ }
+ else if (id == IID_ICorDebugFrame)
+ {
+ *pInterface = static_cast<ICorDebugFrame*>(this);
+ }
+ else
+ {
+ // didn't find an interface yet. Since there's no native frame
+ // associated with this IL frame, go ahead and check for the IL frame
+ return this->QueryInterfaceInternal(id, pInterface);
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//
+// CordbJITILFrame::QueryInterfaceInternal
+//
+// Description
+// Interface query for interfaces implemented ONLY by CordbJITILFrame (as
+// opposed to interfaces implemented by both CordbNativeFrame and
+// CordbJITILFrame)
+//
+// Parameters
+// id the GUID associated with the requested interface
+// pInterface [out] the interface pointer
+// NOTE: id must not be IUnknown or ICorDebugFrame
+// NOTE: if this object is in "forward compatibility mode", passing in
+// IID_ICorDebugILFrame2 for the id will result in a failure (returns
+// E_NOINTERFACE)
+//
+// Returns
+// HRESULT
+// S_OK If this CordbJITILFrame supports the interface
+// E_NOINTERFACE If this object does not support the interface
+//
+// Exceptions
+// None
+//
+HRESULT
+CordbJITILFrame::QueryInterfaceInternal(REFIID id, void** pInterface)
+{
+ _ASSERTE(IID_ICorDebugFrame != id);
+ _ASSERTE(IID_IUnknown != id);
+
+ // don't query for IUnknown or ICorDebugFrame! Someone else should have
+ // already taken care of that.
+ if (id == IID_ICorDebugILFrame)
+ {
+ *pInterface = static_cast<ICorDebugILFrame*>(this);
+ }
+ else if (id == IID_ICorDebugILFrame2)
+ {
+ *pInterface = static_cast<ICorDebugILFrame2*>(this);
+ }
+ else if (id == IID_ICorDebugILFrame3)
+ {
+ *pInterface = static_cast<ICorDebugILFrame3*>(this);
+ }
+ else if (id == IID_ICorDebugILFrame4)
+ {
+ *pInterface = static_cast<ICorDebugILFrame4*>(this);
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Get an enumerator for the generic type and method arguments on this frame.
+//
+// Arguments:
+// ppTypeParameterEnum - out parameter; return the enumerator
+//
+// Return Value:
+// HRESULT for the operation
+//
+HRESULT CordbJITILFrame::EnumerateTypeParameters(ICorDebugTypeEnum **ppTypeParameterEnum)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppTypeParameterEnum, ICorDebugTypeEnum **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ (*ppTypeParameterEnum) = NULL;
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+
+ // load the generic arguments, which may be cached
+ LoadGenericArgs();
+
+ // create the enumerator
+ RSInitHolder<CordbTypeEnum> pEnum(
+ CordbTypeEnum::Build(GetCurrentAppDomain(), m_nativeFrame->m_pThread->GetRefreshStackNeuterList(), m_genericArgs.m_cInst, m_genericArgs.m_ppInst));
+ if ( pEnum == NULL )
+ {
+ ThrowOutOfMemory();
+ }
+
+ pEnum.TransferOwnershipExternal(ppTypeParameterEnum);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+
+// ----------------------------------------------------------------------------
+// CordbJITILFrame::GetChain
+//
+// Description:
+// Return the owning chain. Since chains have been deprecated in Arrowhead,
+// this function returns E_NOTIMPL unless there is a shim.
+//
+// Arguments:
+// * ppChain - out parameter; return the owning chain
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if ppChain is NULL.
+// Return CORDBG_E_OBJECT_NEUTERED if the CordbJITILFrame is neutered.
+// Return E_NOTIMPL if there is no shim.
+//
+
+HRESULT CordbJITILFrame::GetChain(ICorDebugChain **ppChain)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppChain, ICorDebugChain **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ hr = m_nativeFrame->GetChain(ppChain);
+ // Since we are returning anyway, let's not throw even if the call fails.
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+// Return the IL code blob associated with this IL frame.
+// Each IL frame corresponds to exactly one IL code blob.
+HRESULT CordbJITILFrame::GetCode(ICorDebugCode **ppCode)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppCode, ICorDebugCode **);
+
+ *ppCode = static_cast<ICorDebugCode*> (m_ilCode);
+ m_ilCode->ExternalAddRef();
+
+ return S_OK;;
+}
+
+// Return the function associated with this IL frame.
+// Each IL frame corresponds to exactly one function.
+HRESULT CordbJITILFrame::GetFunction(ICorDebugFunction **ppFunction)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this)
+ {
+ ValidateOrThrow(ppFunction);
+
+ CordbFunction * pFunc = m_nativeFrame->GetFunction();
+ *ppFunction = static_cast<ICorDebugFunction *>(pFunc);
+ pFunc->ExternalAddRef();
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+// Return the token of the function associated with this IL frame.
+// Each IL frame corresponds to exactly one function.
+HRESULT CordbJITILFrame::GetFunctionToken(mdMethodDef *pToken)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pToken, mdMethodDef *);
+
+ *pToken = m_nativeFrame->m_nativeCode->GetMetadataToken();
+
+ return S_OK;
+}
+
+// ----------------------------------------------------------------------------
+// CordJITILFrame::GetStackRange
+//
+// Description:
+// Get the stack range owned by the associated native frame.
+// IL frames and native frames are 1:1 for normal jitted managed methods.
+// Dynamic methods are an exception.
+//
+// Arguments:
+// * pStart - out parameter; return the leaf end of the frame
+// * pEnd - out parameter; return the root end of the frame
+//
+// Return Value:
+// Return S_OK on success.
+//
+// Notes: see code:#GetStackRange
+
+HRESULT CordbJITILFrame::GetStackRange(CORDB_ADDRESS *pStart, CORDB_ADDRESS *pEnd)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+
+ // The access of m_nativeFrame is not safe here. It's a weak reference.
+ OK_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ hr = m_nativeFrame->GetStackRange(pStart, pEnd);
+ // Since we are returning anyway, let's not throw even if the call fails.
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+// ----------------------------------------------------------------------------
+// CordbJITILFrame::GetCaller
+//
+// Description:
+// Delegate to the associated native frame to return the caller, which is closer to the root.
+// This function has been deprecated in Arrowhead, and so it returns E_NOTIMPL unless there is a shim.
+//
+// Arguments:
+// * ppFrame - out parameter; return the caller frame
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if ppFrame is NULL.
+// Return CORDBG_E_OBJECT_NEUTERED if the CordbJITILFrame is neutered.
+// Return E_NOTIMPL if there is no shim.
+//
+
+HRESULT CordbJITILFrame::GetCaller(ICorDebugFrame **ppFrame)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppFrame, ICorDebugFrame **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ hr = m_nativeFrame->GetCaller(ppFrame);
+ // Since we are returning anyway, let's not throw even if the call fails.
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+// ----------------------------------------------------------------------------
+// CordbJITILFrame::GetCallee
+//
+// Description:
+// Delegate to the associated native frame to return the callee, which is closer to the leaf.
+// This function has been deprecated in Arrowhead, and so it returns E_NOTIMPL unless there is a shim.
+//
+// Arguments:
+// * ppFrame - out parameter; return the callee frame
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if ppFrame is NULL.
+// Return CORDBG_E_OBJECT_NEUTERED if the CordbJITILFrame is neutered.
+// Return E_NOTIMPL if there is no shim.
+//
+
+HRESULT CordbJITILFrame::GetCallee(ICorDebugFrame **ppFrame)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppFrame, ICorDebugFrame **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ hr = m_nativeFrame->GetCallee(ppFrame);
+ // Since we are returning anyway, let's not throw even if the call fails.
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+// Create a stepper on the frame.
+HRESULT CordbJITILFrame::CreateStepper(ICorDebugStepper **ppStepper)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // by default, a stepper operates on the IL level, using IL offsets
+ return m_nativeFrame->CreateStepper(ppStepper);
+}
+
+// Return the IL offset and the mapping result.
+HRESULT CordbJITILFrame::GetIP(ULONG32 *pnOffset,
+ CorDebugMappingResult *pMappingResult)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pnOffset, ULONG32 *);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pMappingResult, CorDebugMappingResult *);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ *pnOffset = (ULONG32)m_ip;
+ if (pMappingResult)
+ *pMappingResult = m_mapping;
+
+ return S_OK;
+}
+
+// Determine if we can set IP at this point. The specified offset is the IL offset.
+HRESULT CordbJITILFrame::CanSetIP(ULONG32 nOffset)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Check to see that this is a leaf frame
+ if (!m_nativeFrame->IsLeafFrame())
+ {
+ ThrowHR(CORDBG_E_SET_IP_NOT_ALLOWED_ON_NONLEAF_FRAME);
+ }
+
+ // delegate to the associated native frame
+ CordbNativeCode * pNativeCode = m_nativeFrame->m_nativeCode;
+ hr = m_nativeFrame->m_pThread->SetIP(SetIP_fCanSetIPOnly, // specify that this is for checking only
+ pNativeCode,
+ nOffset,
+ SetIP_fIL );
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+// Try to set the IP to the specified offset. The specified offset is the IL offset.
+HRESULT CordbJITILFrame::SetIP(ULONG32 nOffset)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Check to see that this is a leaf frame
+ if (!m_nativeFrame->IsLeafFrame())
+ {
+ ThrowHR(CORDBG_E_SET_IP_NOT_ALLOWED_ON_NONLEAF_FRAME);
+ }
+
+ // delegate to the native frame
+ CordbNativeCode * pNativeCode = m_nativeFrame->m_nativeCode;
+ hr = m_nativeFrame->m_pThread->SetIP(SetIP_fSetIP, // specify that this is a real SetIP operation
+ pNativeCode,
+ nOffset,
+ SetIP_fIL );
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This routine creates backing native info for a local variable, returning an ICorDebugInfo
+// object for the local variable when successful.
+//
+// Arguments:
+// dwIndex - Index of the local variable to create native info for.
+// ppNativeInfo - OUT: Space for storing the resulting pointer to native variable info.
+//
+// Return Value:
+// HRESULT for the operation
+//
+HRESULT CordbJITILFrame::FabricateNativeInfo(DWORD dwIndex,
+ const ICorDebugInfo::NativeVarInfo ** ppNativeInfo)
+{
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ THROW_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess());
+ _ASSERTE(m_fVarArgFnx);
+
+ // This array should have been populated in CordbJITILFrame::Init().
+ _ASSERTE(m_rgNVI != NULL);
+
+ // check if we have already fabricated all the information
+ if (m_rgNVI[dwIndex].loc.vlType != ICorDebugInfo::VLT_INVALID)
+ {
+ (*ppNativeInfo) = &m_rgNVI[dwIndex];
+ }
+ else
+ {
+ // We'll initialize everything at once
+ ULONG cbArchitectureMin;
+
+ // m_FirstArgAddr will already be aligned on platforms that require alignment
+ CORDB_ADDRESS rpCur = m_FirstArgAddr;
+
+#if defined(DBG_TARGET_X86) || defined(DBG_TARGET_ARM)
+ cbArchitectureMin = 4;
+#elif defined(DBG_TARGET_WIN64)
+ cbArchitectureMin = 8;
+#else
+ cbArchitectureMin = 8; //REVISIT_TODO not sure if this is correct
+ PORTABILITY_ASSERT("What is the architecture-dependent minimum word size?");
+#endif // DBG_TARGET_X86
+
+ // make a copy of the cached SigParser
+ SigParser sigParser = m_sigParserCached;
+
+ IfFailThrow(sigParser.SkipMethodHeaderSignature(NULL));
+
+ ULONG32 cbType;
+
+ CordbType * pArgType;
+
+ // make sure all the generic type and method arguments are loaded
+ LoadGenericArgs();
+
+ // get a CordbType object for the generic argument
+ IfFailThrow(CordbType::SigToType(GetModule(), &sigParser, &(this->m_genericArgs), &pArgType));
+
+ IfFailThrow(pArgType->GetUnboxedObjectSize(&cbType));
+
+#if defined(DBG_TARGET_X86) // STACK_GROWS_DOWN_ON_ARGS_WALK
+ // The the rpCur pointer starts off in the right spot for the
+ // first argument, but thereafter we have to decrement it
+ // before getting the variable's location from it. So increment
+ // it here to be consistent later.
+ rpCur += max(cbType, cbArchitectureMin);
+#endif
+
+ // Grab the IL code's function's method signature so we can see if it's static.
+ BOOL fMethodIsStatic;
+
+ m_ilCode->GetSig(NULL, NULL, &fMethodIsStatic); // throws
+
+ ULONG i;
+
+ if (fMethodIsStatic)
+ {
+ i = 0;
+ }
+ else
+ {
+ i = 1;
+ }
+
+ for ( ; i < m_allArgsCount; i++)
+ {
+ m_rgNVI[i].startOffset = 0;
+ m_rgNVI[i].endOffset = 0xFFffFFff;
+ m_rgNVI[i].varNumber = i;
+ m_rgNVI[i].loc.vlType = ICorDebugInfo::VLT_FIXED_VA;
+
+ LoadGenericArgs();
+
+ IfFailThrow(CordbType::SigToType(GetModule(), &sigParser, &(this->m_genericArgs), &pArgType));
+
+ IfFailThrow(pArgType->GetUnboxedObjectSize(&cbType));
+
+#if defined(DBG_TARGET_X86) // STACK_GROWS_DOWN_ON_ARGS_WALK
+ rpCur -= max(cbType, cbArchitectureMin);
+ m_rgNVI[i].loc.vlFixedVarArg.vlfvOffset =
+ (unsigned)(m_FirstArgAddr - rpCur);
+
+ // Since the JIT adds in the size of this field, we do too to
+ // be consistent.
+ m_rgNVI[i].loc.vlFixedVarArg.vlfvOffset += sizeof(((CORINFO_VarArgInfo*)0)->argBytes);
+#else // STACK_GROWS_UP_ON_ARGS_WALK
+ m_rgNVI[i].loc.vlFixedVarArg.vlfvOffset =
+ (unsigned)(rpCur - m_FirstArgAddr);
+ rpCur += max(cbType, cbArchitectureMin);
+ AlignAddressForType(pArgType, rpCur);
+#endif
+
+ IfFailThrow(sigParser.SkipExactlyOne());
+ } // for ( ; i M m_allArgsCount; i++)
+
+ (*ppNativeInfo) = &m_rgNVI[dwIndex];
+ } // else (m_rgNVI[dwIndex].loc.vlType == ICorDebugInfo::VLT_INVALID)
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbJITILFrame::ILVariableToNative(DWORD dwVarNumber,
+ const ICorDebugInfo::NativeVarInfo **ppNativeInfo)
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+
+ _ASSERTE(m_nativeFrame->m_nativeCode->IsNativeCodeValid());
+ // We keep the fixed argument native var infos in the
+ // CordbFunction, which only is an issue for var args info:
+ if (!m_fVarArgFnx || //not a var args function
+ (dwVarNumber < m_nativeFrame->m_nativeCode->GetFixedArgCount()) || // var args,fixed arg
+ // note that this include the implicit 'this' for nonstatic fnxs
+ (dwVarNumber >= m_allArgsCount) ||// var args, local variable
+ (m_sigParserCached.IsNull())) //we don't have any VA info
+ {
+ // If we're in a var args fnx, but we're actually looking
+ // for a local variable, then we want to use the variable
+ // index as the function sees it - fixed (but not var)
+ // args are added to local var number to get native info
+ // We are really trying to find a variable by it's number,
+ // but "special" variables have a negative number which we
+ // don't use. We "number" them conceptually between the
+ // arguments and locals:
+ //
+ // arguments special locals
+ // -----------------------------------------
+ // Actual numbers: 1 2 3 . . . 4 5 6 7
+ // Logical numbers: 0 1 2 3 4 5 6 7 8
+ //
+ // We have two different counts for the number of arguments: the fixedArgCount
+ // gives the actual number of arguments and the allArgsCount is the number of
+ // of fixed arguments plus the number of var args.
+ //
+ // Thus, to get the correct actual number for locals we have to compute it as
+ // logicalNumber - allArgsCount + fixedArgCount
+
+ if (m_fVarArgFnx && (dwVarNumber >= m_allArgsCount) && !m_sigParserCached.IsNull())
+ {
+ dwVarNumber -= m_allArgsCount;
+ dwVarNumber += m_nativeFrame->m_nativeCode->GetFixedArgCount();
+ }
+
+ return m_nativeFrame->m_nativeCode->ILVariableToNative(dwVarNumber,
+ m_nativeFrame->GetInspectionIP(),
+ ppNativeInfo);
+ }
+
+ return FabricateNativeInfo(dwVarNumber,ppNativeInfo);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This routine get the type of a particular argument.
+//
+// Arguments:
+// dwIndex - Index of the argument.
+// ppResultType - OUT: Space for storing the type of the argument.
+//
+// Return Value:
+// HRESULT for the operation
+//
+HRESULT CordbJITILFrame::GetArgumentType(DWORD dwIndex,
+ CordbType ** ppResultType)
+{
+ HRESULT hr = S_OK;
+ THROW_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ LoadGenericArgs();
+
+ if (m_fVarArgFnx && !m_sigParserCached.IsNull())
+ {
+ SigParser sigParser = m_sigParserCached;
+
+ IfFailThrow(sigParser.SkipMethodHeaderSignature(NULL));
+
+ // Grab the IL code's function's method signature so we can see if it's static.
+ BOOL fMethodIsStatic;
+
+ m_ilCode->GetSig(NULL, NULL, &fMethodIsStatic); // throws
+ if (!fMethodIsStatic)
+ {
+ if (dwIndex == 0)
+ {
+ // Return the signature for the 'this' pointer for the
+ // class this method is in.
+
+ IfFailThrow(m_ilCode->GetClass()->GetThisType(&(this->m_genericArgs), ppResultType));
+ return hr;
+ }
+ else
+ {
+ dwIndex--;
+ }
+ }
+ for (ULONG i = 0; i < dwIndex; i++)
+ {
+ IfFailThrow(sigParser.SkipExactlyOne());
+ }
+
+ IfFailThrow(sigParser.SkipFunkyAndCustomModifiers());
+
+ IfFailThrow(sigParser.SkipAnyVASentinel());
+
+ IfFailThrow(CordbType::SigToType(GetModule(), &sigParser, &(this->m_genericArgs), ppResultType));
+ }
+ else // (!m_fVarArgFnx || m_sigParserCached.IsNull())
+ {
+ m_nativeFrame->m_nativeCode->GetArgumentType(dwIndex, &(this->m_genericArgs), ppResultType);
+ }
+
+ return hr;
+}
+
+//
+// GetNativeVariable uses the JIT variable information to delegate to
+// the native frame when the value is really created.
+//
+HRESULT CordbJITILFrame::GetNativeVariable(CordbType *type,
+ const ICorDebugInfo::NativeVarInfo *pNativeVarInfo,
+ ICorDebugValue **ppValue)
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+ if (m_nativeFrame->IsFunclet())
+ {
+ if ( (pNativeVarInfo->loc.vlType != ICorDebugInfo::VLT_STK) &&
+ (pNativeVarInfo->loc.vlType != ICorDebugInfo::VLT_STK2) &&
+ (pNativeVarInfo->loc.vlType != ICorDebugInfo::VLT_STK_BYREF) )
+ {
+ _ASSERTE(!"CordbJITILFrame::GetNativeVariable()"
+ " - Variables used in funclets should always be homed on the stack.\n");
+ return E_FAIL;
+ }
+ }
+#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM
+
+ switch (pNativeVarInfo->loc.vlType)
+ {
+ case ICorDebugInfo::VLT_REG:
+ hr = m_nativeFrame->GetLocalRegisterValue(
+ ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlReg.vlrReg),
+ type, ppValue);
+ break;
+
+ case ICorDebugInfo::VLT_REG_BYREF:
+ {
+ CORDB_ADDRESS pRemoteByRefAddr = PTR_TO_CORDB_ADDRESS(
+ *( m_nativeFrame->GetAddressOfRegister(ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlReg.vlrReg))) );
+
+ hr = m_nativeFrame->GetLocalMemoryValue(pRemoteByRefAddr,
+ type,
+ ppValue);
+ }
+ break;
+
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+ case ICorDebugInfo::VLT_REG_FP:
+#if defined(DBG_TARGET_ARM) // @ARMTODO
+ hr = E_NOTIMPL;
+#else // DBG_TARGET_ARM @ARMTODO
+ hr = m_nativeFrame->GetLocalFloatingPointValue(pNativeVarInfo->loc.vlReg.vlrReg + REGISTER_AMD64_XMM0,
+ type, ppValue);
+
+#endif // DBG_TARGET_ARM @ARMTODO
+ break;
+#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM
+
+ case ICorDebugInfo::VLT_STK_BYREF:
+ {
+ CORDB_ADDRESS pRemoteByRefAddr = m_nativeFrame->GetLSStackAddress(
+ pNativeVarInfo->loc.vlStk.vlsBaseReg, pNativeVarInfo->loc.vlStk.vlsOffset) ;
+
+ hr = m_nativeFrame->GetLocalByRefMemoryValue(pRemoteByRefAddr,
+ type,
+ ppValue);
+ }
+ break;
+
+ case ICorDebugInfo::VLT_STK:
+ {
+ CORDB_ADDRESS pRemoteValue = m_nativeFrame->GetLSStackAddress(
+ pNativeVarInfo->loc.vlStk.vlsBaseReg, pNativeVarInfo->loc.vlStk.vlsOffset) ;
+
+ hr = m_nativeFrame->GetLocalMemoryValue(pRemoteValue,
+ type,
+ ppValue);
+ }
+ break;
+
+ case ICorDebugInfo::VLT_REG_REG:
+ hr = m_nativeFrame->GetLocalDoubleRegisterValue(
+ ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlRegReg.vlrrReg2),
+ ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlRegReg.vlrrReg1),
+ type, ppValue);
+ break;
+
+ case ICorDebugInfo::VLT_REG_STK:
+ {
+ CORDB_ADDRESS pRemoteValue = m_nativeFrame->GetLSStackAddress(
+ pNativeVarInfo->loc.vlRegStk.vlrsStk.vlrssBaseReg, pNativeVarInfo->loc.vlRegStk.vlrsStk.vlrssOffset);
+
+ hr = m_nativeFrame->GetLocalMemoryRegisterValue(
+ pRemoteValue,
+ ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlRegStk.vlrsReg),
+ type, ppValue);
+ }
+ break;
+
+ case ICorDebugInfo::VLT_STK_REG:
+ {
+ CORDB_ADDRESS pRemoteValue = m_nativeFrame->GetLSStackAddress(
+ pNativeVarInfo->loc.vlStkReg.vlsrStk.vlsrsBaseReg, pNativeVarInfo->loc.vlStkReg.vlsrStk.vlsrsOffset);
+
+ hr = m_nativeFrame->GetLocalRegisterMemoryValue(
+ ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlStkReg.vlsrReg),
+ pRemoteValue, type, ppValue);
+ }
+ break;
+
+ case ICorDebugInfo::VLT_STK2:
+ {
+ CORDB_ADDRESS pRemoteValue = m_nativeFrame->GetLSStackAddress(
+ pNativeVarInfo->loc.vlStk2.vls2BaseReg, pNativeVarInfo->loc.vlStk2.vls2Offset);
+
+ hr = m_nativeFrame->GetLocalMemoryValue(pRemoteValue,
+ type,
+ ppValue);
+ }
+ break;
+
+ case ICorDebugInfo::VLT_FPSTK:
+#if defined(DBG_TARGET_ARM) // @ARMTODO
+ hr = E_NOTIMPL;
+#else
+ /*
+ @TODO [Microsoft] We have to make this work!!!!!!!!!!!!!
+ hr = m_nativeFrame->GetLocalFloatingPointValue(
+ pNativeVarInfo->loc.vlFPstk.vlfReg + REGISTER_X86_FPSTACK_0,
+ type, ppValue);
+ */
+ hr = CORDBG_E_IL_VAR_NOT_AVAILABLE;
+#endif
+ break;
+
+ case ICorDebugInfo::VLT_FIXED_VA:
+ if (m_sigParserCached.IsNull()) //no var args info
+ return CORDBG_E_IL_VAR_NOT_AVAILABLE;
+
+ CORDB_ADDRESS pRemoteValue;
+
+
+#if defined(DBG_TARGET_X86) // STACK_GROWS_DOWN_ON_ARGS_WALK
+ pRemoteValue = m_FirstArgAddr - pNativeVarInfo->loc.vlFixedVarArg.vlfvOffset;
+ // Remember to subtract out this amount
+ pRemoteValue += sizeof(((CORINFO_VarArgInfo*)0)->argBytes);
+#else // STACK_GROWS_UP_ON_ARGS_WALK
+ pRemoteValue = m_FirstArgAddr + pNativeVarInfo->loc.vlFixedVarArg.vlfvOffset;
+#endif
+
+ hr = m_nativeFrame->GetLocalMemoryValue(pRemoteValue,
+ type,
+ ppValue);
+
+ break;
+
+
+ default:
+ _ASSERTE(!"Invalid locVarType");
+ hr = E_FAIL;
+ break;
+ }
+
+ return hr;
+}
+
+HRESULT CordbJITILFrame::EnumerateLocalVariables(ICorDebugValueEnum **ppValueEnum)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValueEnum, ICorDebugValueEnum **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return EnumerateLocalVariablesEx(ILCODE_ORIGINAL_IL, ppValueEnum);
+}
+
+HRESULT CordbJITILFrame::GetLocalVariable(DWORD dwIndex,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return GetLocalVariableEx(ILCODE_ORIGINAL_IL, dwIndex, ppValue);
+}
+
+
+HRESULT CordbJITILFrame::EnumerateArguments(ICorDebugValueEnum **ppValueEnum)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValueEnum, ICorDebugValueEnum **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ RSInitHolder<CordbValueEnum> cdVE(new CordbValueEnum(m_nativeFrame, CordbValueEnum::ARGS));
+
+ // Initialize the new enum
+ hr = cdVE->Init();
+ IfFailThrow(hr);
+
+ cdVE.TransferOwnershipExternal(ppValueEnum);
+
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This routine gets the value of a particular argument
+//
+// Arguments:
+// dwIndex - Index of the argument.
+// ppValue - OUT: Space for storing the value of the argument
+//
+// Return Value:
+// HRESULT for the operation
+//
+HRESULT CordbJITILFrame::GetArgument(DWORD dwIndex, ICorDebugValue ** ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ const ICorDebugInfo::NativeVarInfo * pNativeInfo;
+
+ //
+ // First, make sure that we've got the jitted variable location data
+ // loaded from the left side.
+ //
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ m_nativeFrame->m_nativeCode->LoadNativeInfo(); //throws
+
+ hr = ILVariableToNative(dwIndex, &pNativeInfo);
+ IfFailThrow(hr);
+
+ // Get the type of this argument from the function
+ CordbType * pType;
+
+ hr = GetArgumentType(dwIndex, &pType);
+ IfFailThrow(hr);
+
+ hr = GetNativeVariable(pType, pNativeInfo, ppValue);
+ IfFailThrow(hr);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+
+HRESULT CordbJITILFrame::GetStackDepth(ULONG32 *pDepth)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pDepth, ULONG32 *);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+
+ /* !!! */
+
+ return E_NOTIMPL;
+}
+
+HRESULT CordbJITILFrame::GetStackValue(DWORD dwIndex, ICorDebugValue **ppValue)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ /* !!! */
+
+ return E_NOTIMPL;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Remaps the active frame to the latest EnC version of the function, preserving the
+// execution state of the method such as the values of locals.
+// Can only be called when the leaf frame is at a remap opportunity.
+//
+// Arguments:
+// nOffset - the IL offset in the new version of the function to remap to
+//
+
+HRESULT CordbJITILFrame::RemapFunction(ULONG32 nOffset)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this)
+ {
+#if !defined(EnC_SUPPORTED)
+ ThrowHR(E_NOTIMPL);
+
+#else // EnC_SUPPORTED
+ // Can only be called on leaf frame.
+ if (!m_nativeFrame->IsLeafFrame())
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ // mark frames as not fresh, because this frame has been updated.
+ m_nativeFrame->m_pThread->CleanupStack();
+
+ // Since we may have overwritten anything (objects, code, etc), we should mark
+ // everything as needing to be re-cached.
+ m_nativeFrame->m_pThread->GetProcess()->m_continueCounter++;
+
+ // Tell the left-side to do the remap
+ hr = m_nativeFrame->m_pThread->SetRemapIP(nOffset);
+
+#endif // EnC_SUPPORTED
+ }
+ PUBLIC_API_END(hr);
+
+ return hr;
+}
+HRESULT CordbJITILFrame::GetReturnValueForILOffset(ULONG32 ILoffset, ICorDebugValue** ppReturnValue)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ EX_TRY
+ {
+ hr = GetReturnValueForILOffsetImpl(ILoffset, ppReturnValue);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+
+HRESULT CordbJITILFrame::BuildInstantiationForCallsite(CordbModule * pModule, NewArrayHolder<CordbType*> &types, Instantiation &inst, Instantiation *currentInstantiation, mdToken targetClass, SigParser genericSig)
+{
+ // This function builds an Instantiation object (and backing "types" array) for a given
+ // class and method signature.
+ HRESULT hr = S_OK;
+ RSExtSmartPtr<IMetaDataImport2> pImport2;
+ IfFailRet(pModule->GetMetaDataImporter()->QueryInterface(IID_IMetaDataImport2, (void**)&pImport2));
+
+ // If the targetClass is a TypeSpec that means its first element is GENERICINST.
+ // We only need to build types for the Instantiation if targetClass is a TypeSpec.
+ ULONG classGenerics = 0;
+ SigParser typeSig;
+ if (TypeFromToken(targetClass) == mdtTypeSpec)
+ {
+ // Our goal with this is to full "classGenerics" with the number of
+ // generics, and move "typeSig" to the start of the first generic type.
+ PCCOR_SIGNATURE sig = 0;
+ ULONG sigCount = 0;
+
+ IfFailRet(pImport2->GetTypeSpecFromToken(targetClass, &sig, &sigCount));
+
+ typeSig = SigParser(sig, sigCount);
+ CorElementType elemType;
+ IfFailRet(typeSig.GetElemType(&elemType));
+
+ if (elemType != ELEMENT_TYPE_GENERICINST)
+ return META_E_BAD_SIGNATURE;
+
+ IfFailRet(typeSig.GetElemType(&elemType));
+ if (elemType != ELEMENT_TYPE_VALUETYPE && elemType != ELEMENT_TYPE_CLASS)
+ return META_E_BAD_SIGNATURE;
+
+ IfFailRet(typeSig.GetToken(NULL));
+ IfFailRet(typeSig.GetData(&classGenerics));
+ }
+
+ // Similarly for method generics. Simply fill "methodGenerics" with the number
+ // of generics, and move "genericSig" to the start of the first generic param.
+ ULONG methodGenerics = 0;
+ if (!genericSig.IsNull())
+ {
+ ULONG callingConv = 0;
+ IfFailRet(genericSig.GetCallingConvInfo(&callingConv));
+ if (callingConv == IMAGE_CEE_CS_CALLCONV_GENERICINST)
+ IfFailRet(genericSig.GetData(&methodGenerics));
+ }
+
+
+ // Now build "types" and "inst".
+ CordbType *pType = 0;
+ types = new CordbType*[methodGenerics+classGenerics];
+ ULONG i = 0;
+ for (;i < classGenerics; ++i)
+ {
+ CorElementType et;
+ IfFailRet(typeSig.PeekElemType(&et));
+ if ((et == ELEMENT_TYPE_VAR || et == ELEMENT_TYPE_MVAR) && currentInstantiation->m_cInst == 0)
+ return E_FAIL;
+
+ CordbType::SigToType(pModule, &typeSig, currentInstantiation, &pType);
+ types[i] = pType;
+ typeSig.SkipExactlyOne();
+ }
+
+ for (; i < methodGenerics+classGenerics; ++i)
+ {
+ CorElementType et;
+ IfFailRet(genericSig.PeekElemType(&et));
+ if ((et == ELEMENT_TYPE_VAR || et == ELEMENT_TYPE_MVAR) && currentInstantiation->m_cInst == 0)
+ return E_FAIL;
+
+ CordbType::SigToType(pModule, &genericSig, currentInstantiation, &pType);
+ types[i] = pType;
+ genericSig.SkipExactlyOne();
+ }
+
+ inst = Instantiation(methodGenerics+classGenerics, types, classGenerics);
+ return S_OK;
+}
+
+HRESULT CordbJITILFrame::GetReturnValueForILOffsetImpl(ULONG32 ILoffset, ICorDebugValue** ppReturnValue)
+{
+ if (ppReturnValue == NULL)
+ return E_INVALIDARG;
+
+ if (!m_genericArgsLoaded)
+ LoadGenericArgs();
+
+ // First verify that we're stopped at the correct native offset
+ // by calling ICorDebugCode3::GetReturnValueLiveOffset and
+ // compare the returned native offset to our current location.
+ HRESULT hr = S_OK;
+ CordbNativeCode *pCode = m_nativeFrame->m_nativeCode;
+ pCode->LoadNativeInfo();
+
+ ULONG32 count = 0;
+ IfFailRet(pCode->GetReturnValueLiveOffsetImpl(&m_genericArgs, ILoffset, 0, &count, NULL));
+
+ NewArrayHolder<ULONG32> offsets(new ULONG32[count]);
+ IfFailRet(pCode->GetReturnValueLiveOffsetImpl(&m_genericArgs, ILoffset, count, &count, offsets));
+
+ bool found = false;
+ ULONG32 currentOffset = m_nativeFrame->GetIPOffset();
+ for (ULONG32 i = 0; i < count; ++i)
+ if ((found = currentOffset == offsets[i]))
+ break;
+
+ if (!found)
+ return E_UNEXPECTED;
+
+ // Get the signatures and mdToken for the callee.
+ SigParser methodSig, genericSig;
+ mdToken mdFunction = 0, targetClass = 0;
+ IfFailRet(pCode->GetCallSignature(ILoffset, &targetClass, &mdFunction, methodSig, genericSig));
+ IfFailRet(CordbNativeCode::SkipToReturn(methodSig));
+
+
+
+
+ // Create the Instantiation, type and then return value
+ NewArrayHolder<CordbType*> types;
+ Instantiation inst;
+ CordbType *pType = 0;
+ IfFailRet(BuildInstantiationForCallsite(GetModule(), types, inst, &m_genericArgs, targetClass, genericSig));
+ IfFailRet(CordbType::SigToType(GetModule(), &methodSig, &inst, &pType));
+ return GetReturnValueForType(pType, ppReturnValue);
+}
+
+
+HRESULT CordbJITILFrame::GetReturnValueForType(CordbType *pType, ICorDebugValue **ppReturnValue)
+{
+#if defined(DBG_TARGET_ARM)
+ return E_NOTIMPL;
+#else
+
+
+#if defined(DBG_TARGET_X86)
+ const CorDebugRegister floatRegister = REGISTER_X86_FPSTACK_0;
+#elif defined(DBG_TARGET_AMD64)
+ const CorDebugRegister floatRegister = REGISTER_AMD64_XMM0;
+#elif defined(DBG_TARGET_ARM64)
+ const CorDebugRegister floatRegister = REGISTER_ARM64_V0;
+#endif
+
+#if defined(DBG_TARGET_X86)
+ const CorDebugRegister ptrRegister = REGISTER_X86_EAX;
+#elif defined(DBG_TARGET_AMD64)
+ const CorDebugRegister ptrRegister = REGISTER_AMD64_RAX;
+#elif defined(DBG_TARGET_ARM64)
+ const CorDebugRegister ptrRegister = REGISTER_ARM64_X0;
+#endif
+
+ CorElementType corReturnType = pType->GetElementType();
+ switch (corReturnType)
+ {
+ default:
+ return m_nativeFrame->GetLocalRegisterValue(ptrRegister, pType, ppReturnValue);
+
+ case ELEMENT_TYPE_R4:
+ case ELEMENT_TYPE_R8:
+ return m_nativeFrame->GetLocalFloatingPointValue(floatRegister, pType, ppReturnValue);
+
+#ifdef DBG_TARGET_X86
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_U8:
+ return m_nativeFrame->GetLocalDoubleRegisterValue(REGISTER_X86_EDX, REGISTER_X86_EAX, pType, ppReturnValue);
+#endif
+ }
+#endif
+}
+
+HRESULT CordbJITILFrame::EnumerateLocalVariablesEx(ILCodeKind flags, ICorDebugValueEnum **ppValueEnum)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValueEnum, ICorDebugValueEnum **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ if (flags != ILCODE_ORIGINAL_IL && flags != ILCODE_REJIT_IL)
+ return E_INVALIDARG;
+
+ EX_TRY
+ {
+ RSInitHolder<CordbValueEnum> cdVE(new CordbValueEnum(m_nativeFrame,
+ flags == ILCODE_ORIGINAL_IL ? CordbValueEnum::LOCAL_VARS_ORIGINAL_IL : CordbValueEnum::LOCAL_VARS_REJIT_IL));
+
+ // Initialize the new enum
+ hr = cdVE->Init();
+ IfFailThrow(hr);
+
+ cdVE.TransferOwnershipExternal(ppValueEnum);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+HRESULT CordbJITILFrame::GetLocalVariableEx(ILCodeKind flags, DWORD dwIndex, ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (flags != ILCODE_ORIGINAL_IL && flags != ILCODE_REJIT_IL)
+ return E_INVALIDARG;
+ if (flags == ILCODE_REJIT_IL && m_pReJitCode == NULL)
+ return E_INVALIDARG;
+
+ const ICorDebugInfo::NativeVarInfo *pNativeInfo;
+
+ //
+ // First, make sure that we've got the jitted variable location data
+ // loaded from the left side.
+ //
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ m_nativeFrame->m_nativeCode->LoadNativeInfo(); //throws
+
+ ULONG cArgs;
+ if (m_fVarArgFnx && (!m_sigParserCached.IsNull()))
+ {
+ cArgs = m_allArgsCount;
+ }
+ else
+ {
+ cArgs = m_nativeFrame->m_nativeCode->GetFixedArgCount();
+ }
+
+ hr = ILVariableToNative(dwIndex + cArgs, &pNativeInfo);
+ IfFailThrow(hr);
+
+ LoadGenericArgs();
+
+ // Get the type of this argument from the function
+ CordbType *type;
+ CordbILCode* pActiveCode = m_pReJitCode != NULL ? m_pReJitCode : m_ilCode;
+ hr = pActiveCode->GetLocalVariableType(dwIndex, &(this->m_genericArgs), &type);
+ IfFailThrow(hr);
+
+ // if the caller wants the original IL local, it should implicitly map to the same index
+ // variable in the profiler instrumented code. We can't determine whether the instrumented code
+ // really adhered to this, but we can check two things:
+ // a) the requested index was valid in the original signature
+ // (GetLocalVariableType will return E_INVALIDARG if not)
+ // b) the type of local in the original signature matches the type of local in the instrumented signature
+ // (the code below will return CORDBG_E_IL_VAR_NOT_AVAILABLE)
+ if (flags == ILCODE_ORIGINAL_IL && m_pReJitCode != NULL)
+ {
+ CordbType* pOriginalType;
+ hr = m_ilCode->GetLocalVariableType(dwIndex, &(this->m_genericArgs), &pOriginalType);
+ IfFailThrow(hr);
+ if (pOriginalType != type)
+ {
+ IfFailThrow(CORDBG_E_IL_VAR_NOT_AVAILABLE); // bad profiler, it shouldn't have changed types
+ }
+ }
+
+
+ hr = GetNativeVariable(type, pNativeInfo, ppValue);
+ IfFailThrow(hr);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbJITILFrame::GetCodeEx(ILCodeKind flags, ICorDebugCode **ppCode)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (flags != ILCODE_ORIGINAL_IL && flags != ILCODE_REJIT_IL)
+ return E_INVALIDARG;
+
+ if (flags == ILCODE_ORIGINAL_IL)
+ {
+ return GetCode(ppCode);
+ }
+ else
+ {
+ *ppCode = m_pReJitCode;
+ if (m_pReJitCode != NULL)
+ {
+ m_pReJitCode->ExternalAddRef();
+ }
+ }
+ return S_OK;
+}
+
+CordbILCode* CordbJITILFrame::GetOriginalILCode()
+{
+ return m_ilCode;
+}
+
+CordbReJitILCode* CordbJITILFrame::GetReJitILCode()
+{
+ return m_pReJitCode;
+}
+
+/* ------------------------------------------------------------------------- *
+ * Eval class
+ * ------------------------------------------------------------------------- */
+
+CordbEval::CordbEval(CordbThread *pThread)
+ : CordbBase(pThread->GetProcess(), 0, enumCordbEval),
+ m_thread(pThread), // implicit InternalAddRef
+ m_function(NULL),
+ m_complete(false),
+ m_successful(false),
+ m_aborted(false),
+ m_resultAddr(NULL),
+ m_evalDuringException(false)
+{
+ m_vmObjectHandle = VMPTR_OBJECTHANDLE::NullPtr();
+ m_debuggerEvalKey = LSPTR_DEBUGGEREVAL::NullPtr();
+
+ m_resultType.elementType = ELEMENT_TYPE_VOID;
+ m_resultAppDomainToken = VMPTR_AppDomain::NullPtr();
+
+ CordbAppDomain * pDomain = m_thread->GetAppDomain();
+ (void)pDomain; //prevent "unused variable" error from GCC
+#ifdef _DEBUG
+ // Remember what AD we started in so that we can check that we finish there too.
+ m_DbgAppDomainStarted = pDomain;
+#endif
+
+ // Place ourselves on the processes neuter-list.
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ GetProcess()->AddToLeftSideResourceCleanupList(this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+}
+
+CordbEval::~CordbEval()
+{
+ _ASSERTE(IsNeutered());
+}
+
+// Free the left-side resources for the eval.
+void CordbEval::NeuterLeftSideResources()
+{
+ SendCleanup();
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ Neuter();
+}
+
+// Neuter the CordbEval
+//
+// Assumptions:
+// By the time we neuter the eval, it's associated left-side resources
+// are already cleaned up (either explicitly from calling code:CordbEval::SendCleanup
+// or implicitly from the left-side exiting).
+//
+// Notes:
+// We place ourselves on a neuter list. This gets called when the neuterlist sweeps.
+void CordbEval::Neuter()
+{
+ // By now, we should have freed our target-resources (code:CordbEval::NeuterLeftSideResources
+ // or code:CordbEval::SendCleanup), unless the target is dead (terminated or about to exit).
+ BOOL fTargetIsDead = !GetProcess()->IsSafeToSendEvents() || GetProcess()->m_exiting;
+ (void)fTargetIsDead; //prevent "unused variable" error from GCC
+ _ASSERTE(fTargetIsDead || (m_debuggerEvalKey == NULL));
+
+ m_thread.Clear();
+
+ CordbBase::Neuter();
+}
+
+HRESULT CordbEval::SendCleanup()
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+
+ HRESULT hr = S_OK;
+
+ // Send a message to the left side to release the eval object over
+ // there if one exists.
+ if ((m_debuggerEvalKey != NULL) &&
+ GetProcess()->IsSafeToSendEvents())
+ {
+ // Call Abort() before doing new CallFunction()
+ if (!m_complete)
+ return CORDBG_E_FUNC_EVAL_NOT_COMPLETE;
+
+ // Release the left side handle to the object
+ DebuggerIPCEvent event;
+
+ GetProcess()->InitIPCEvent(
+ &event,
+ DB_IPCE_FUNC_EVAL_CLEANUP,
+ true,
+ m_thread->GetAppDomain()->GetADToken());
+
+ event.FuncEvalCleanup.debuggerEvalKey = m_debuggerEvalKey;
+
+ hr = GetProcess()->SendIPCEvent(&event, sizeof(DebuggerIPCEvent));
+ IfFailRet(hr);
+
+#if _DEBUG
+ if (SUCCEEDED(hr))
+ _ASSERTE(event.type == DB_IPCE_FUNC_EVAL_CLEANUP_RESULT);
+#endif
+
+ // Null out the key so we don't try to do this again.
+ m_debuggerEvalKey = LSPTR_DEBUGGEREVAL::NullPtr();
+
+ hr = event.hr;
+ }
+
+ // Release the cached HandleValue for the result. This may cleanup resources,
+ // like our object handle to the func-eval result.
+ m_pHandleValue.Clear();
+
+
+ return hr;
+}
+
+HRESULT CordbEval::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugEval)
+ {
+ *pInterface = static_cast<ICorDebugEval*>(this);
+ }
+ else if (id == IID_ICorDebugEval2)
+ {
+ *pInterface = static_cast<ICorDebugEval2*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugEval*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//
+// Gather data about an argument to either CallFunction or NewObject
+// and place it into a DebuggerIPCE_FuncEvalArgData struct for passing
+// to the Left Side.
+//
+HRESULT CordbEval::GatherArgInfo(ICorDebugValue *pValue,
+ DebuggerIPCE_FuncEvalArgData *argData)
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+
+ HRESULT hr;
+ CORDB_ADDRESS addr;
+ CorElementType ty;
+ bool needRelease = false;
+
+ pValue->GetType(&ty);
+
+ // Note: if the value passed in is in fact a byref, then we need to dereference it to get to the real thing. Passing
+ // a byref as a byref to a func eval is never right.
+ if ((ty == ELEMENT_TYPE_BYREF) || (ty == ELEMENT_TYPE_TYPEDBYREF))
+ {
+ ICorDebugReferenceValue *prv = NULL;
+
+ // The value had better implement ICorDebugReference value.
+ IfFailRet(pValue->QueryInterface(IID_ICorDebugReferenceValue, (void**)&prv));
+
+ // This really should always work for a byref, unless we're out of memory.
+ hr = prv->Dereference(&pValue);
+ prv->Release();
+
+ IfFailRet(hr);
+
+ // Make sure to get the type we were referencing for use below.
+ pValue->GetType(&ty);
+ needRelease = true;
+ }
+
+ // We should never have a byref by this point.
+ _ASSERTE((ty != ELEMENT_TYPE_BYREF) && (ty != ELEMENT_TYPE_TYPEDBYREF));
+
+ pValue->GetAddress(&addr);
+
+ argData->argAddr = CORDB_ADDRESS_TO_PTR(addr);
+ argData->argElementType = ty;
+
+ argData->argIsHandleValue = false;
+ argData->argIsLiteral = false;
+ argData->fullArgType = NULL;
+ argData->fullArgTypeNodeCount = 0;
+
+ // We have to have knowledge of our value implementation here,
+ // which it would nice if we didn't have to know.
+ CordbValue *cv = NULL;
+
+ switch(ty)
+ {
+
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_OBJECT:
+ case ELEMENT_TYPE_STRING:
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ {
+ ICorDebugHandleValue *pHandle = NULL;
+ pValue->QueryInterface(IID_ICorDebugHandleValue, (void **) &pHandle);
+ if (pHandle == NULL)
+ {
+ // A reference value
+ cv = static_cast<CordbValue*> (static_cast<CordbReferenceValue*> (pValue));
+ argData->argIsHandleValue = !(((CordbReferenceValue *)pValue)->m_valueHome.ObjHandleIsNull());
+
+ // Is this a literal value? If, we'll copy the data to the
+ // buffer area so the left side can get it.
+ CordbReferenceValue *rv;
+ rv = static_cast<CordbReferenceValue*>(pValue);
+ argData->argIsLiteral = rv->CopyLiteralData(argData->argLiteralData);
+ if (rv->GetValueHome())
+ {
+ rv->GetValueHome()->CopyToIPCEType(&(argData->argHome));
+ }
+ }
+ else
+ {
+ argData->argIsHandleValue = true;
+ argData->argIsLiteral = false;
+ pHandle->Release();
+ argData->argHome.kind = RAK_NONE;
+ }
+ }
+ break;
+
+ case ELEMENT_TYPE_VALUETYPE: // OK: this E_T_VALUETYPE comes ICorDebugValue::GetType
+
+ // A value class object
+ cv = static_cast<CordbValue*> (static_cast<CordbVCObjectValue*>(static_cast<ICorDebugObjectValue*> (pValue)));
+
+ // The EE does not guarantee to have exact type information
+ // available for all struct types, so we indicate the type by using a
+ // DebuggerIPCE_TypeArgData serialization of a type.
+ //
+ // At the moment the LHS only cares about this data
+ // when boxing the "this" pointer.
+ {
+ CordbVCObjectValue * pVCObjVal =
+ static_cast<CordbVCObjectValue *>(static_cast<ICorDebugObjectValue*> (pValue));
+
+ unsigned int fullArgTypeNodeCount = 0;
+ cv->m_type->CountTypeDataNodes(&fullArgTypeNodeCount);
+
+ _ASSERTE(fullArgTypeNodeCount > 0);
+ unsigned int bufferSize = sizeof(DebuggerIPCE_TypeArgData) * fullArgTypeNodeCount;
+ DebuggerIPCE_TypeArgData *bufferFrom = (DebuggerIPCE_TypeArgData *) _alloca(bufferSize);
+
+ DebuggerIPCE_TypeArgData *curr = bufferFrom;
+ CordbType::GatherTypeData(cv->m_type, &curr);
+
+ void *buffer = NULL;
+ IfFailRet(m_thread->GetProcess()->GetAndWriteRemoteBuffer(m_thread->GetAppDomain(), bufferSize, bufferFrom, &buffer));
+
+ argData->fullArgType = buffer;
+ argData->fullArgTypeNodeCount = fullArgTypeNodeCount;
+ // Is it enregistered?
+ if ((addr == NULL) && (pVCObjVal->GetValueHome() != NULL))
+ {
+ pVCObjVal->GetValueHome()->CopyToIPCEType(&(argData->argHome));
+ }
+
+ }
+ break;
+
+ default:
+
+ // A generic value
+ cv = static_cast<CordbValue*> (static_cast<CordbGenericValue*> (pValue));
+
+ // Is this a literal value? If, we'll copy the data to the
+ // buffer area so the left side can get it.
+ CordbGenericValue *gv = (CordbGenericValue*)pValue;
+ argData->argIsLiteral = gv->CopyLiteralData(argData->argLiteralData);
+ // Is it enregistered?
+ if ((addr == NULL) && (gv->GetValueHome() != NULL))
+ {
+ gv->GetValueHome()->CopyToIPCEType(&(argData->argHome));
+ }
+ break;
+ }
+
+
+ // Release pValue if we got it via a dereference from above.
+ if (needRelease)
+ pValue->Release();
+
+ return S_OK;
+}
+
+
+HRESULT CordbEval::SendFuncEval(unsigned int genericArgsCount,
+ ICorDebugType *genericArgs[],
+ void *argData1, unsigned int argData1Size,
+ void *argData2, unsigned int argData2Size,
+ DebuggerIPCEvent * event)
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+ unsigned int genericArgsNodeCount = 0;
+
+ DebuggerIPCE_TypeArgData *tyargData = NULL;
+ CordbType::CountTypeDataNodesForInstantiation(genericArgsCount,genericArgs,&genericArgsNodeCount);
+
+ unsigned int tyargDataSize = sizeof(DebuggerIPCE_TypeArgData) * genericArgsNodeCount;
+
+ if (genericArgsNodeCount > 0)
+ {
+ tyargData = new (nothrow) DebuggerIPCE_TypeArgData[genericArgsNodeCount];
+ if (tyargData == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ DebuggerIPCE_TypeArgData *curr_tyargData = tyargData;
+ CordbType::GatherTypeDataForInstantiation(genericArgsCount, genericArgs, &curr_tyargData);
+
+ }
+ event->FuncEval.genericArgsNodeCount = genericArgsNodeCount;
+
+
+ // Are we doing an eval during an exception? If so, we need to remember
+ // that over here and also tell the Left Side.
+ event->FuncEval.evalDuringException = m_thread->HasException();
+ m_evalDuringException = !!event->FuncEval.evalDuringException;
+ m_vmThreadOldExceptionHandle = m_thread->GetThreadExceptionRawObjectHandle();
+
+ // Corresponding Release() on DB_IPCE_FUNC_EVAL_COMPLETE.
+ // If a func eval is aborted, the LHS may not complete the abort
+ // immediately and hence we cant do a SendCleanup(). Hence, we maintain
+ // an extra ref-count to determine when this can be done.
+ AddRef();
+
+ HRESULT hr = m_thread->GetProcess()->SendIPCEvent(event, sizeof(DebuggerIPCEvent));
+
+ // If the send failed, return that failure.
+ if (FAILED(hr))
+ goto LExit;
+
+ _ASSERTE(event->type == DB_IPCE_FUNC_EVAL_SETUP_RESULT);
+
+ hr = event->hr;
+
+ // Memory has been allocated to hold info about each argument on
+ // the left side now, so copy the argument data over to the left
+ // side. No need to send another event, since the left side won't
+ // take any more action on this evaluation until the process is
+ // continued anyway.
+ //
+ // The type arguments come first, followed by up to two blobs of data
+ // for other arguments.
+ if (SUCCEEDED(hr))
+ {
+ EX_TRY
+ {
+ CORDB_ADDRESS argdata = event->FuncEvalSetupComplete.argDataArea;
+
+ if ((tyargData != NULL) && (tyargDataSize != 0))
+ {
+
+ TargetBuffer tb(argdata, tyargDataSize);
+ m_thread->GetProcess()->SafeWriteBuffer(tb, (const BYTE*) tyargData); // throws
+
+ argdata += tyargDataSize;
+ }
+
+ if ((argData1 != NULL) && (argData1Size != 0))
+ {
+ TargetBuffer tb(argdata, argData1Size);
+ m_thread->GetProcess()->SafeWriteBuffer(tb, (const BYTE*) argData1); // throws
+
+ argdata += argData1Size;
+ }
+
+ if ((argData2 != NULL) && (argData2Size != 0))
+ {
+ TargetBuffer tb(argdata, argData2Size);
+ m_thread->GetProcess()->SafeWriteBuffer(tb, (const BYTE*) argData2); // throws
+
+ argdata += argData2Size;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ }
+
+LExit:
+ if (tyargData)
+ {
+ delete [] tyargData;
+ }
+
+ // Save the key to the eval on the left side for future reference.
+ if (SUCCEEDED(hr))
+ {
+ m_debuggerEvalKey = event->FuncEvalSetupComplete.debuggerEvalKey;
+ m_thread->GetProcess()->IncrementOutstandingEvalCount();
+ }
+ else
+ {
+ // We dont expect to receive a DB_IPCE_FUNC_EVAL_COMPLETE, so just release here
+ Release();
+ }
+
+ return hr;
+}
+
+
+// Get the AppDomain that an object lives in.
+// This does not adjust any reference counts.
+// Returns NULL if we can't determine the appdomain, or if the value is known to be agile.
+CordbAppDomain * GetAppDomainFromValue(ICorDebugValue * pValue)
+{
+ // Unfortunately, there's no direct way to cast from an ICDValue to a CordbValue.
+ // So we need to QI for the culprit interfaces and check specifically.
+
+ {
+ RSExtSmartPtr<ICorDebugHandleValue> handleP;
+ pValue->QueryInterface(IID_ICorDebugHandleValue, (void**)&handleP);
+ if (handleP != NULL)
+ {
+ CordbHandleValue * chp = static_cast<CordbHandleValue *> (handleP.GetValue());
+ return chp->GetAppDomain();
+ }
+ }
+
+ {
+ RSExtSmartPtr<ICorDebugReferenceValue> refP;
+ pValue->QueryInterface(IID_ICorDebugReferenceValue, (void**)&refP);
+ if (refP != NULL)
+ {
+ CordbReferenceValue * crp = static_cast<CordbReferenceValue *> (refP.GetValue());
+ return crp->GetAppDomain();
+ }
+ }
+
+ {
+ RSExtSmartPtr<ICorDebugObjectValue> objP;
+ pValue->QueryInterface(IID_ICorDebugObjectValue, (void**)&objP);
+ if (objP != NULL)
+ {
+ CordbVCObjectValue * crp = static_cast<CordbVCObjectValue*> (objP.GetValue());
+ return crp->GetAppDomain();
+ }
+ }
+
+ // Assume nothing else has AD affinity.
+ return NULL;
+}
+
+HRESULT CordbEval::CallFunction(ICorDebugFunction *pFunction,
+ ULONG32 nArgs,
+ ICorDebugValue *pArgs[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ if (GetProcess()->GetShim() == NULL)
+ {
+ return E_NOTIMPL;
+ }
+ return CallParameterizedFunction(pFunction,0,NULL,nArgs,pArgs);
+}
+
+//-----------------------------------------------------------------------------
+// See if we can convert general Func-eval failure HRs (which are usually based on EE-invariants that
+// may be meaningless to the user) into a more specific user-friendly hr.
+// Doing the conversions here in the RS (instead of in the LS) makes it more clear that these
+// HRs definitely map to concepts described by the ICorDebugAPI instead of EE-invariants.
+// It also lets us clearly prioritize the HRs in case of ambiguity.
+//-----------------------------------------------------------------------------
+HRESULT CordbEval::FilterHR(HRESULT hr)
+{
+ // Currently, we only make CORDBG_E_ILLEGAL_AT_GC_UNSAFE_POINT more specific.
+ // If it's not that HR, then shortcut our work.
+ if (hr != CORDBG_E_ILLEGAL_AT_GC_UNSAFE_POINT)
+ {
+ return hr;
+ }
+
+ // In the case of conflicting HRs (if the func-eval fails for multiple reasons),
+ // we'll try to give priority to the more general HR.
+ // This communicates the quickest action for the user to be able to get to a
+ // func-eval friendly spot. It also means less churn in the hrs we return
+ // because specific hrs are more likely to change than general ones.
+
+ // If we got CORDBG_E_ILLEGAL_AT_GC_UNSAFE_POINT, check the common reasons.
+ // We'll use the Right-Side's intimate knowledge of the Left-Side to guess _why_
+ // it's a GC-unsafe spot, and then we'll communicate that back w/ a more meaningful HR.
+ // If GC safe-spots change, then these errors should be updated.
+
+
+ //
+ // Most likely is if we're in native code. Check that first.
+ //
+ // In V2, we do this check by checking if the leaf chain is native. Since we have no chain in Arrowhead,
+ // we can't do this check. Instead, we check whether the active frame is NULL or not. If it's NULL,
+ // then we are stopped in native code.
+ //
+ HRESULT hrTemp = S_OK;
+ if (GetProcess()->GetShim() != NULL)
+ {
+ // the V2 case
+ RSExtSmartPtr<ICorDebugChain> pChain;
+ hrTemp = m_thread->GetActiveChain(&pChain);
+ if (FAILED(hrTemp))
+ {
+ // just return the original HR if this call fails
+ return hr;
+ }
+
+ // pChain should never be NULL here, since we should have at least one thread start chain even if
+ // there is no managed code on the stack, but let's just be extra careful here.
+ if (pChain == NULL)
+ {
+ return hr;
+ }
+
+ BOOL fManagedChain;
+ hrTemp = pChain->IsManaged(&fManagedChain);
+ if (FAILED(hrTemp))
+ {
+ // just return the original HR if this call fails
+ return hr;
+ }
+
+ if (fManagedChain == FALSE)
+ {
+ return CORDBG_E_ILLEGAL_IN_NATIVE_CODE;
+ }
+ }
+
+ RSExtSmartPtr<ICorDebugFrame> pIFrame;
+ hrTemp = m_thread->GetActiveFrame(&pIFrame);
+ if (FAILED(hrTemp))
+ {
+ // just return the original HR if this call fails
+ return hr;
+ }
+
+ CordbFrame * pFrame = NULL;
+ pFrame = CordbFrame::GetCordbFrameFromInterface(pIFrame);
+
+ if (GetProcess()->GetShim() == NULL)
+ {
+ // the Arrowhead case
+ if (pFrame == NULL)
+ {
+ return CORDBG_E_ILLEGAL_IN_NATIVE_CODE;
+ }
+ }
+
+ // Next, check if we're in optimized code.
+ // Optimized code doesn't directly mean that func-evals are illegal; but it greatly
+ // increases the odds of being at a GC-unsafe point.
+ // We give this failure higher precedence than the "Is in prolog" failure.
+
+ if (pFrame != NULL)
+ {
+ CordbNativeFrame * pNativeFrame = pFrame->GetAsNativeFrame();
+ if (pNativeFrame != NULL)
+ {
+ CordbNativeCode * pCode = pNativeFrame->GetNativeCode();
+ if (pCode != NULL)
+ {
+ DWORD flags;
+ hrTemp = pCode->GetModule()->GetJITCompilerFlags(&flags);
+
+ if (SUCCEEDED(hrTemp))
+ {
+ if ((flags & CORDEBUG_JIT_DISABLE_OPTIMIZATION) != CORDEBUG_JIT_DISABLE_OPTIMIZATION)
+ {
+ return CORDBG_E_ILLEGAL_IN_OPTIMIZED_CODE;
+ }
+
+ } // GetCompilerFlags
+ } // Code
+
+ CordbJITILFrame * pILFrame = pNativeFrame->m_JITILFrame;
+ if (pILFrame != NULL)
+ {
+ if (pILFrame->m_mapping == MAPPING_PROLOG)
+ {
+ return CORDBG_E_ILLEGAL_IN_PROLOG;
+ }
+ }
+ } // Native Frame
+ }
+
+ // No filtering.
+ return hr;
+
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This routine calls a function with the given set of type arguments and actual arguments.
+// This is the jumping off point for func-eval.
+//
+// Arguments:
+// pFunction - The function to call.
+// nTypeArgs - The number of type-arguments for the method in rgpTypeArgs
+// rgpTypeArgs - An array of pointers to types.
+// nArgs - The number of arguments for the method in rgpArgs
+// rgpArgs - An array of pointers to values for the arguments to the method.
+//
+// Return Value:
+// HRESULT for the operation
+//
+HRESULT CordbEval::CallParameterizedFunction(ICorDebugFunction *pFunction,
+ ULONG32 nTypeArgs,
+ ICorDebugType * rgpTypeArgs[],
+ ULONG32 nArgs,
+ ICorDebugValue * rgpArgs[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ VALIDATE_POINTER_TO_OBJECT(pFunction, ICorDebugFunction *);
+
+ if (nArgs > 0)
+ {
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(rgpArgs, ICorDebugValue *, nArgs, true, true);
+ }
+
+ HRESULT hr = E_FAIL;
+
+ {
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // The LS will assume that all of the ICorDebugValues and ICorDebugTypes are in
+ // the same appdomain as the function. Verify this.
+ CordbAppDomain * pMethodAppDomain = (static_cast<CordbFunction *> (pFunction))->GetAppDomain();
+
+ if (!DoAppDomainsMatch(pMethodAppDomain, nTypeArgs, rgpTypeArgs, nArgs, rgpArgs))
+ {
+ return ErrWrapper(CORDBG_E_APPDOMAIN_MISMATCH);
+ }
+
+ // Callers are free to reuse an ICorDebugEval object for multiple
+ // evals. Since we create a Left Side eval representation each
+ // time, we need to be sure to clean it up now that we know we're
+ // done with it.
+ hr = SendCleanup();
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ // Must be locked to get a cookie
+ RsPtrHolder<CordbEval> hFuncEval(this);
+
+ if (hFuncEval.Ptr().IsNull())
+ {
+ return E_OUTOFMEMORY;
+ }
+ lockHolder.Release(); // release to send an IPC event.
+
+ // Remember the function that we're evaluating.
+ m_function = static_cast<CordbFunction *>(pFunction);
+ m_evalType = DB_IPCE_FET_NORMAL;
+
+
+ // Arrange the arguments into a form that the left side can deal
+ // with. We do this before starting the func eval setup to ensure
+ // that we can complete this step before mutating the left
+ // side.
+ DebuggerIPCE_FuncEvalArgData * pArgData = NULL;
+
+ if (nArgs > 0)
+ {
+ // We need to make the same type of array that the left side
+ // holds.
+ pArgData = new (nothrow) DebuggerIPCE_FuncEvalArgData[nArgs];
+
+ if (pArgData == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ // For each argument, convert its home into something the left
+ // side can understand.
+ for (unsigned int i = 0; i < nArgs; i++)
+ {
+ hr = GatherArgInfo(rgpArgs[i], &(pArgData[i]));
+
+ if (FAILED(hr))
+ {
+ delete [] pArgData;
+ return hr;
+ }
+ }
+ }
+
+ // Send over to the left side and get it to setup this eval.
+ DebuggerIPCEvent event;
+ m_thread->GetProcess()->InitIPCEvent(&event, DB_IPCE_FUNC_EVAL, true, m_thread->GetAppDomain()->GetADToken());
+
+ event.FuncEval.vmThreadToken = m_thread->m_vmThreadToken;
+ event.FuncEval.funcEvalType = m_evalType;
+ event.FuncEval.funcMetadataToken = m_function->GetMetadataToken();
+ event.FuncEval.vmDomainFile = m_function->GetModule()->GetRuntimeDomainFile();
+ event.FuncEval.funcEvalKey = hFuncEval.Ptr();
+ event.FuncEval.argCount = nArgs;
+ event.FuncEval.genericArgsCount = nTypeArgs;
+
+
+
+ hr = SendFuncEval(nTypeArgs,
+ rgpTypeArgs,
+ reinterpret_cast<void *>(pArgData),
+ sizeof(DebuggerIPCE_FuncEvalArgData) * nArgs,
+ NULL,
+ 0,
+ &event);
+
+ // Cleanup
+
+ if (pArgData)
+ {
+ delete [] pArgData;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ hFuncEval.SuppressRelease(); // Now LS owns.
+ }
+ }
+
+ // Convert from LS EE-centric failure code to something more friendly to end-users.
+ // Success HRs will not be converted.
+ hr = FilterHR(hr);
+
+ // Return any failure the Left Side may have told us about.
+ return hr;
+}
+
+BOOL CordbEval::DoAppDomainsMatch( CordbAppDomain * pAppDomain,
+ ULONG32 nTypes,
+ ICorDebugType *pTypes[],
+ ULONG32 nValues,
+ ICorDebugValue *pValues[] )
+{
+ _ASSERTE( !(pTypes == NULL && nTypes != 0) );
+ _ASSERTE( !(pValues == NULL && nValues != 0) );
+
+ // Make sure each value is in the appdomain.
+ for(unsigned int i = 0; i < nValues; i++)
+ {
+ // Assuming that only Ref Values have AD affinity
+ CordbAppDomain * pValueAppDomain = GetAppDomainFromValue( pValues[i] );
+
+ if ((pValueAppDomain != NULL) && (pValueAppDomain != pAppDomain))
+ {
+ LOG((LF_CORDB,LL_INFO1000, "CordbEval::DADM - AD mismatch. appDomain=0x%08x, param #%d=0x%08x, must fail.\n",
+ pAppDomain, i, pValueAppDomain));
+ return FALSE;
+ }
+ }
+
+ for(unsigned int i = 0; i < nTypes; i++ )
+ {
+ CordbType* t = static_cast<CordbType*>( pTypes[i] );
+ CordbAppDomain * pTypeAppDomain = t->GetAppDomain();
+
+ if( pTypeAppDomain != NULL && pTypeAppDomain != pAppDomain )
+ {
+ LOG((LF_CORDB,LL_INFO1000, "CordbEval::DADM - AD mismatch. appDomain=0x%08x, type param #%d=0x%08x, must fail.\n",
+ pAppDomain, i, pTypeAppDomain));
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+HRESULT CordbEval::NewObject(ICorDebugFunction *pConstructor,
+ ULONG32 nArgs,
+ ICorDebugValue *pArgs[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ return NewParameterizedObject(pConstructor,0,NULL,nArgs,pArgs);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This routine calls a constructor with the given set of type arguments and actual arguments.
+// This is the jumping off point for func-evaling "new".
+//
+// Arguments:
+// pConstructor - The function to call.
+// nTypeArgs - The number of type-arguments for the method in rgpTypeArgs
+// rgpTypeArgs - An array of pointers to types.
+// nArgs - The number of arguments for the method in rgpArgs
+// rgpArgs - An array of pointers to values for the arguments to the method.
+//
+// Return Value:
+// HRESULT for the operation
+//
+HRESULT CordbEval::NewParameterizedObject(ICorDebugFunction * pConstructor,
+ ULONG32 nTypeArgs,
+ ICorDebugType * rgpTypeArgs[],
+ ULONG32 nArgs,
+ ICorDebugValue * rgpArgs[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pConstructor, ICorDebugFunction *);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(rgpArgs, ICorDebugValue *, nArgs, true, true);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // The LS will assume that all of the ICorDebugValues and ICorDebugTypes are in
+ // the same appdomain as the constructor. Verify this.
+ CordbAppDomain * pConstructorAppDomain = (static_cast<CordbFunction *> (pConstructor))->GetAppDomain();
+
+ if (!DoAppDomainsMatch(pConstructorAppDomain, nTypeArgs, rgpTypeArgs, nArgs, rgpArgs))
+ {
+ return ErrWrapper(CORDBG_E_APPDOMAIN_MISMATCH);
+ }
+
+ // Callers are free to reuse an ICorDebugEval object for multiple
+ // evals. Since we create a Left Side eval representation each
+ // time, we need to be sure to clean it up now that we know we're
+ // done with it.
+ HRESULT hr = SendCleanup();
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ RsPtrHolder<CordbEval> hFuncEval(this);
+
+ if (hFuncEval.Ptr().IsNull())
+ {
+ return E_OUTOFMEMORY;
+ }
+ lockHolder.Release();
+
+ // Remember the function that we're evaluating.
+ m_function = static_cast<CordbFunction *>(pConstructor);
+ m_evalType = DB_IPCE_FET_NEW_OBJECT;
+
+ // Arrange the arguments into a form that the left side can deal
+ // with. We do this before starting the func eval setup to ensure
+ // that we can complete this step before mutating up the left
+ // side.
+ DebuggerIPCE_FuncEvalArgData * pArgData = NULL;
+
+ if (nArgs > 0)
+ {
+ // We need to make the same type of array that the left side
+ // holds.
+ pArgData = new (nothrow) DebuggerIPCE_FuncEvalArgData[nArgs];
+
+ if (pArgData == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ // For each argument, convert its home into something the left
+ // side can understand.
+ for (unsigned int i = 0; i < nArgs; i++)
+ {
+ hr = GatherArgInfo(rgpArgs[i], &(pArgData[i]));
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ }
+ }
+
+ // Send over to the left side and get it to setup this eval.
+ DebuggerIPCEvent event;
+
+ m_thread->GetProcess()->InitIPCEvent(&event, DB_IPCE_FUNC_EVAL, true, m_thread->GetAppDomain()->GetADToken());
+
+ event.FuncEval.vmThreadToken = m_thread->m_vmThreadToken;
+ event.FuncEval.funcEvalType = m_evalType;
+ event.FuncEval.funcMetadataToken = m_function->GetMetadataToken();
+ event.FuncEval.vmDomainFile = m_function->GetModule()->GetRuntimeDomainFile();
+ event.FuncEval.funcEvalKey = hFuncEval.Ptr();
+ event.FuncEval.argCount = nArgs;
+ event.FuncEval.genericArgsCount = nTypeArgs;
+
+ hr = SendFuncEval(nTypeArgs,
+ rgpTypeArgs,
+ reinterpret_cast<void *>(pArgData),
+ sizeof(DebuggerIPCE_FuncEvalArgData) * nArgs,
+ NULL,
+ 0,
+ &event);
+
+ // Cleanup
+
+ if (pArgData)
+ {
+ delete [] pArgData;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ hFuncEval.SuppressRelease(); // Now LS owns.
+ }
+
+
+ // Return any failure the Left Side may have told us about.
+ return hr;
+}
+
+HRESULT CordbEval::NewObjectNoConstructor(ICorDebugClass *pClass)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ return NewParameterizedObjectNoConstructor(pClass,0,NULL);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This routine creates an object of a certain type, but does not call the constructor
+// for the type on the object.
+//
+// Arguments:
+// pClass - the type of the object to create.
+// nTypeArgs - The number of type-arguments for the method in rgpTypeArgs
+// rgpTypeArgs - An array of pointers to types.
+//
+// Return Value:
+// HRESULT for the operation
+//
+HRESULT CordbEval::NewParameterizedObjectNoConstructor(ICorDebugClass * pClass,
+ ULONG32 nTypeArgs,
+ ICorDebugType * rgpTypeArgs[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pClass, ICorDebugClass *);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // The LS will assume that all of the ICorDebugTypes are in
+ // the same appdomain as the class. Verify this.
+ CordbAppDomain * pClassAppDomain = (static_cast<CordbClass *> (pClass))->GetAppDomain();
+
+ if (!DoAppDomainsMatch(pClassAppDomain, nTypeArgs, rgpTypeArgs, 0, NULL))
+ {
+ return ErrWrapper(CORDBG_E_APPDOMAIN_MISMATCH);
+ }
+
+ // Callers are free to reuse an ICorDebugEval object for multiple
+ // evals. Since we create a Left Side eval representation each
+ // time, we need to be sure to clean it up now that we know we're
+ // done with it.
+ HRESULT hr = SendCleanup();
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ RsPtrHolder<CordbEval> hFuncEval(this);
+ lockHolder.Release(); // release to send an IPC event.
+
+ if (hFuncEval.Ptr().IsNull())
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ // Remember the function that we're evaluating.
+ m_class = (CordbClass*)pClass;
+ m_evalType = DB_IPCE_FET_NEW_OBJECT_NC;
+
+ // Send over to the left side and get it to setup this eval.
+ DebuggerIPCEvent event;
+
+ m_thread->GetProcess()->InitIPCEvent(&event, DB_IPCE_FUNC_EVAL, true, m_thread->GetAppDomain()->GetADToken());
+
+ event.FuncEval.vmThreadToken = m_thread->m_vmThreadToken;
+ event.FuncEval.funcEvalType = m_evalType;
+ event.FuncEval.funcMetadataToken = mdMethodDefNil;
+ event.FuncEval.funcClassMetadataToken = (mdTypeDef)m_class->m_id;
+ event.FuncEval.vmDomainFile = m_class->GetModule()->GetRuntimeDomainFile();
+ event.FuncEval.funcEvalKey = hFuncEval.Ptr();
+ event.FuncEval.argCount = 0;
+ event.FuncEval.genericArgsCount = nTypeArgs;
+
+ hr = SendFuncEval(nTypeArgs, rgpTypeArgs, NULL, 0, NULL, 0, &event);
+
+ if (SUCCEEDED(hr))
+ {
+ hFuncEval.SuppressRelease(); // Now LS owns.
+ }
+
+ // Return any failure the Left Side may have told us about.
+ return hr;
+}
+
+/*
+ *
+ * NewString
+ *
+ * This routine is the interface function for ICorDebugEval::NewString
+ *
+ * Parameters:
+ * string - the string to create - must be null-terminated
+ *
+ * Return Value:
+ * HRESULT from the helper routines on RS and LS.
+ *
+ */
+HRESULT CordbEval::NewString(LPCWSTR string)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ return NewStringWithLength(string, (UINT)wcslen(string));
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This routine is the interface function for ICorDebugEval::NewStringWithLength.
+//
+// Arguments:
+// wszString - the string to create
+// iLength - the number of characters that you want to create. Can include embedded nulls.
+//
+// Return Value:
+// HRESULT for the operation
+//
+HRESULT CordbEval::NewStringWithLength(LPCWSTR wszString, UINT iLength)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(wszString, LPCWSTR); // Gotta have a string...
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // Callers are free to reuse an ICorDebugEval object for multiple
+ // evals. Since we create a Left Side eval representation each
+ // time, we need to be sure to clean it up now that we know we're
+ // done with it.
+ HRESULT hr = SendCleanup();
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ RsPtrHolder<CordbEval> hFuncEval(this);
+ lockHolder.Release(); // release to send an IPC event.
+
+ if (hFuncEval.Ptr().IsNull())
+ {
+ return E_OUTOFMEMORY;
+ }
+
+
+ // Length of the string? Don't account for null as COMString::NewString is length-based
+ SIZE_T cbString = iLength * sizeof(WCHAR);
+
+ // Remember that we're doing a func eval for a new string.
+ m_function = NULL;
+ m_evalType = DB_IPCE_FET_NEW_STRING;
+
+ // Send over to the left side and get it to setup this eval.
+ DebuggerIPCEvent event;
+
+ m_thread->GetProcess()->InitIPCEvent(&event, DB_IPCE_FUNC_EVAL, true, m_thread->GetAppDomain()->GetADToken());
+
+ event.FuncEval.vmThreadToken = m_thread->m_vmThreadToken;
+ event.FuncEval.funcEvalType = m_evalType;
+ event.FuncEval.funcEvalKey = hFuncEval.Ptr();
+ event.FuncEval.stringSize = cbString;
+
+ // Note: no function or module here...
+ event.FuncEval.funcMetadataToken = mdMethodDefNil;
+ event.FuncEval.funcClassMetadataToken = mdTypeDefNil;
+ event.FuncEval.vmDomainFile = VMPTR_DomainFile::NullPtr();
+ event.FuncEval.argCount = 0;
+ event.FuncEval.genericArgsCount = 0;
+ event.FuncEval.genericArgsNodeCount = 0;
+
+ hr = SendFuncEval(0, NULL, (void *)wszString, (unsigned int)cbString, NULL, 0, &event);
+
+ if (SUCCEEDED(hr))
+ {
+ hFuncEval.SuppressRelease(); // Now LS owns.
+ }
+
+ // Return any failure the Left Side may have told us about.
+ return hr;
+}
+
+HRESULT CordbEval::NewArray(CorElementType elementType,
+ ICorDebugClass *pElementClass,
+ ULONG32 rank,
+ ULONG32 dims[],
+ ULONG32 lowBounds[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pElementClass, ICorDebugClass *);
+
+ // If you want a class, you gotta pass a class.
+ if ((elementType == ELEMENT_TYPE_CLASS) && (pElementClass == NULL))
+ return E_INVALIDARG;
+
+ // If you want an array of objects, then why pass a class?
+ if ((elementType == ELEMENT_TYPE_OBJECT) && (pElementClass != NULL))
+ return E_INVALIDARG;
+
+ // Arg check...
+ if (elementType == ELEMENT_TYPE_VOID)
+ return E_INVALIDARG;
+
+ CordbType *typ;
+ HRESULT hr = S_OK;
+ hr = CordbType::MkUnparameterizedType(m_thread->GetAppDomain(), elementType, (CordbClass *) pElementClass, &typ);
+
+ if (FAILED(hr))
+ return hr;
+
+ return NewParameterizedArray(typ, rank,dims,lowBounds);
+
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// This routine sets up a func-eval to create a new array of the given type.
+//
+// Arguments:
+// pElementType - The type of each element of the array.
+// rank - Rank of the array.
+// rgDimensions - Array of dimensions for the array.
+// rmLowBounds - Array of lower bounds on the array.
+//
+// Return Value:
+// HRESULT for the operation
+//
+HRESULT CordbEval::NewParameterizedArray(ICorDebugType * pElementType,
+ ULONG32 rank,
+ ULONG32 rgDimensions[],
+ ULONG32 rgLowBounds[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pElementType, ICorDebugType *);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+
+ // Callers are free to reuse an ICorDebugEval object for multiple evals. Since we create a Left Side eval
+ // representation each time, we need to be sure to clean it up now that we know we're done with it.
+ HRESULT hr = SendCleanup();
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ // Arg check...
+ if ((rank == 0) || (rgDimensions == NULL))
+ {
+ return E_INVALIDARG;
+ }
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ RsPtrHolder<CordbEval> hFuncEval(this);
+ lockHolder.Release(); // release to send an IPC event.
+
+ if (hFuncEval.Ptr().IsNull())
+ {
+ return E_OUTOFMEMORY;
+ }
+
+
+ // Remember that we're doing a func eval for a new string.
+ m_function = NULL;
+ m_evalType = DB_IPCE_FET_NEW_ARRAY;
+
+ // Send over to the left side and get it to setup this eval.
+ DebuggerIPCEvent event;
+
+ m_thread->GetProcess()->InitIPCEvent(&event, DB_IPCE_FUNC_EVAL, true, m_thread->GetAppDomain()->GetADToken());
+
+ event.FuncEval.vmThreadToken = m_thread->m_vmThreadToken;
+ event.FuncEval.funcEvalType = m_evalType;
+ event.FuncEval.funcEvalKey = hFuncEval.Ptr();
+
+ event.FuncEval.arrayRank = rank;
+
+ // Note: no function or module here...
+ event.FuncEval.funcMetadataToken = mdMethodDefNil;
+ event.FuncEval.funcClassMetadataToken = mdTypeDefNil;
+ event.FuncEval.vmDomainFile = VMPTR_DomainFile::NullPtr();
+ event.FuncEval.argCount = 0;
+ event.FuncEval.genericArgsCount = 1;
+
+ // Prefast overflow sanity check.
+ S_UINT32 allocSize = S_UINT32(rank) * S_UINT32(sizeof(SIZE_T));
+
+ if (allocSize.IsOverflow())
+ {
+ return E_INVALIDARG;
+ }
+
+ // Just in case sizeof(SIZE_T) != sizeof(ULONG32)
+ SIZE_T * rgDimensionsSizeT = reinterpret_cast<SIZE_T *>(_alloca(allocSize.Value()));
+
+ for (unsigned int i = 0; i < rank; i++)
+ {
+ rgDimensionsSizeT[i] = rgDimensions[i];
+ }
+
+ ICorDebugType * rgpGenericArgs[1];
+
+ rgpGenericArgs[0] = pElementType;
+
+ // @dbgtodo funceval : lower bounds were ignored in V1 - fix this.
+ hr = SendFuncEval(1,
+ rgpGenericArgs,
+ reinterpret_cast<void *>(rgDimensionsSizeT),
+ rank * sizeof(SIZE_T),
+ NULL, // (void*)lowBounds,
+ 0, // ((lowBounds == NULL) ? 0 : rank * sizeof(SIZE_T)),
+ &event);
+
+ if (SUCCEEDED(hr))
+ {
+ hFuncEval.SuppressRelease(); // Now LS owns.
+ }
+
+ // Return any failure the Left Side may have told us about.
+ return hr;
+}
+
+HRESULT CordbEval::IsActive(BOOL *pbActive)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pbActive, BOOL *);
+
+ *pbActive = (m_complete == true);
+ return S_OK;
+}
+
+/*
+ * This routine submits an abort request to the LS.
+ *
+ * Parameters:
+ * None.
+ *
+ * Returns:
+ * The HRESULT as returned by the LS.
+ *
+ */
+
+HRESULT
+CordbEval::Abort(
+ void
+ )
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ ATT_ALLOW_LIVE_DO_STOPGO(GetProcess());
+
+
+ //
+ // No need to abort if its already completed.
+ //
+ if (m_complete)
+ {
+ return S_OK;
+ }
+
+
+ //
+ // Can't abort if its never even been started.
+ //
+ if (m_debuggerEvalKey == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ CORDBRequireProcessStateOK(m_thread->GetProcess());
+
+ //
+ // Send over to the left side to get the eval aborted.
+ //
+ DebuggerIPCEvent event;
+
+ m_thread->GetProcess()->InitIPCEvent(&event,
+ DB_IPCE_FUNC_EVAL_ABORT,
+ true,
+ m_thread->GetAppDomain()->GetADToken()
+ );
+
+ event.FuncEvalAbort.debuggerEvalKey = m_debuggerEvalKey;
+
+ HRESULT hr = m_thread->GetProcess()->SendIPCEvent(&event,
+ sizeof(DebuggerIPCEvent)
+ );
+
+
+ //
+ // If the send failed, return that failure.
+ //
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ _ASSERTE(event.type == DB_IPCE_FUNC_EVAL_ABORT_RESULT);
+
+ //
+ // Since we may have
+ // overwritten anything (objects, code, etc), we should mark
+ // everything as needing to be re-cached.
+ //
+ m_thread->GetProcess()->m_continueCounter++;
+
+ hr = event.hr;
+
+ return hr;
+}
+
+HRESULT CordbEval::GetResult(ICorDebugValue **ppResult)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppResult, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ *ppResult = NULL;
+
+ // Is the evaluation complete?
+ if (!m_complete)
+ {
+ return CORDBG_E_FUNC_EVAL_NOT_COMPLETE;
+ }
+
+ if (m_aborted)
+ {
+ return CORDBG_S_FUNC_EVAL_ABORTED;
+ }
+
+ // Does the evaluation have a result?
+ if (m_resultType.elementType == ELEMENT_TYPE_VOID)
+ {
+ return CORDBG_S_FUNC_EVAL_HAS_NO_RESULT;
+ }
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Make a ICorDebugValue out of the result.
+ CordbAppDomain * pAppDomain;
+
+ if (!m_resultAppDomainToken.IsNull())
+ {
+ // @dbgtodo funceval - push this up
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ pAppDomain = m_thread->GetProcess()->LookupOrCreateAppDomain(m_resultAppDomainToken);
+ }
+ else
+ {
+ pAppDomain = m_thread->GetAppDomain();
+ }
+ PREFIX_ASSUME(pAppDomain != NULL);
+
+ CordbType * pType = NULL;
+ hr = CordbType::TypeDataToType(pAppDomain, &m_resultType, &pType);
+ IfFailThrow(hr);
+
+ bool resultInHandle =
+ ((m_resultType.elementType == ELEMENT_TYPE_CLASS) ||
+ (m_resultType.elementType == ELEMENT_TYPE_SZARRAY) ||
+ (m_resultType.elementType == ELEMENT_TYPE_OBJECT) ||
+ (m_resultType.elementType == ELEMENT_TYPE_ARRAY) ||
+ (m_resultType.elementType == ELEMENT_TYPE_STRING));
+
+ if (resultInHandle)
+ {
+ // if object handle is null here, something has gone wrong!!!
+ _ASSERTE(!m_vmObjectHandle.IsNull());
+
+ if (m_pHandleValue == NULL)
+ {
+ // Create CordbHandleValue for result
+ RSInitHolder<CordbHandleValue> pHandleValue(new CordbHandleValue(pAppDomain, pType, HANDLE_STRONG));
+
+ // Initialize the handle value object. The HandleValue will now
+ // own the m_objectHandle.
+ hr = pHandleValue->Init(m_vmObjectHandle);
+
+ if (!SUCCEEDED(hr))
+ {
+ // Neuter the new object we've been working on. This will
+ // call Dispose(), and that will go back to the left side
+ // and free the handle that we got above.
+ pHandleValue->NeuterLeftSideResources();
+
+ //
+
+ // Do not delete chv here. The neuter list still has a reference to it, and it will be cleaned up automatically.
+ ThrowHR(hr);
+ }
+ m_pHandleValue.Assign(pHandleValue);
+ pHandleValue.ClearAndMarkDontNeuter();
+ }
+
+ // This AddRef is for caller to release
+ //
+ *ppResult = m_pHandleValue;
+ m_pHandleValue->ExternalAddRef();
+ }
+ else if (CorIsPrimitiveType(m_resultType.elementType) && (m_resultType.elementType != ELEMENT_TYPE_STRING))
+ {
+ // create a CordbGenericValue flagged as a literal
+ hr = CordbEval::CreatePrimitiveLiteral(pType, ppResult);
+ }
+ else
+ {
+ TargetBuffer remoteValue(m_resultAddr, CordbValue::GetSizeForType(pType, kBoxed));
+ // Now that we have the module, go ahead and create the result.
+
+ CordbValue::CreateValueByType(pAppDomain,
+ pType,
+ true,
+ remoteValue,
+ MemoryRange(NULL, 0),
+ NULL,
+ ppResult); // throws
+ }
+
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT CordbEval::GetThread(ICorDebugThread **ppThread)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppThread, ICorDebugThread **);
+
+ *ppThread = static_cast<ICorDebugThread*> (m_thread);
+ m_thread->ExternalAddRef();
+
+ return S_OK;
+}
+
+// Create a RS literal for primitive type funceval result. In case the result is used as an argument for
+// another funceval, we need to make sure that we're not relying on the LS value, which will be freed and
+// thus unavailable.
+// Arguments:
+// input: pType - CordbType instance representing the type of the primitive value
+// output: ppValue - ICorDebugValue representing the result as a literal CordbGenericValue
+// Return Value:
+// hr: may fail for OOM, ReadProcessMemory failures
+HRESULT CordbEval::CreatePrimitiveLiteral(CordbType * pType,
+ ICorDebugValue ** ppValue)
+{
+ CordbGenericValue * gv = NULL;
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Create a generic value.
+ gv = new CordbGenericValue(pType);
+
+ // initialize the local value
+ int size = CordbValue::GetSizeForType(pType, kBoxed);
+ if (size > 8)
+ {
+ ThrowHR(HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER));
+ }
+ TargetBuffer remoteValue(m_resultAddr, size);
+ BYTE localBuffer[8] = {0};
+
+ GetProcess()->SafeReadBuffer (remoteValue, localBuffer);
+ gv->SetValue(localBuffer);
+
+ // Do not delete gv here even if the initialization fails.
+ // The neuter list still has a reference to it, and it will be cleaned up automatically.
+ gv->ExternalAddRef();
+ *ppValue = (ICorDebugValue*)(ICorDebugGenericValue*)gv;
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT CordbEval::CreateValue(CorElementType elementType,
+ ICorDebugClass *pElementClass,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ CordbType *typ;
+
+ // @todo: only primitive values right now.
+ if (((elementType < ELEMENT_TYPE_BOOLEAN) ||
+ (elementType > ELEMENT_TYPE_R8)) &&
+ !(elementType == ELEMENT_TYPE_CLASS))
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+
+ // MkUnparameterizedType now works if you give it ELEMENT_TYPE_CLASS and
+ // a null pElementClass - it returns the type for ELEMENT_TYPE_OBJECT.
+
+ hr = CordbType::MkUnparameterizedType(m_thread->GetAppDomain(), elementType, (CordbClass *) pElementClass, &typ);
+
+ if (FAILED(hr))
+ return hr;
+
+ return CreateValueForType(typ, ppValue);
+}
+
+// create an ICDValue to represent a value for a funceval
+// Arguments:
+// input: pIType - the type for the new value
+// output: ppValue - the new ICDValue. If there is a failure of some sort, this will be NULL
+// ReturnValue: S_OK on success (ppValue should contain a non-NULL address)
+// E_OUTOFMEMORY, if we can't allocate space for the new ICDValue
+// Notes: We can also get read process memory errors or E_INVALIDARG if errors occur during initialization,
+// but in that case, we don't return the hresult. Instead, we just never update ppValue, so it will still be
+// NULL on exit.
+HRESULT CordbEval::CreateValueForType(ICorDebugType * pIType,
+ ICorDebugValue ** ppValue)
+{
+ HRESULT hr = S_OK;
+
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ VALIDATE_POINTER_TO_OBJECT(pIType, ICorDebugType*);
+
+ *ppValue = NULL;
+ CordbType *pType = static_cast<CordbType *> (pIType);
+
+ CorElementType elementType = pType->m_elementType;
+ // We don't support IntPtr and UIntPtr types as arguments here, but we do support these types as results
+ // (see code:CordbEval::CreatePrimitiveLiteral) and we have changed the LS to support them as well
+ if (((elementType < ELEMENT_TYPE_BOOLEAN) ||
+ (elementType > ELEMENT_TYPE_R8)) &&
+ !((elementType == ELEMENT_TYPE_CLASS) || (elementType == ELEMENT_TYPE_OBJECT)))
+ return E_INVALIDARG;
+
+ // Note: ELEMENT_TYPE_OBJECT is what we'll get for the null reference case, so allow that.
+ if ((elementType == ELEMENT_TYPE_CLASS) || (elementType == ELEMENT_TYPE_OBJECT))
+ {
+ EX_TRY
+ {
+ // create a reference value
+ CordbReferenceValue *rv = new CordbReferenceValue(pType);
+
+ if (SUCCEEDED(rv->InitRef(MemoryRange(NULL,0))))
+ {
+ // Do not delete rv here even if the initialization fails.
+ // The neuter list still has a reference to it, and it will be cleaned up automatically.
+ rv->ExternalAddRef();
+ *ppValue = (ICorDebugValue*)(ICorDebugReferenceValue*)rv;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ }
+ else
+ {
+ CordbGenericValue * gv = NULL;
+ EX_TRY
+ {
+ // Create a generic value.
+ gv = new CordbGenericValue(pType);
+
+ gv->Init(MemoryRange(NULL,0));
+ // Do not delete gv here even if the initialization fails.
+ // The neuter list still has a reference to it, and it will be cleaned up automatically.
+ gv->ExternalAddRef();
+ *ppValue = (ICorDebugValue*)(ICorDebugGenericValue*)gv;
+ }
+ EX_CATCH_HRESULT(hr);
+ }
+
+ return hr;
+} // CordbEval::CreateValueForType
+
+
+/* ------------------------------------------------------------------------- *
+ * CordbEval2
+ *
+ * Extentions to the CordbEval class for Whidbey
+ *
+ * ------------------------------------------------------------------------- */
+
+
+/*
+ * This routine submits a rude abort request to the LS.
+ *
+ * Parameters:
+ * None.
+ *
+ * Returns:
+ * The HRESULT as returned by the LS.
+ *
+ */
+
+HRESULT
+CordbEval::RudeAbort(
+ void
+ )
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+
+ ATT_ALLOW_LIVE_DO_STOPGO(GetProcess());
+
+ //
+ // No need to abort if its already completed.
+ //
+ if (m_complete)
+ {
+ return S_OK;
+ }
+
+ //
+ // Can't abort if its never even been started.
+ //
+ if (m_debuggerEvalKey == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ CORDBRequireProcessStateOK(m_thread->GetProcess());
+
+ //
+ // Send over to the left side to get the eval aborted.
+ //
+ DebuggerIPCEvent event;
+
+ m_thread->GetProcess()->InitIPCEvent(&event,
+ DB_IPCE_FUNC_EVAL_RUDE_ABORT,
+ true,
+ m_thread->GetAppDomain()->GetADToken()
+ );
+
+ event.FuncEvalRudeAbort.debuggerEvalKey = m_debuggerEvalKey;
+
+ HRESULT hr = m_thread->GetProcess()->SendIPCEvent(&event,
+ sizeof(DebuggerIPCEvent)
+ );
+
+ //
+ // If the send failed, return that failure.
+ //
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ _ASSERTE(event.type == DB_IPCE_FUNC_EVAL_RUDE_ABORT_RESULT);
+
+ //
+ // Since we may have
+ // overwritten anything (objects, code, etc), we should mark
+ // everything as needing to be re-cached.
+ //
+ m_thread->GetProcess()->m_continueCounter++;
+
+ hr = event.hr;
+
+ return hr;
+}
+
+
+
+
+/* ------------------------------------------------------------------------- *
+ * CodeParameter Enumerator class
+ * ------------------------------------------------------------------------- */
+
+CordbCodeEnum::CordbCodeEnum(unsigned int cCodes, RSSmartPtr<CordbCode> * ppCodes) :
+ CordbBase(NULL, 0)
+{
+ // Because the array is of smart-ptrs, the elements are already reffed
+ // We now take ownership of the array itself too.
+ m_ppCodes = ppCodes;
+
+ m_iCurrent = 0;
+ m_iMax = cCodes;
+}
+
+
+CordbCodeEnum::~CordbCodeEnum()
+{
+ // This will invoke the SmartPtr dtors on each element and call release.
+ delete [] m_ppCodes;
+}
+
+HRESULT CordbCodeEnum::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugEnum)
+ *pInterface = static_cast<ICorDebugEnum*>(this);
+ else if (id == IID_ICorDebugCodeEnum)
+ *pInterface = static_cast<ICorDebugCodeEnum*>(this);
+ else if (id == IID_IUnknown)
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugCodeEnum*>(this));
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbCodeEnum::Skip(ULONG celt)
+{
+ HRESULT hr = E_FAIL;
+ if ( (m_iCurrent+celt) < m_iMax ||
+ celt == 0)
+ {
+ m_iCurrent += celt;
+ hr = S_OK;
+ }
+
+ return hr;
+}
+
+HRESULT CordbCodeEnum::Reset()
+{
+ m_iCurrent = 0;
+ return S_OK;
+}
+
+HRESULT CordbCodeEnum::Clone(ICorDebugEnum **ppEnum)
+{
+ VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorDebugEnum **);
+ (*ppEnum) = NULL;
+
+ HRESULT hr = S_OK;
+
+ // Create a new copy of the array because the CordbCodeEnum will
+ // take ownership of it.
+ RSSmartPtr<CordbCode> * ppCodes = new (nothrow) RSSmartPtr<CordbCode> [m_iMax];
+ if (ppCodes == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+ for(UINT i = 0; i < m_iMax; i++)
+ {
+ ppCodes[i].Assign(m_ppCodes[i]);
+ }
+
+
+ CordbCodeEnum *pCVE = new (nothrow) CordbCodeEnum( m_iMax, ppCodes);
+ if ( pCVE == NULL )
+ {
+ delete [] ppCodes;
+ hr = E_OUTOFMEMORY;
+ goto LExit;
+ }
+
+ pCVE->ExternalAddRef();
+ (*ppEnum) = (ICorDebugEnum*)pCVE;
+
+LExit:
+ return hr;
+}
+
+HRESULT CordbCodeEnum::GetCount(ULONG *pcelt)
+{
+ VALIDATE_POINTER_TO_OBJECT(pcelt, ULONG *);
+
+ if( pcelt == NULL)
+ return E_INVALIDARG;
+
+ (*pcelt) = m_iMax;
+ return S_OK;
+}
+
+//
+// In the event of failure, the current pointer will be left at
+// one element past the troublesome element. Thus, if one were
+// to repeatedly ask for one element to iterate through the
+// array, you would iterate exactly m_iMax times, regardless
+// of individual failures.
+HRESULT CordbCodeEnum::Next(ULONG celt, ICorDebugCode *values[], ULONG *pceltFetched)
+{
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(values, ICorDebugClass *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched, ULONG *);
+
+ if ((pceltFetched == NULL) && (celt != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ if (celt == 0)
+ {
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = 0;
+ }
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+
+ int iMax = min( m_iMax, m_iCurrent+celt);
+ int i;
+
+ for (i = m_iCurrent; i < iMax; i++)
+ {
+ values[i-m_iCurrent] = m_ppCodes[i];
+ values[i-m_iCurrent]->AddRef();
+ }
+
+ int count = (i - m_iCurrent);
+
+ if ( FAILED( hr ) )
+ { //we failed: +1 pushes us past troublesome element
+ m_iCurrent += 1 + count;
+ }
+ else
+ {
+ m_iCurrent += count;
+ }
+
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = count;
+ }
+
+ //
+ // If we reached the end of the enumeration, but not the end
+ // of the number of requested items, we return S_FALSE.
+ //
+ if (((ULONG)count) < celt)
+ {
+ return S_FALSE;
+ }
+
+ return hr;
+}
+
diff --git a/src/debug/di/rstype.cpp b/src/debug/di/rstype.cpp
new file mode 100644
index 0000000000..b183fdf39e
--- /dev/null
+++ b/src/debug/di/rstype.cpp
@@ -0,0 +1,2815 @@
+// 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.
+//*****************************************************************************
+// File: rstype.cpp
+//
+
+//
+// Define implementation of ICorDebugType
+//*****************************************************************************
+
+
+#include "stdafx.h"
+#include "winbase.h"
+#include "corpriv.h"
+
+
+//-----------------------------------------------------------------------------
+// Public method to get the static field from a type.
+//
+// Parameters:
+// fieldDef - metadata token for which field on this type to retrieve.
+// pFrame - context for Thread/AppDomains statics.
+// ppValue - OUT: out-parameter to get value.
+//
+// Returns:
+// S_OK on success.
+//
+HRESULT CordbType::GetStaticFieldValue(mdFieldDef fieldDef,
+ ICorDebugFrame * pFrame,
+ ICorDebugValue ** ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+
+ IMetaDataImport * pImport = NULL;
+
+ EX_TRY
+ {
+ // Ensure we were actually passed a mdFieldDef. This is especially useful to protect
+ // against an accidental mdPropertyDef, because properties look like fields.
+ //
+ if (TypeFromToken(fieldDef) != mdtFieldDef)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ pImport = m_pClass->GetModule()->GetMetaDataImporter(); // throws
+
+ if (((m_elementType != ELEMENT_TYPE_CLASS) && (m_elementType != ELEMENT_TYPE_VALUETYPE)) || (m_pClass == NULL))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+
+
+ BOOL fSyncBlockField = FALSE;
+
+ // If non generic type, then degenerate to CordbClass implementation.
+ if (m_inst.m_cInst == 0)
+ {
+ hr = m_pClass->GetStaticFieldValue(fieldDef, pFrame, ppValue);
+ }
+ else
+ {
+ *ppValue = NULL;
+
+ // Validate the token.
+ if (!pImport->IsValidToken(fieldDef))
+ {
+ ThrowHR(hr = E_INVALIDARG);
+ }
+
+ // Make sure we have enough info about the class.
+ hr = Init(FALSE);
+ IfFailThrow(hr);
+
+ // Lookup the field given its metadata token.
+ FieldData * pFieldData;
+
+ hr = GetFieldInfo(fieldDef, &pFieldData);
+
+ if (hr == CORDBG_E_ENC_HANGING_FIELD)
+ {
+ // Generics + EnC is Not supported.
+ hr = CORDBG_E_STATIC_VAR_NOT_AVAILABLE;
+ }
+
+ IfFailThrow(hr);
+
+ hr = CordbClass::GetStaticFieldValue2(m_pClass->GetModule(),
+ pFieldData,
+ fSyncBlockField,
+ &m_inst,
+ pFrame,
+ ppValue);
+ // fall through to translate HR
+ }
+
+ }
+ EX_CATCH_HRESULT(hr);
+ if (pImport != NULL)
+ {
+ hr = CordbClass::PostProcessUnavailableHRESULT(hr, pImport, fieldDef);
+ }
+ return hr;
+
+}
+
+// Combine E_T_s and rank together to get an id for the m_sharedtypes table
+#define CORDBTYPE_ID(elementType,rank) ((unsigned int) elementType * (rank + 1) + 1)
+
+
+//-----------------------------------------------------------------------------
+// Constructor
+// Builds a CordbType around a primitive.
+//-----------------------------------------------------------------------------
+CordbType::CordbType(CordbAppDomain *appdomain, CorElementType et, unsigned int rank)
+: CordbBase(appdomain->GetProcess(), CORDBTYPE_ID(et,rank) , enumCordbType),
+ m_elementType(et),
+ m_appdomain(appdomain),
+ m_pClass(NULL),
+ m_rank(rank),
+ m_spinetypes(2),
+ m_objectSize(0),
+ m_fieldInfoNeedsInit(TRUE)
+{
+ m_typeHandleExact = VMPTR_TypeHandle::NullPtr();
+
+ _ASSERTE(m_elementType != ELEMENT_TYPE_VALUETYPE);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ m_appdomain->AddToTypeList(this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+}
+
+//-----------------------------------------------------------------------------
+// Constructor
+// Builds a CordbType around a class. This is an Open CordbType.
+// For a generic type, this CordbType will not have the generic parameters,
+// but it will be a subordinate type to another Closed (instantiated) CordbType
+//-----------------------------------------------------------------------------
+CordbType::CordbType(CordbAppDomain *appdomain, CorElementType et, CordbClass *cls)
+: CordbBase(appdomain->GetProcess(), et, enumCordbType),
+ m_elementType(et),
+ m_appdomain(appdomain),
+ m_pClass(cls),
+ m_rank(0),
+ m_spinetypes(2),
+ m_objectSize(0),
+ m_fieldInfoNeedsInit(TRUE)
+{
+ m_typeHandleExact = VMPTR_TypeHandle::NullPtr();
+ _ASSERTE(m_elementType != ELEMENT_TYPE_VALUETYPE);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ m_appdomain->AddToTypeList(this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+}
+
+//-----------------------------------------------------------------------------
+// Constructor
+// Builds a Partial-Type, instantiation is tycon's instantation plus tyarg.
+// Eg, if tycon is "Dict<int>", and tyarg is "string", then this yields
+// "Dict<int, string>"
+//-----------------------------------------------------------------------------
+CordbType::CordbType(CordbType *tycon, CordbType *tyarg)
+: CordbBase(tycon->GetProcess(), (UINT_PTR)tyarg, enumCordbType),
+ m_elementType(tycon->m_elementType),
+ m_appdomain(tycon->m_appdomain),
+ m_pClass(tycon->m_pClass),
+ m_rank(tycon->m_rank),
+ m_spinetypes(2),
+ m_objectSize(0),
+ m_fieldInfoNeedsInit(TRUE)
+ // tyarg is added as part of instantiation -see below...
+{
+ m_typeHandleExact = VMPTR_TypeHandle::NullPtr();
+ _ASSERTE(m_elementType != ELEMENT_TYPE_VALUETYPE);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ m_appdomain->AddToTypeList(this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+}
+
+
+ULONG STDMETHODCALLTYPE CordbType::AddRef()
+{
+ // This AddRef/Release pair creates a weak ref-counted reference to the class for this
+ // type. This avoids a circularity in ref-counted references between
+ // classes and types - if we had a circularity the objects would never get
+ // collected at all...
+ //if (m_class)
+ // m_class->AddRef();
+ return (BaseAddRef());
+}
+ULONG STDMETHODCALLTYPE CordbType::Release()
+{
+ // if (m_class)
+ // m_class->Release();
+ return (BaseRelease());
+}
+
+/*
+ A list of which resources owened by this object are accounted for.
+
+ HANDLED:
+ CordbClass *m_class; Weakly referenced by increasing count directly in AddRef() and Release()
+ Instantiation m_inst; // Internal pointers to CordbClass released in CordbClass::Neuter
+ CordbHashTable m_spinetypes; // Neutered
+ CordbHashTable m_fields; // Deleted in ~CordbType
+*/
+
+//-----------------------------------------------------------------------------
+// Cleanup memory for CordbTypes.
+//-----------------------------------------------------------------------------
+CordbType::~CordbType()
+{
+ _ASSERTE(IsNeutered());
+}
+
+//-----------------------------------------------------------------------------
+// Neutered by CordbModule
+// See CordbBase::Neuter for neuter semantics.
+//-----------------------------------------------------------------------------
+void CordbType::Neuter()
+{
+ _ASSERTE(GetProcess()->GetProcessLock()->HasLock());
+
+ // We have some direct releases below. If we call Neuter twice, that could
+ // result in double-releases. So check if we're already neutered, and
+ // if so, no work left to do.
+ if (IsNeutered())
+ {
+ return;
+ }
+
+ for (unsigned int i = 0; i < m_inst.m_cInst; i++)
+ {
+ m_inst.m_ppInst[i]->Release();
+ }
+
+ m_spinetypes.NeuterAndClear(GetProcess()->GetProcessLock());
+
+ if(m_inst.m_ppInst)
+ {
+ delete [] m_inst.m_ppInst;
+ m_inst.m_ppInst = NULL;
+ }
+ m_fieldList.Dealloc();
+
+ CordbBase::Neuter();
+}
+
+//-----------------------------------------------------------------------------
+// Public method for IUnknown::QueryInterface.
+// Has standard QI semantics.
+//-----------------------------------------------------------------------------
+HRESULT CordbType::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugType)
+ *pInterface = static_cast<ICorDebugType*>(this);
+ else if (id == IID_IUnknown)
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugType*>(this));
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+
+//-----------------------------------------------------------------------------
+// Make a simple type with no type arguments by specifying a CorElementType,
+// e.g. ELEMENT_TYPE_I1
+//
+// CordbType's are effectively a full representation of
+// structured types. They are hashed via a combination of their constituent
+// elements (e.g. CordbClass's or CordbType's) and the element type that is used to
+// combine the elements, or if they have no elements then via
+// the element type alone. The following is used to create all CordbTypes.
+//
+// An AppDomain holds a cache of CordbTypes for each of the basic CorElementTypes.
+//
+// Arguments:
+// pAppDomain - the AppDomain that the type lives in.
+// elementType - element_type to create the CordbType around.
+// ppResultType - OUT: out-parameter to get the CordbType.
+//
+// Returns:
+// S_OK on success.
+//
+//
+HRESULT CordbType::MkType(CordbAppDomain * pAppDomain,
+ CorElementType elementType,
+ CordbType ** ppResultType)
+{
+ _ASSERTE(pAppDomain != NULL);
+ _ASSERTE(ppResultType != NULL);
+
+ RSLockHolder lockHolder(pAppDomain->GetProcess()->GetProcessLock());
+
+ // Some points in the code create types via element types that are clearly objects but where
+ // no further information is given. This is always done when creating a CordbValue, prior
+ // to actually going over to the EE to discover what kind of value it is. In all these
+ // cases we can just use the type for "Object" - the code for dereferencing the value
+ // will update the type correctly once it has been determined. We don't do this for ELEMENT_TYPE_STRING
+ // as that is actually a NullaryType and at other places in the code we will want exactly that type!
+ if ((elementType == ELEMENT_TYPE_CLASS) ||
+ (elementType == ELEMENT_TYPE_SZARRAY) ||
+ (elementType == ELEMENT_TYPE_ARRAY))
+ {
+ elementType = ELEMENT_TYPE_OBJECT;
+ }
+
+ switch (elementType)
+ {
+ // this one is included because we need a "seed" type to uniquely hash FNPTR types,
+ // i.e. the nullary FNPTR type is used as the type constructor for all function pointer types,
+ // when combined with an approproiate instantiation.
+ case ELEMENT_TYPE_FNPTR:
+ // fall through ...
+
+ case ELEMENT_TYPE_VOID:
+ case ELEMENT_TYPE_BOOLEAN:
+ case ELEMENT_TYPE_CHAR:
+ case ELEMENT_TYPE_I1:
+ case ELEMENT_TYPE_U1:
+ case ELEMENT_TYPE_I2:
+ case ELEMENT_TYPE_U2:
+ case ELEMENT_TYPE_I4:
+ case ELEMENT_TYPE_U4:
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_U8:
+ case ELEMENT_TYPE_R4:
+ case ELEMENT_TYPE_R8:
+ case ELEMENT_TYPE_STRING:
+ case ELEMENT_TYPE_OBJECT:
+ case ELEMENT_TYPE_TYPEDBYREF:
+ case ELEMENT_TYPE_I:
+ case ELEMENT_TYPE_U:
+
+ *ppResultType = pAppDomain->m_sharedtypes.GetBase(CORDBTYPE_ID(elementType, 0));
+
+ if (*ppResultType == NULL)
+ {
+ CordbType * pNewType = new (nothrow) CordbType(pAppDomain, elementType, (unsigned int) 0);
+
+ if (pNewType == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ HRESULT hr = pAppDomain->m_sharedtypes.AddBase(pNewType);
+
+ if (SUCCEEDED(hr))
+ {
+ *ppResultType = pNewType;
+ }
+ else
+ {
+ _ASSERTE(!"unexpected failure!");
+ delete pNewType;
+ }
+
+ return hr;
+ }
+ return S_OK;
+
+ default:
+ _ASSERTE(!"unexpected element type!");
+ return E_FAIL;
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+// Internal method to make a type with exactly one type argument by specifying
+// ELEMENT_TYPE_PTR, ELEMENT_TYPE_BYREF, ELEMENT_TYPE_SZARRAY or
+// ELEMENT_TYPE_ARRAY.
+//
+// Arguments:
+// pAppDomain - appdomain containing the type.
+// elementType - element type to create around. This is limited to: ELEMENT_TYPE_PTR,
+// ELEMENT_TYPE_BYREF, ELEMENT_TYPE_SZARRAY or ELEMENT_TYPE_ARRAY.
+// rank - for non-arrays, this must be 0. For szarray, this must be 1.
+// For multi-dimensional arrays, this is the rank.
+// pType - the single input type-parameter required for the specified element type.
+// ppResultType - OUT: the output parameter to get the corresponding CordbType
+//
+// Returns:
+// S_OK on success.
+//
+HRESULT CordbType::MkType(CordbAppDomain *pAppDomain,
+ CorElementType elementType,
+ ULONG rank,
+ CordbType * pType,
+ CordbType ** ppResultType)
+{
+ _ASSERTE(pAppDomain != NULL);
+ _ASSERTE(ppResultType != NULL);
+
+ RSLockHolder lockHolder(pAppDomain->GetProcess()->GetProcessLock());
+
+ switch (elementType)
+ {
+
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_BYREF:
+ _ASSERTE(rank == 0);
+ goto LUnary;
+
+ case ELEMENT_TYPE_SZARRAY:
+ _ASSERTE(rank == 1);
+ goto LUnary;
+
+ case ELEMENT_TYPE_ARRAY:
+LUnary:
+ {
+ CordbType * pFoundType = pAppDomain->m_sharedtypes.GetBase(CORDBTYPE_ID(elementType, rank));
+
+ if (pFoundType == NULL)
+ {
+ pFoundType = new (nothrow) CordbType(pAppDomain, elementType, rank);
+
+ if (pFoundType == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ HRESULT hr = pAppDomain->m_sharedtypes.AddBase(pFoundType);
+
+ if (FAILED(hr))
+ {
+ _ASSERTE(!"unexpected failure!");
+ delete pFoundType;
+ return hr;
+ }
+ }
+
+ Instantiation inst(1, &pType);
+
+ return MkTyAppType(pAppDomain, pFoundType, &inst, ppResultType);
+
+ }
+
+ default:
+ _ASSERTE(!"unexpected element type!");
+ return E_FAIL;
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+// Internal method to make a type for an instantiation of a class or value type, or just for the
+// class or value type if it accepts no type parameters.
+// Creates a CordbType instantiation around an uninstantiated CordbType and TypeParameter list.
+// In other words, this does:
+// CordbType(List<T>) + Instantiation({T=int}) --> CordbType(List<int>)
+//
+// This will create the subordinate types. Eg, for Triple<x,y,z>, it will create:
+// CordbType(Triple<x>), CordbType(Triple<x,y>), and CordbType(Triple<x,y,z)).
+// The fully instantiated one (the last one) is returned via the out parameter *pRes.
+//
+// Arguments:
+// pAppDomain - the appdomain that the type lives in.
+// pType - the open type to instantiate. Eg, CordbType(List<T>)
+// pInst - instantiation parameters.
+// ppResultType - OUT: out parameter to hold resulting type.
+//
+// Returns:
+// S_OK on success.
+//
+HRESULT CordbType::MkTyAppType(CordbAppDomain * pAppDomain,
+ CordbType * pType,
+ const Instantiation * pInst,
+ CordbType ** ppResultType)
+{
+ _ASSERTE(pAppDomain == pType->GetAppDomain());
+
+ CordbType * pCordbType = pType;
+
+ // Loop through and create each of the subordinate types, building up to the final fully Closed type.
+ for (unsigned int i = 0; i < pInst->m_cClassTyPars; i++)
+ {
+
+ CordbType * pCordbBaseType = pCordbType->m_spinetypes.GetBase((UINT_PTR) (pInst->m_ppInst[i]));
+
+ if (pCordbBaseType == NULL)
+ {
+ pCordbBaseType = new (nothrow) CordbType(pCordbType, pInst->m_ppInst[i]);
+
+ if (pCordbBaseType == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ HRESULT hr = pCordbType->m_spinetypes.AddBase(pCordbBaseType);
+
+ if (FAILED(hr))
+ {
+ _ASSERTE(!"unexpected failure!");
+ delete pCordbBaseType;
+ // @dbgtodo Microsoft leaks: Release the previously created types if this fails later in the loop
+ return hr;
+ }
+
+ pCordbBaseType->m_inst.m_cInst = i + 1;
+ pCordbBaseType->m_inst.m_cClassTyPars = i + 1;
+ pCordbBaseType->m_inst.m_ppInst = new (nothrow) CordbType *[i+1];
+
+ if (pCordbBaseType->m_inst.m_ppInst == NULL)
+ {
+ delete pCordbBaseType;
+ // @dbgtodo Microsoft leaks: Doesn't release the previously created types if this fails later in the loop
+ return E_OUTOFMEMORY;
+ }
+
+ for (unsigned int j = 0; j < (i + 1); j++)
+ {
+ // Constructed types include pointers across to other types - increase
+ // the reference counts on these....
+ pInst->m_ppInst[j]->AddRef();
+
+ pCordbBaseType->m_inst.m_ppInst[j] = pInst->m_ppInst[j];
+ }
+ }
+ pCordbType = pCordbBaseType;
+ }
+
+ *ppResultType = pCordbType;
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Creates a CordbType instantation around a cordbClass and TypeParameter list.
+// In other words, this does:
+// CordbClass(List<T>) + Instantiation({T=int}) --> CordbType(List<int>)
+//
+// This really just converts CordbClass(List<T>) --> CordbType(List<T>), and then calls CordbType::MkTyAppType
+//
+// Arguments:
+// pAppDomain - the AD that the class lives in.
+// elementType - element type of the class. Either ELEMENT_TYPE_CLASS or ELEMENT_TYPE_VALUETYPE
+// pClass - the uninstantiated class (eg, List<T>). This function will fill out the tycon->m_type field
+// to an uninstantiated CordbType (eg CordbType(List<T>))
+// pInst - the list of type parameters to instantiate with.
+// ppResultType - OUT: the CordbType instantiated with the type parameters (eg, CordbType(List<int>))
+//
+// Returns:
+// S_OK on success.
+//
+HRESULT CordbType::MkType(CordbAppDomain * pAppDomain,
+ CorElementType elementType,
+ CordbClass * pClass,
+ const Instantiation * pInst,
+ CordbType ** ppResultType)
+{
+ _ASSERTE(pAppDomain != NULL);
+ _ASSERTE(ppResultType != NULL);
+
+ switch (elementType)
+ {
+ // Normalize E_T_VALUETYPE away, so types do not record whether they are VCs or not, but CorDebugClass does.
+ // Update our view of whether a class is a VC based on the evidence we have here.
+ case ELEMENT_TYPE_VALUETYPE:
+
+ _ASSERTE(((pClass != NULL) && (!pClass->IsValueClassKnown() || pClass->IsValueClassNoInit())) ||
+ !"A non-value class is being used with ELEMENT_TYPE_VALUETYPE");
+
+ pClass->SetIsValueClass(true);
+ pClass->SetIsValueClassKnown(true);
+ // drop through
+
+ case ELEMENT_TYPE_CLASS:
+ {
+ // This probably isn't needed...
+ if (pClass == NULL)
+ {
+ elementType = ELEMENT_TYPE_OBJECT;
+ goto LReallyObject;
+ }
+
+ CordbType * pType = NULL;
+
+ pType = pClass->GetType();
+
+ if (pType == NULL)
+ {
+ pType = new (nothrow) CordbType(pAppDomain, ELEMENT_TYPE_CLASS, pClass);
+
+ if (pType == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ pClass->SetType(pType);
+ }
+
+ _ASSERTE(pClass->GetType() != NULL);
+
+ return CordbType::MkTyAppType(pAppDomain, pType, pInst, ppResultType);
+ }
+
+ default:
+LReallyObject:
+
+ _ASSERTE(pInst->m_cInst == 0);
+ return MkType(pAppDomain, elementType, ppResultType);
+
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Make a CordbType for a function pointer type (ELEMENT_TYPE_FNPTR).
+//
+// Arguments:
+// pAppDomain - the Appdomian the type lives in.
+// elementType - must be ELEMENT_TYPE_FNPTR.
+// pInst - instantiation information.
+// ppResultType - OUT: out-parameter to hold resulting CordbType
+//
+// Return:
+// S_OK on success.
+//
+HRESULT CordbType::MkType(CordbAppDomain * pAppDomain,
+ CorElementType elementType,
+ const Instantiation * pInst,
+ CordbType ** ppResultType)
+{
+ CordbType * pType;
+
+ _ASSERTE(elementType == ELEMENT_TYPE_FNPTR);
+
+ HRESULT hr = MkType(pAppDomain, elementType, &pType);
+
+ if (!SUCCEEDED(hr))
+ {
+ return hr;
+ }
+ return CordbType::MkTyAppType(pAppDomain, pType, pInst, ppResultType);
+}
+
+
+//-----------------------------------------------------------------------------
+// Public API to get the CorElementType of the type.
+//
+// Parameters:
+// pType - OUT: on return, gets the CorElementType
+//
+// Returns:
+// S_OK on success. CORDBG_E_CLASS_NOT_LOADED or synchronization errors on failure
+//-----------------------------------------------------------------------------
+HRESULT CordbType::GetType(CorElementType *pType)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ // See if this E_T_CLASS is really a value type?
+ if (m_elementType == ELEMENT_TYPE_CLASS)
+ {
+ _ASSERTE(m_pClass);
+ bool isVC = false;
+ // Determining if something is a VC or not can involve asking the EE.
+ // We could do it ourselves based on the metadata but it's non-trivial
+ // determining if a class has System.ValueType as a parent (we have
+ // to find and OpenScope the mscorlib.dll which we don't currently do
+ // on the right-side). But the IsValueClass call can fail if the
+ // class is not yet loaded on the right side. In that case we
+ // ignore the failure and return ELEMENT_TYPE_CLASS
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ isVC = m_pClass->IsValueClass();
+ }
+ EX_CATCH_HRESULT(hr);
+ if (!FAILED(hr) && isVC)
+ {
+ *pType = ELEMENT_TYPE_VALUETYPE;
+ return S_OK;
+ }
+ }
+ *pType = m_elementType;
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Public method to get the ICorDebugClass that matches this type.
+// ICorDebugType has instantiated type-params (eg, List<int>), whereas
+// ICorDebugClass is open (eg, List<T>).
+//
+// Parameters:
+// pClass - OUT: gets class on return.
+// Returns:
+// S_OK on success. CORDBG_E_CLASS_NOT_LOADED if the class is not loaded.
+// Else some other error.
+//-----------------------------------------------------------------------------
+HRESULT CordbType::GetClass(ICorDebugClass **pClass)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if ((m_pClass == NULL) && (m_elementType == ELEMENT_TYPE_STRING ||
+ m_elementType == ELEMENT_TYPE_OBJECT))
+ {
+ Init(FALSE);
+ }
+ if (m_pClass == NULL)
+ {
+ *pClass = NULL;
+ return CORDBG_E_CLASS_NOT_LOADED;
+ }
+ *pClass = m_pClass;
+ m_pClass->ExternalAddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Public method to get array rank. This is only valid for arrays.
+//
+// Parameters:
+// pnRank - OUT: *pnRank is set to rank on return
+//
+// Return:
+// S_OK if success. E_INVALIDARG is this Type doesn't have a rank.
+//-----------------------------------------------------------------------------
+HRESULT CordbType::GetRank(ULONG32 *pnRank)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pnRank, ULONG32 *);
+
+ if (m_elementType != ELEMENT_TYPE_SZARRAY &&
+ m_elementType != ELEMENT_TYPE_ARRAY)
+ return E_INVALIDARG;
+
+ *pnRank = (ULONG32) m_rank;
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Public convenience method to get the first type parameter.
+// This is purely to avoid needing to call EnumerateTypeParameters for
+// the set of types that only have 1 type-parameter.
+//
+// Parameters:
+// pType - OUT: get the ICorDebugType for the first type-parameter.
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbType::GetFirstTypeParameter(ICorDebugType **pType)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pType, ICorDebugType **);
+
+ // Since this is a public API, make sure there actually is at least 1 type-parameter.
+ if (m_inst.m_cInst == 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ _ASSERTE(m_inst.m_ppInst != NULL);
+ _ASSERTE(m_inst.m_ppInst[0] != NULL);
+
+ *pType = m_inst.m_ppInst[0];
+ if (*pType)
+ (*pType)->AddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Internal worker to create a CordbType around a CordbClass.
+// Parameters:
+// appdomain - AD that the type lives in.
+// et - CorElementType of the incoming CordbClass
+// cl - CordbClass representing the type to build a CordbType for
+// pRes - OUT: out parameter to return the newly created CordbType object.
+//
+// Return:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbType::MkUnparameterizedType(CordbAppDomain *appdomain, CorElementType et, CordbClass *cl,CordbType **pRes)
+{
+ // Pass in empty instantiation since CordbClass has no generic info.
+ // We should make some assert between et and cl->GetType().
+ Instantiation emptyInstantiation;
+
+ return CordbType::MkType(appdomain, et, cl, &emptyInstantiation, pRes);
+}
+
+
+//-----------------------------------------------------------------------------
+// Internal helper to get the First type parameter.
+// This is an internal convenience function for the public GetFirstTypeParameter.
+//
+// Parameters:
+// pRes - OUT: out-param to get the unary type-parameter.
+//-----------------------------------------------------------------------------
+void
+CordbType::DestUnaryType(CordbType **pRes)
+{
+ _ASSERTE(m_elementType == ELEMENT_TYPE_PTR
+ || m_elementType == ELEMENT_TYPE_BYREF
+ || m_elementType == ELEMENT_TYPE_ARRAY
+ || m_elementType == ELEMENT_TYPE_SZARRAY);
+ _ASSERTE(m_inst.m_cInst == 1);
+ _ASSERTE(m_inst.m_ppInst != NULL);
+ *pRes = m_inst.m_ppInst[0];
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Internal method to get the Class and type-parameters from a CordbType.
+//-----------------------------------------------------------------------------
+void
+CordbType::DestConstructedType(CordbClass **cls, Instantiation *inst)
+{
+ ASSERT(m_elementType == ELEMENT_TYPE_CLASS);
+ *cls = m_pClass;
+ *inst = m_inst;
+}
+
+//-----------------------------------------------------------------------------
+// Internal method to get all the type-parameters for a FnPtr
+//-----------------------------------------------------------------------------
+void
+CordbType::DestNaryType(Instantiation *inst)
+{
+ ASSERT(m_elementType == ELEMENT_TYPE_FNPTR);
+ *inst = m_inst;
+}
+
+
+//-----------------------------------------------------------------------------
+// CordbType::SigToType
+// Internal helper to create a CordbType from a Metadata signature (SigParser)
+//
+// This parses a metadata signature in the context of a module to return a CordbType.
+// This heavily relies on the metadata and signature format. See ECMA Partition II for details.
+// Since signatures may be recursive, this function can be called recursively.
+// Since metadata signatures exist all over, this can be called in many different scenarios, including
+// resolving a TypeSpec, looking up a field.
+//
+// pModule - module that the signature lives in.
+// pSigParse - Signature, positioned at the point to read the Type from.
+// This will not change the SigParser's current position.
+// inst - instantiation containing Type Params for the context of the SigParser.
+// For a local var or argument lookup, this would be the type-params from the Frame.
+// For a field lookup, this would be the type-params for the containing type.
+// pRes - OUT: yields the CordbType for this signature.
+//
+// Returns:
+// S_OK on success
+//-----------------------------------------------------------------------------
+HRESULT
+CordbType::SigToType(CordbModule * pModule,
+ SigParser * pSigParser,
+ const Instantiation * pInst,
+ CordbType ** ppResultType)
+{
+ FAIL_IF_NEUTERED(pModule);
+ INTERNAL_SYNC_API_ENTRY(pModule->GetProcess());
+
+ _ASSERTE(pSigParser != NULL);
+
+
+ //
+ // Make a local copy of the SigParser since we are going to mutate it.
+ //
+ SigParser sigParser = *pSigParser;
+
+ CorElementType elementType;
+ HRESULT hr;
+
+ IfFailRet(sigParser.GetElemType(&elementType));
+
+ switch (elementType)
+ {
+ case ELEMENT_TYPE_VAR:
+ case ELEMENT_TYPE_MVAR:
+ {
+ ULONG tyvar_num;
+
+ IfFailRet(sigParser.GetData(&tyvar_num));
+
+
+ if (elementType == ELEMENT_TYPE_VAR)
+ {
+ // ELEMENT_TYPE_VAR refers to an indexed type-parameter in the containing Type.
+ // Eg, we may be doing a field lookup on 'List<T> { T m_head}', and the field's return type 'T' is Type-parameter #0.
+ // Or this maybe part of a base class's TypeSpec.
+ _ASSERTE (tyvar_num < (pInst->m_cClassTyPars));
+ if (tyvar_num >= (pInst->m_cClassTyPars))
+ return E_FAIL;
+
+ _ASSERTE (pInst->m_ppInst != NULL);
+ *ppResultType = pInst->m_ppInst[tyvar_num];
+ }
+ else
+ {
+ //ELEMENT_TYPE_MVAR refers to an indexed type-parameter in the containing Method.
+ // Eg, we may be in Class::Func<T> and refering to T.
+ // The Instantiation array has Type type-parameters first, and then any Method Type-parameters.
+ // The m_cClassTyPars field indicats where the split is between Type and Method type-parameters. Type type-params
+ // come first.
+ _ASSERTE(elementType == ELEMENT_TYPE_MVAR);
+
+
+ _ASSERTE (tyvar_num < (pInst->m_cInst - pInst->m_cClassTyPars));
+ if (tyvar_num >= (pInst->m_cInst - pInst->m_cClassTyPars))
+ return E_FAIL;
+
+ _ASSERTE (pInst->m_ppInst != NULL);
+ *ppResultType = pInst->m_ppInst[tyvar_num + pInst->m_cClassTyPars];
+ }
+
+ return S_OK;
+ }
+ case ELEMENT_TYPE_GENERICINST:
+ {
+ //ELEMENT_TYPE_GENERICINST is that start of a instantiated generic type.
+ //Format for the signature blob is:
+ // 1) CorElementType, Token - this is the uninstantiated type (eg, for Pair<int, string>, it would be token for Pair<T,U>)
+ // 2) int - Count of generic args - eg, for Pair<T,U>, it would be "2".
+ // 3) type1,type2, ... - meteadata representation for generic args. For example above, it would be Type(int), Type(string).
+
+
+ // ignore "WITH", look at next ELEMENT_TYPE to get CLASS or VALUE
+
+ IfFailRet(sigParser.GetElemType(&elementType));
+
+ mdToken token;
+
+ IfFailRet(sigParser.GetToken(&token));
+
+ CordbClass * pClass;
+
+ IfFailRet( pModule->ResolveTypeRefOrDef(token, &pClass));
+
+ // The use of a class in a signature provides definite evidence as to whether it is a VC or not.
+ _ASSERTE(!pClass->IsValueClassKnown() ||
+ (pClass->IsValueClassNoInit() == (elementType == ELEMENT_TYPE_VALUETYPE)) ||
+ !"A value class is being used with ELEMENT_TYPE_GENERICINST");
+
+ pClass->SetIsValueClass(elementType == ELEMENT_TYPE_VALUETYPE);
+ pClass->SetIsValueClassKnown(true);
+
+ // Build up the array of generic arguments.
+ ULONG cArgs; // number of generic arguments in the type.
+
+ IfFailRet(sigParser.GetData(&cArgs));
+
+ S_UINT32 allocSize = S_UINT32( cArgs ) * S_UINT32( sizeof(CordbType *) );
+
+ if (allocSize.IsOverflow())
+ {
+ IfFailRet(E_OUTOFMEMORY);
+ }
+
+ CordbType ** ppTypeInstantiations = reinterpret_cast<CordbType **>(_alloca( allocSize.Value()));
+
+ for (unsigned int i = 0; i < cArgs;i++)
+ {
+ IfFailRet(CordbType::SigToType(pModule, &sigParser, pInst, &ppTypeInstantiations[i]));
+
+ IfFailRet(sigParser.SkipExactlyOne());
+ }
+
+ // Now we have the Open type (eg, Pair<T,U>) and the instantiation list, so create the Closed CordbType..
+ Instantiation typeInstantiation(cArgs, ppTypeInstantiations);
+
+ return CordbType::MkType(pModule->GetAppDomain(), elementType, pClass, &typeInstantiation, ppResultType);
+ }
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_VALUETYPE: // OK: this E_T_VALUETYPE comes from signature
+ {
+ // Path for non-generic types
+
+ mdToken token;
+
+ IfFailRet(sigParser.GetToken(&token));
+
+ CordbClass * pClass;
+
+ IfFailRet(pModule->ResolveTypeRefOrDef(token, &pClass));
+
+ // The use of a class in a signature provides definite evidence as to whether it is a VC or not.
+
+ _ASSERTE(!pClass->IsValueClassKnown() ||
+ (pClass->IsValueClassNoInit() == (elementType == ELEMENT_TYPE_VALUETYPE)) ||
+ !"A non-value class is being used with ELEMENT_TYPE_VALUETYPE");
+
+ pClass->SetIsValueClass(elementType == ELEMENT_TYPE_VALUETYPE);
+ pClass->SetIsValueClassKnown(true);
+
+ return CordbType::MkUnparameterizedType(pModule->GetAppDomain(), elementType, pClass, ppResultType);
+ }
+ case ELEMENT_TYPE_SENTINEL:
+ case ELEMENT_TYPE_MODIFIER:
+ case ELEMENT_TYPE_PINNED:
+ {
+ IfFailRet(CordbType::SigToType(pModule, &sigParser, pInst, ppResultType));
+ // Throw away SENTINELS on all CordbTypes...
+ return S_OK;
+ }
+ case ELEMENT_TYPE_CMOD_REQD:
+ case ELEMENT_TYPE_CMOD_OPT:
+ {
+ mdToken token;
+
+ IfFailRet(sigParser.GetToken(&token));
+
+ IfFailRet(CordbType::SigToType(pModule, &sigParser, pInst, ppResultType));
+ // Throw away CMOD on all CordbTypes...
+ return S_OK;
+ }
+
+ case ELEMENT_TYPE_ARRAY:
+ {
+ CordbType * pType;
+
+ IfFailRet(CordbType::SigToType(pModule, &sigParser, pInst, &pType));
+
+ IfFailRet(sigParser.SkipExactlyOne());
+
+ ULONG rank;
+
+ IfFailRet(sigParser.GetData(&rank));
+
+ return CordbType::MkType(pModule->GetAppDomain(), elementType, rank, pType, ppResultType);
+ }
+ case ELEMENT_TYPE_SZARRAY:
+ {
+ CordbType * pType;
+
+ IfFailRet(CordbType::SigToType(pModule, &sigParser, pInst, &pType));
+
+ return CordbType::MkType(pModule->GetAppDomain(), elementType, 1, pType, ppResultType);
+ }
+
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_BYREF:
+ {
+ CordbType * pType;
+
+ IfFailRet(CordbType::SigToType(pModule, &sigParser, pInst, &pType));
+
+ return CordbType::MkType(pModule->GetAppDomain(),elementType, 0, pType, ppResultType);
+ }
+
+ case ELEMENT_TYPE_FNPTR:
+ {
+ ULONG cArgs;
+
+ IfFailRet(sigParser.GetData(&cArgs)); // Skip callingConv
+
+ IfFailRet(sigParser.GetData(&cArgs)); // Get number of parameters
+
+ S_UINT32 allocSize = ( S_UINT32(cArgs) + S_UINT32(1) ) * S_UINT32( sizeof(CordbType *) );
+
+ if (allocSize.IsOverflow())
+ {
+ IfFailRet(E_OUTOFMEMORY);
+ }
+
+ CordbType ** ppTypeInstantiations = (CordbType **) _alloca( allocSize.Value() );
+
+ for (unsigned int i = 0; i <= cArgs; i++)
+ {
+ IfFailRet(CordbType::SigToType(pModule, &sigParser, pInst, &ppTypeInstantiations[i]));
+
+ IfFailRet(sigParser.SkipExactlyOne());
+ }
+
+ Instantiation typeInstantiation(cArgs + 1, ppTypeInstantiations);
+
+ return CordbType::MkType(pModule->GetAppDomain(), elementType, &typeInstantiation, ppResultType);
+ }
+
+ case ELEMENT_TYPE_VOID:
+ case ELEMENT_TYPE_BOOLEAN:
+ case ELEMENT_TYPE_CHAR:
+ case ELEMENT_TYPE_I1:
+ case ELEMENT_TYPE_U1:
+ case ELEMENT_TYPE_I2:
+ case ELEMENT_TYPE_U2:
+ case ELEMENT_TYPE_I4:
+ case ELEMENT_TYPE_U4:
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_U8:
+ case ELEMENT_TYPE_R4:
+ case ELEMENT_TYPE_R8:
+ case ELEMENT_TYPE_STRING:
+ case ELEMENT_TYPE_TYPEDBYREF:
+ case ELEMENT_TYPE_OBJECT:
+ case ELEMENT_TYPE_I:
+ case ELEMENT_TYPE_U:
+ return CordbType::MkType(pModule->GetAppDomain(), elementType, ppResultType);
+
+ default:
+ _ASSERTE(!"unexpected element type!");
+ return E_FAIL;
+ }
+} // CordbType::SigToType
+
+//-----------------------------------------------------------------------------
+// Marshal a DebuggerIPCE_BasicTypeData --> CordbType.
+//
+// This will build up a DebuggerIPCE_ExpandedTypeData and convert that into
+// a CordbType. This may send additional IPC events if needed to
+// go from Basic --> Expanded data. Note that this is designed to handle generics.
+//
+// Parameters:
+// pAppDomain - the AppDomain the type lives in.
+// data - DebuggerIPCE_BasicTypeData from Left-Side containing type description.
+// pRes - OUT: out-parameter to hold built type.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbType::TypeDataToType(CordbAppDomain *pAppDomain, DebuggerIPCE_BasicTypeData *data, CordbType **pRes)
+{
+ FAIL_IF_NEUTERED(pAppDomain);
+ INTERNAL_SYNC_API_ENTRY(pAppDomain->GetProcess()); //
+
+
+
+ HRESULT hr = S_OK;
+ CorElementType et = data->elementType;
+ switch (et)
+ {
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_BYREF:
+ // For these element types the "Basic" type data only contains the type handle.
+ // So we fetch some more data, and the go onto the "Expanded" case...
+ {
+ EX_TRY
+ {
+ DebuggerIPCE_ExpandedTypeData typeInfo;
+ CordbProcess * pProcess = pAppDomain->GetProcess();
+
+ {
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ pProcess->GetDAC()->TypeHandleToExpandedTypeInfo(NoValueTypeBoxing, // could be generics
+ // which are never boxed
+ pAppDomain->GetADToken(),
+ data->vmTypeHandle,
+ &typeInfo);
+ }
+
+ IfFailThrow(CordbType::TypeDataToType(pAppDomain,&typeInfo, pRes));
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+
+ }
+
+ case ELEMENT_TYPE_FNPTR:
+ {
+ DebuggerIPCE_ExpandedTypeData e;
+ e.elementType = et;
+ e.NaryTypeData.typeHandle = data->vmTypeHandle;
+ return CordbType::TypeDataToType(pAppDomain, &e, pRes);
+ }
+ default:
+ // For all other element types the "Basic" view of a type
+ // contains the same information as the "expanded"
+ // view, so just reuse the code for the Expanded view...
+ DebuggerIPCE_ExpandedTypeData e;
+ e.elementType = et;
+ e.ClassTypeData.metadataToken = data->metadataToken;
+ e.ClassTypeData.vmDomainFile = data->vmDomainFile;
+ e.ClassTypeData.vmModule = data->vmModule;
+ e.ClassTypeData.typeHandle = data->vmTypeHandle;
+ return CordbType::TypeDataToType(pAppDomain, &e, pRes);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Marshal DebuggerIPCE_ExpandedTypeData --> CordbType
+// The ExpandedTypeData just contains top level generic info, and so
+// the RS may need to send more IPC events to fill out details.
+//
+// Parameters:
+// pAppDomain - the appdomain that all the types live in.
+// data - data used to build up CordbType
+// pRes - OUT: out param to get back CordbType on return.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbType::TypeDataToType(CordbAppDomain *pAppDomain, DebuggerIPCE_ExpandedTypeData *data, CordbType **pRes)
+{
+ INTERNAL_SYNC_API_ENTRY(pAppDomain->GetProcess()); //
+
+ CorElementType et = data->elementType;
+ HRESULT hr = S_OK;
+ switch (et)
+ {
+
+ case ELEMENT_TYPE_OBJECT:
+ case ELEMENT_TYPE_VOID:
+ case ELEMENT_TYPE_BOOLEAN:
+ case ELEMENT_TYPE_CHAR:
+ case ELEMENT_TYPE_I1:
+ case ELEMENT_TYPE_U1:
+ case ELEMENT_TYPE_I2:
+ case ELEMENT_TYPE_U2:
+ case ELEMENT_TYPE_I4:
+ case ELEMENT_TYPE_U4:
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_U8:
+ case ELEMENT_TYPE_R4:
+ case ELEMENT_TYPE_R8:
+ case ELEMENT_TYPE_STRING:
+ case ELEMENT_TYPE_TYPEDBYREF:
+ case ELEMENT_TYPE_I:
+ case ELEMENT_TYPE_U:
+ETObject:
+ // It's a primitive (therefore non-generic) type, so we can just create it immediately.
+ IfFailRet (CordbType::MkType(pAppDomain, et, pRes));
+ break;
+
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_VALUETYPE: // OK: this E_T_VALUETYPE comes from the EE
+ {
+ //
+ if (data->ClassTypeData.metadataToken == mdTokenNil) {
+ et = ELEMENT_TYPE_OBJECT;
+ goto ETObject;
+ }
+ CordbModule * pClassModule = NULL;
+ EX_TRY
+ {
+ pClassModule = pAppDomain->LookupOrCreateModule(data->ClassTypeData.vmModule, data->ClassTypeData.vmDomainFile);
+ }
+ EX_CATCH_HRESULT(hr);
+ if( pClassModule == NULL )
+ {
+ // We don't know anything about this module - shouldn't happen.
+ // <TODO>This can be hit by the issue described in VSWhidbey 465120</TODO>
+ _ASSERTE(!"Unrecognized module");
+ return CORDBG_E_MODULE_NOT_LOADED;
+ }
+
+ CordbClass *tycon;
+ IfFailRet (pClassModule->LookupOrCreateClass(data->ClassTypeData.metadataToken,&tycon));
+ if (!(data->ClassTypeData.typeHandle.IsNull()))
+ {
+ // It's a generic type. We have the typehandle, use that to query for the rest of
+ // the tyeparameters and build up the instantiation for the CordbType.
+
+ IfFailRet (CordbType::InstantiateFromTypeHandle(pAppDomain, data->ClassTypeData.typeHandle, et, tycon, pRes));
+ // Set the type handle regardless of how we found
+ // the type. For example if type was already
+ // constructed without the type handle still set
+ // it here.
+ if (*pRes)
+ {
+ (*pRes)->m_typeHandleExact = data->ClassTypeData.typeHandle;
+ }
+ break;
+ }
+ else
+ {
+ // Non generic type. Since we already have the CordbClass for it, we can trivially create the CordbType
+ IfFailRet (CordbType::MkUnparameterizedType(pAppDomain, et,tycon,pRes));
+ break;
+ }
+
+ }
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ {
+ CordbType *argty;
+ IfFailRet (CordbType::TypeDataToType(pAppDomain, &(data->ArrayTypeData.arrayTypeArg), &argty));
+ IfFailRet (CordbType::MkType(pAppDomain, et, data->ArrayTypeData.arrayRank, argty, pRes));
+ break;
+ }
+
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_BYREF:
+ {
+ CordbType *argty;
+ IfFailRet (CordbType::TypeDataToType(pAppDomain, &(data->UnaryTypeData.unaryTypeArg), &argty));
+ IfFailRet (CordbType::MkType(pAppDomain, et, 0, argty, pRes));
+ break;
+ }
+ case ELEMENT_TYPE_FNPTR:
+ {
+ IfFailRet (CordbType::InstantiateFromTypeHandle(pAppDomain, data->NaryTypeData.typeHandle, et, NULL, pRes));
+ if (*pRes)
+ {
+ (*pRes)->m_typeHandleExact = data->NaryTypeData.typeHandle;
+ }
+ break;
+ }
+ case ELEMENT_TYPE_END:
+ *pRes = NULL;
+ return E_FAIL;
+
+ default:
+ _ASSERTE(!"unexpected element type!");
+ return E_FAIL;
+
+ }
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CordbType::InstantiateFromTypeHandle
+// Internal helper method.
+// Builds (Left-Side) TypeHandle --> (Right-Side) CordbType
+// This is very useful when we get a typehandle from the LeftSide. A common
+// scenario is when we get an Object back from the LS, which happens when
+// we build the CordbType corresponding to a Cordb*Value.
+//
+// Parameters:
+// pAppdomain - the appdomain the type lives in.
+// vmTypeHandle - a Left-Side typehandle describing the type.
+// elementType - convenient way to indicate whether we've got ELEMENT_TYPE_FNPTR or
+// something else. We should be able to retrieve this from the TypeHandle,
+// but our caller already has it available.
+// typeConstructor - CordbClass corresponding to the typeHandle. This could be built
+// up from typehandle, but our caller already has it.
+// Will be NULL for ELEMENT_TYPE_FNPTR
+// pResultType - OUT: out parameter to yield CordbType for the TypeHandle.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbType::InstantiateFromTypeHandle(CordbAppDomain * pAppDomain,
+ VMPTR_TypeHandle vmTypeHandle,
+ CorElementType elementType,
+ CordbClass * typeConstructor,
+ CordbType ** pResultType)
+{
+ HRESULT hr = S_OK;
+
+ // Should already by synced by caller.
+ INTERNAL_SYNC_API_ENTRY(pAppDomain->GetProcess()); //
+ _ASSERTE((pAppDomain->GetProcess()->GetShim() == NULL) || (pAppDomain->GetProcess()->GetSynchronized()));
+
+ EX_TRY
+ {
+ CordbProcess * pProcess = pAppDomain->GetProcess();
+ //
+ // Step 1) Ask DacDbi interface for a list of type-parameters given a TypeHandle.
+ //
+
+ TypeParamsList params;
+ {
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ pProcess->GetDAC()->GetTypeHandleParams(pAppDomain->GetADToken(), vmTypeHandle, &params);
+ }
+
+ // convert the parameter type information to a list of CordbTypeInstances (one for each parameter)
+ // note: typeList will be destroyed on exit, running destructors for each element. In this case, that
+ // means it will simply assert IsNeutered.
+ DacDbiArrayList<CordbType *> typeList;
+ typeList.Alloc(params.Count());
+ for (int i = 0; i < params.Count(); ++i)
+ {
+ IfFailThrow(TypeDataToType(pAppDomain, &(params[i]), &(typeList[i])));
+ }
+
+ // now make an instance of CordbType from an instantiation
+ Instantiation instantiation(params.Count(), &(typeList[0]));
+ if (elementType == ELEMENT_TYPE_FNPTR)
+ {
+ IfFailThrow(CordbType::MkType(pAppDomain, elementType, &instantiation, pResultType));
+ }
+ else
+ {
+ IfFailThrow(CordbType::MkType(pAppDomain, elementType, typeConstructor, &instantiation, pResultType));
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbType::InstantiateFromTypeHandle
+
+//-----------------------------------------------------------------------------
+// Initialize the CordbType.
+// This will involve a lot of queries to the Left-side.
+// This means finding the type-handle, getting / creating associated CordbClass,
+// filling out the instantiation, getting field info, etc.
+//
+// Parameters:
+// fForceInit - if false, may skip initialization if TypeHandle already known.
+//
+// Returns:
+// S_OK if success, CORDBG_E_CLASS_NOT_LOADED, E_INVALIDARG, OOM on failure
+//-----------------------------------------------------------------------------
+HRESULT CordbType::Init(BOOL fForceInit)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+
+ HRESULT hr = S_OK;
+
+ if (m_pClass && m_pClass->GetLoadLevel() != CordbClass::FullInfo)
+ fForceInit = TRUE;
+
+ // Step 1. initialize the type constructor (if one exists)
+ // and the (class) type parameters....
+ if (m_elementType == ELEMENT_TYPE_CLASS)
+ {
+
+ // start by initing only enough so that we can determine whether
+ // or not this is a generic class. When dealing with generic
+ // type instantiations there is no guarantee the open generic
+ // type is fully restored. If we load too eagerly it might fail
+ // and we wouldn't actually need that extra data anyways.
+ _ASSERTE(m_pClass != NULL);
+ EX_TRY
+ {
+ m_pClass->Init(CordbClass::BasicInfo);
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+
+ // non-generic classes need the class object to be fully inited
+ // in the generic case we won't ever use that data
+ if (!m_pClass->HasTypeParams())
+ {
+ EX_TRY
+ {
+ m_pClass->Init(CordbClass::FullInfo);
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+
+ return S_OK; // Non-generic, that's all - no clean-up required
+ }
+ }
+
+ _ASSERTE(m_elementType != ELEMENT_TYPE_CLASS || m_pClass->HasTypeParams());
+
+ for (unsigned int i = 0; i<m_inst.m_cClassTyPars; i++)
+ {
+ _ASSERTE(m_inst.m_ppInst != NULL);
+ _ASSERTE(m_inst.m_ppInst[i] != NULL);
+ IfFailRet( m_inst.m_ppInst[i]->Init(fForceInit) );
+ }
+
+ // Step 2. Try to fetch the type handle if necessary (only
+ // for instantiated class types, pointer types etc.)
+ // We do this by preparing an event specifying the type and
+ // then fetching the type handle from the left-side. This
+ // will not always succeed, as forcing the load of the type handle would be the
+ // equivalent of doing a FuncEval, i.e. the instantiation may
+ // not have been created. But we try anyway to reduce the number of
+ // failures.
+ //
+ // Note that in the normal case we will have the type handle from the EE
+ // anyway, e.g. if the CordbType was created when reporting the type
+ // of an actual object.
+
+ // Initialize m_typeHandleExact if it needs it
+ if (m_elementType == ELEMENT_TYPE_ARRAY ||
+ m_elementType == ELEMENT_TYPE_SZARRAY ||
+ m_elementType == ELEMENT_TYPE_BYREF ||
+ m_elementType == ELEMENT_TYPE_PTR ||
+ m_elementType == ELEMENT_TYPE_FNPTR ||
+ (m_elementType == ELEMENT_TYPE_CLASS && m_pClass->HasTypeParams()))
+ {
+ // It is OK if getting an exact type handle
+ // fails with CORDBG_E_CLASS_NOT_LOADED. In that case we leave
+ // the type information incomplete and subsequent operations
+ // will try to call Init() again. The immediate operation will fail later if
+ // TypeToBasicTypeData requests the exact type information for this type.
+ hr = InitInstantiationTypeHandle(fForceInit);
+ if (hr != CORDBG_E_CLASS_NOT_LOADED)
+ IfFailRet(hr);
+ }
+
+
+ // For OBJECT and STRING we may not have a value for m_class
+ // object. Go try and get it.
+ if (m_elementType == ELEMENT_TYPE_STRING ||
+ m_elementType == ELEMENT_TYPE_OBJECT)
+ {
+ IfFailRet(InitStringOrObjectClass(fForceInit));
+ }
+
+ // Step 3. Fetch the information that is specific to the type where necessary...
+ // Now we have the type handle for the constructed type, we can ask for the size of
+ // the object. Only do this for constructed value types.
+ //
+ // Note that the exact and/or approximate type handles may not be available.
+ if ((m_elementType == ELEMENT_TYPE_CLASS) && m_pClass->HasTypeParams())
+ {
+ IfFailRet(InitInstantiationFieldInfo(fForceInit));
+ }
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Internal function to communicate with Left-Side to get an exact TypeHandle
+// (runtime type representation) for this CordbType.
+//
+// Parameters:
+// fForceInit - if false, may skip initialization if TypeHandle already known.
+//
+// Returns:
+// S_OK on success or failure HR E_INVALIDARG, OOM, CORDBG_E_CLASS_NOT_LOADED
+// on failure
+//-----------------------------------------------------------------------------
+HRESULT CordbType::InitInstantiationTypeHandle(BOOL fForceInit)
+{
+
+ // Check if we've already done this Init
+ if (!fForceInit && !m_typeHandleExact.IsNull())
+ return S_OK;
+
+ HRESULT hr = S_OK;
+
+ // Create an array of DebuggerIPCE_BasicTypeData structures from the array of type parameters.
+ // First, get a buffer to hold the information
+ CordbProcess *pProcess = GetProcess();
+ S_UINT32 bufferSize = S_UINT32(sizeof(DebuggerIPCE_BasicTypeData)) *
+ S_UINT32(m_inst.m_cClassTyPars);
+ EX_TRY
+ {
+ if( bufferSize.IsOverflow() )
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+ NewHolder<DebuggerIPCE_BasicTypeData> pArgTypeData(new DebuggerIPCE_BasicTypeData[bufferSize.Value()]);
+
+ // We will have already called Init on each of the type parameters further above. Now we build a
+ // list of type information for each type parameter.
+ for (unsigned int i = 0; i < m_inst.m_cClassTyPars; i++)
+ {
+ _ASSERTE(m_inst.m_ppInst != NULL);
+ _ASSERTE(m_inst.m_ppInst[i] != NULL);
+ IfFailThrow(m_inst.m_ppInst[i]->TypeToBasicTypeData(&pArgTypeData[i]));
+ }
+
+ DebuggerIPCE_ExpandedTypeData typeData;
+
+ // get the top-level type information
+ TypeToExpandedTypeData(&typeData);
+
+ ArgInfoList argInfo(pArgTypeData, m_inst.m_cClassTyPars);
+
+ {
+ // Get the TypeHandle based on the type data
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ hr = pProcess->GetDAC()->GetExactTypeHandle(&typeData, &argInfo, m_typeHandleExact);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+} // CordbType::InitInstantiationTypeHandle
+
+//-----------------------------------------------------------------------------
+// Internal helper for CordbType::Init to finish initialize types for
+// System.String or System.Object.
+// This just needs to set the m_class field.
+//
+// Parameters:
+// fForceInit - force re-initialization if already initialized.
+//
+// Returns:
+// S_OK on success or CORDBG_E_CLASS_NOT_LOADED on failure.
+//
+// Note: verification with IPC result may assert
+//-----------------------------------------------------------------------------
+
+HRESULT CordbType::InitStringOrObjectClass(BOOL fForceInit)
+{
+ // This CordbType is a non-generic class, either System.String or System.Object.
+ // Need to find the CordbClass instance (in the proper AppDomain) that matches that type.
+
+ // Check if we've already done this Init
+ if (!fForceInit && m_pClass != NULL)
+ {
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ //
+ // Step 1a) Send a request to the DAC to map: CorElementType --> {token, Module}
+ //
+ CordbProcess *pProcess = GetProcess();
+ mdTypeDef metadataToken;
+ VMPTR_DomainFile vmDomainFile = VMPTR_DomainFile::NullPtr();
+ VMPTR_Module vmModule = VMPTR_Module::NullPtr();
+
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ pProcess->GetDAC()->GetSimpleType(m_appdomain->GetADToken(),
+ m_elementType,
+ &metadataToken,
+ &vmModule,
+ &vmDomainFile);
+ }
+
+ //
+ // Step 2) Lookup CordbClass based off token + Module.
+ //
+ CordbModule * pTypeModule = m_appdomain->LookupOrCreateModule(vmModule, vmDomainFile);
+
+ _ASSERTE(pTypeModule != NULL);
+ IfFailThrow(pTypeModule->LookupOrCreateClass(metadataToken, &m_pClass));
+
+ _ASSERTE(m_pClass != NULL);
+
+ _ASSERTE(SUCCEEDED(hr));
+ m_pClass->AddRef();
+
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbType::InitStringOrObjectClass
+
+//-----------------------------------------------------------------------------
+// Internal helper for CordbType::Init to get FieldInfos for a generic Type.
+// Non-generic types can use the FieldInfos off their associated CordbClass.
+//
+// Parameters:
+// fForceInit - force re-initialization if already initialized?
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbType::InitInstantiationFieldInfo(BOOL fForceInit)
+{
+ HRESULT hr = S_OK;
+
+ // Check if we've already done this Init
+ if (!m_fieldInfoNeedsInit && !fForceInit)
+ {
+ return hr;
+ }
+
+ _ASSERTE(m_elementType == ELEMENT_TYPE_CLASS);
+ _ASSERTE(m_pClass->HasTypeParams());
+
+ VMPTR_TypeHandle typeHandleApprox = m_typeHandleExact;
+
+ // If the exact type handle is not available then get the approximate type handle.
+ if (typeHandleApprox.IsNull())
+ {
+ // set up a buffer to hold type parameter information for the type. (See
+ // code:CordbType::GatherTypeData for more information). First, compute its size.
+ unsigned int typeDataNodeCount = 0;
+ this->CountTypeDataNodes(&typeDataNodeCount);
+
+ EX_TRY
+ {
+ // allocate a buffer to hold the parameter data
+ TypeInfoList typeData;
+
+ typeData.Alloc(typeDataNodeCount);
+
+ // fill the buffer
+ DebuggerIPCE_TypeArgData * pCurrent = &(typeData[0]);
+ GatherTypeData(this, &pCurrent);
+
+ // request the type handle from the DAC
+ CordbProcess *pProcess = GetProcess();
+ {
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ typeHandleApprox = pProcess->GetDAC()->GetApproxTypeHandle(&typeData);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ if(FAILED(hr)) return hr;
+ }
+
+ // OK, now get the field info if we can.
+ CordbProcess *pProcess = GetProcess();
+ EX_TRY
+ {
+ {
+ // this may be called multiple times. Each call will discard previous values in m_fieldList and reinitialize
+ // the list with updated information
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ pProcess->GetDAC()->GetInstantiationFieldInfo(m_pClass->GetModule()->GetRuntimeDomainFile(),
+ m_typeHandleExact,
+ typeHandleApprox,
+ &m_fieldList,
+ &m_objectSize);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbType::ReturnedByValue()
+{
+ HRESULT hr = S_OK;
+
+ if (!IsValueType())
+ return S_OK;
+
+
+ ULONG32 unboxedSize = 0;
+ IfFailRet(GetUnboxedObjectSize(&unboxedSize));
+
+ if (unboxedSize > sizeof(SIZE_T))
+ return S_FALSE;
+
+ mdToken mdClass = m_pClass->GetToken();
+
+ int fieldCount = 0;
+ bool unsupported = false;
+
+ HCORENUM fields = 0;
+ ULONG fetched = 0;
+ mdToken mdField;
+ IMetaDataImport *pImport = m_pClass->GetModule()->GetMetaDataImporter();
+ IfFailRet(pImport->EnumFields(&fields, mdClass, &mdField, 1, &fetched));
+
+ while (hr == S_OK && fetched == 1)
+ {
+ DWORD attr = 0;
+ PCCOR_SIGNATURE sigBlob = 0;
+ ULONG sigLen = 0;
+ hr = pImport->GetFieldProps(mdField, NULL, NULL, 0, NULL, &attr, &sigBlob, &sigLen, NULL, NULL, NULL);
+
+ if (SUCCEEDED(hr))
+ {
+ // !static
+ if ((attr & 0x10) == 0)
+ {
+ if (fieldCount++)
+ break;
+
+ CorElementType et;
+ SigParser parser(sigBlob, sigLen);
+ parser.GetByte(NULL); // 0x6, field signature
+ parser.SkipCustomModifiers();
+ hr = parser.GetElemType(&et);
+ if (SUCCEEDED(hr))
+ {
+ switch (et)
+ {
+ case ELEMENT_TYPE_R4:
+ case ELEMENT_TYPE_R8:
+ unsupported = true;
+ break;
+
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_STRING:
+ case ELEMENT_TYPE_PTR:
+ // OK
+ break;
+
+ default:
+ if (!CorIsPrimitiveType(et))
+ unsupported = true;
+ break;
+ }
+
+ if (unsupported)
+ break;
+ }
+ }
+
+ hr = pImport->EnumFields(&fields, mdClass, &mdField, 1, &fetched);
+ }
+
+ if (FAILED(hr))
+ {
+ pImport->CloseEnum(fields);
+ return hr;
+ }
+ }
+
+ pImport->CloseEnum(fields);
+
+ if (unsupported)
+ return S_FALSE;
+
+ return fieldCount <= 1 ? S_OK : S_FALSE;
+}
+
+
+//-----------------------------------------------------------------------------
+// Internal helper to get the size (in bytes) of the unboxed object.
+// For a generic type, the size of the type depends on the size of the
+// type-parameters.
+// This is commonly used by Cordb*Value in their Initialization when they
+// need to cache the size of the Target object they refer to.
+//
+// This should only be called on Value-types and Primitives (eg, i4, FnPtr).
+// It should not be called on Reference types.
+//
+// Parameters:
+// pObjectSize - OUT: out-parameter to get the size in bytes.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT
+CordbType::GetUnboxedObjectSize(ULONG32 *pObjectSize)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+
+ HRESULT hr = S_OK;
+ bool isVC = false;
+
+ EX_TRY
+ {
+ isVC = IsValueType();
+ }
+ EX_CATCH_HRESULT(hr);
+
+ IfFailRet(hr);
+
+ if (isVC)
+ {
+ *pObjectSize = 0;
+
+ hr = Init(FALSE);
+
+ if (!SUCCEEDED(hr))
+ return hr;
+
+ *pObjectSize = (ULONG) ((!m_pClass->HasTypeParams()) ? m_pClass->ObjectSize() : this->m_objectSize);
+
+ return hr;
+ }
+ else
+ {
+ // Caller gaurantees that we're not a class. And the check above guarantees we're not a value-type.
+ // So we're some sort of primitive, and thus we can determine size from the signature.
+ //
+ // @dbgtodo inspection - We didn't have this assert in Whidbey, and it's firing in vararg
+ // scenarios even though it's returning the right value for reference types (i.e. 4 on x86 and 8 on
+ // 64-bit). Commenting it out for now.
+ //_ASSERTE(m_elementType != ELEMENT_TYPE_CLASS);
+
+ // We need to use a temporary variable here -- attempting to cast among pointer types
+ // (i.e., (PCCOR_SIGNATURE) &m_elementType) yields incorrect results on big-endian machines
+ COR_SIGNATURE corSig = (COR_SIGNATURE) m_elementType;
+
+ SigParser sigParser(&corSig, sizeof(corSig));
+
+ ULONG size;
+
+ IfFailRet(sigParser.PeekElemTypeSize(&size));
+
+ *pObjectSize = size;
+ return hr;
+ }
+}
+
+VMPTR_DomainFile CordbType::GetDomainFile()
+{
+ if (m_pClass != NULL)
+ {
+ CordbModule * pModule = m_pClass->GetModule();
+ if (pModule)
+ {
+ return pModule->m_vmDomainFile;
+ }
+ else
+ {
+ return VMPTR_DomainFile::NullPtr();
+ }
+ }
+ else
+ {
+ return VMPTR_DomainFile::NullPtr();
+ }
+}
+
+
+VMPTR_Module CordbType::GetModule()
+{
+ if (m_pClass != NULL)
+ {
+ CordbModule * pModule = m_pClass->GetModule();
+ if (pModule)
+ {
+ return pModule->GetRuntimeModule();
+ }
+ else
+ {
+ return VMPTR_Module::NullPtr();
+ }
+ }
+ else
+ {
+ return VMPTR_Module::NullPtr();
+ }
+}
+//-----------------------------------------------------------------------------
+// Internal method to Marshal: CordbType --> DebuggerIPCE_BasicTypeData
+// Nb. CordbType::Init will call this. The operation
+// fails if the exact type information has been requested but was not available
+//
+// Parameters:
+// data - OUT: BasicTypeData instance to fill out.
+//
+// Returns:
+// S_OK on success, CORDBG_E_CLASS_NOT_LOADED on failure
+//-----------------------------------------------------------------------------
+HRESULT CordbType::TypeToBasicTypeData(DebuggerIPCE_BasicTypeData *data)
+{
+ switch (m_elementType)
+ {
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ case ELEMENT_TYPE_BYREF:
+ case ELEMENT_TYPE_PTR:
+ data->elementType = m_elementType;
+ data->metadataToken = mdTokenNil;
+ data->vmDomainFile = VMPTR_DomainFile::NullPtr();
+ data->vmTypeHandle = m_typeHandleExact;
+ if (data->vmTypeHandle.IsNull())
+ {
+ return CORDBG_E_CLASS_NOT_LOADED;
+ }
+ _ASSERTE(!data->vmTypeHandle.IsNull());
+ break;
+
+ case ELEMENT_TYPE_CLASS:
+ _ASSERTE(m_pClass != NULL);
+ data->elementType = m_pClass->IsValueClassNoInit() ? ELEMENT_TYPE_VALUETYPE : ELEMENT_TYPE_CLASS;
+ data->metadataToken = m_pClass->MDToken();
+ data->vmDomainFile = GetDomainFile();
+ data->vmTypeHandle = m_typeHandleExact;
+ if (m_pClass->HasTypeParams() && data->vmTypeHandle.IsNull())
+ {
+ return CORDBG_E_CLASS_NOT_LOADED;
+ }
+ break;
+ default:
+ // This includes all the "primitive" types, in which CorElementType is a sufficient description.
+ data->elementType = m_elementType;
+ data->metadataToken = mdTokenNil;
+ data->vmDomainFile = VMPTR_DomainFile::NullPtr();
+ data->vmTypeHandle = VMPTR_TypeHandle::NullPtr();
+ break;
+ }
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Internal method to marshal: CordbType --> ExpandedTypeData
+//
+// Nb. CordbType::Init need NOT have been called before this...
+// Also, this does not write the type arguments. How this is done depends
+// depends on where this is called from.
+//
+// Parameters:
+// data - OUT: outgoing ExpandedTypeData to fill in with stats about CordbType.
+//-----------------------------------------------------------------------------
+void CordbType::TypeToExpandedTypeData(DebuggerIPCE_ExpandedTypeData *data)
+{
+
+ switch (m_elementType)
+ {
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+
+ data->ArrayTypeData.arrayRank = m_rank;
+ data->elementType = m_elementType;
+ break;
+
+ case ELEMENT_TYPE_BYREF:
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_FNPTR:
+
+ data->elementType = m_elementType;
+ break;
+
+ case ELEMENT_TYPE_CLASS:
+ {
+ data->elementType = m_pClass->IsValueClassNoInit() ? ELEMENT_TYPE_VALUETYPE : ELEMENT_TYPE_CLASS;
+ data->ClassTypeData.metadataToken = m_pClass->GetToken();
+ data->ClassTypeData.vmDomainFile = GetDomainFile();
+ data->ClassTypeData.vmModule = GetModule();
+ data->ClassTypeData.typeHandle = VMPTR_TypeHandle::NullPtr();
+
+ break;
+ }
+ case ELEMENT_TYPE_END:
+ _ASSERTE(!"bad element type!");
+
+ default:
+ data->elementType = m_elementType;
+ break;
+ }
+}
+
+
+void CordbType::TypeToTypeArgData(DebuggerIPCE_TypeArgData *data)
+{
+ TypeToExpandedTypeData(&(data->data));
+ data->numTypeArgs = m_inst.m_cClassTyPars;
+}
+
+
+//-----------------------------------------------------------------------------
+// Query if this CordbType represents a ValueType (Does not include primitives).
+// Since CordbType doesn't record ValueType status, this may involve querying
+// the CordbClass or even asking the Left-Side (if the CordbClass is not init)
+//
+// Return Value:
+// indicates whether this is a value type
+// Note:
+// Throws.
+//-----------------------------------------------------------------------------
+bool CordbType::IsValueType()
+{
+ if (m_elementType == ELEMENT_TYPE_CLASS)
+ {
+ return m_pClass->IsValueClass();
+ }
+ else
+ return false;
+}
+
+//------------------------------------------------------------------------
+// If this is a ptr type, get the CordbType that it points to.
+// Eg, for CordbType("Int*") or CordbType("Int&"), returns CordbType("Int").
+// If not a ptr type, returns null.
+// Since it's all internal, no reference counting.
+// This is effectively a specialized version of DestUnaryType.
+//------------------------------------------------------------------------
+CordbType * CordbType::GetPointerElementType()
+{
+ if ((m_elementType != ELEMENT_TYPE_PTR) && (m_elementType != ELEMENT_TYPE_BYREF))
+ {
+ return NULL;
+ }
+
+ CordbType * pOut;
+ DestUnaryType(&pOut);
+
+ _ASSERTE(pOut != NULL);
+ return pOut;
+}
+//------------------------------------------------------------------------
+// Helper for IsGcRoot.
+// Determine if the element type is a non GC-root candidate.
+// Updating GC-roots requires coordinating with the GC's write-barrier.
+// Whereas non-GC roots can be updated more freely.
+//
+// Parameters:
+// et - An element type.
+// Returns:
+// True if variables of et can be used as a GC root.
+//------------------------------------------------------------------------
+static inline bool IsElementTypeNonGcRoot(CorElementType et)
+{
+ // Functon ptrs are raw data, not GC-roots.
+ if (et == ELEMENT_TYPE_FNPTR)
+ {
+ return true;
+ }
+
+ // This is almost exactly if we're a primitive, but
+ // primitives include some things that could be GC-roots, so we strip those out,
+ return CorIsPrimitiveType(et)
+ && (et != ELEMENT_TYPE_STRING) && (et != ELEMENT_TYPE_VOID); // exlcude these from primitives
+
+}
+//------------------------------------------------------------------------
+// Helper for IsGcRoot
+// Non-gc roots include Value types + non-gc elemement types (like E_T_I4, E_T_FNPTR)
+//
+// Parameters:
+// pType - type to check whether it's a GC-root.
+// Returns:
+// true if we know we're not a GC-root
+// false if we still might be (so caller must do further checkin)
+//------------------------------------------------------------------------
+static inline bool _IsNonGCRootHelper(CordbType * pType)
+{
+ _ASSERTE(pType != NULL);
+
+ CorElementType et = pType->GetElementType();
+ if (IsElementTypeNonGcRoot(et))
+ {
+ return true;
+ }
+
+ HRESULT hr = S_OK;
+ bool fValueClass = false;
+
+ // If we are a value-type, then we can't be a Gc-root.
+ EX_TRY
+ {
+ fValueClass = pType->IsValueType();
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr) || fValueClass)
+ {
+ return true;
+ }
+
+ // Don't know
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Is this type a GC-root. (Not to be confused w/ "does this contain embedded GC roots")
+// All object references are GC-roots. E_T_PTR are actually not GC-roots.
+//
+// Returns:
+// True - if this is a GC-root.
+// False - not a GC root.
+//-----------------------------------------------------------------------------
+bool CordbType::IsGCRoot()
+{
+ // If it's a E_T_PTR type, then look at what it's a a pointer of.
+ CordbType * pPtr = this->GetPointerElementType();
+ if (pPtr == NULL)
+ {
+ // If non pointer, than we can just look at our current type.
+ return !_IsNonGCRootHelper(this);
+ }
+
+ return !_IsNonGCRootHelper(pPtr);
+}
+
+
+//------------------------------------------------------------------------
+// Public function to enumerate type-parameters.
+// Parameters:
+// ppTypeParameterEnum - OUT: on return, get an enumerator.
+// Returns:
+// S_OK on success.
+//------------------------------------------------------------------------
+HRESULT CordbType::EnumerateTypeParameters(ICorDebugTypeEnum **ppTypeParameterEnum)
+{
+ PUBLIC_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppTypeParameterEnum, ICorDebugTypeEnum **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+
+ CordbTypeEnum *icdTPE = CordbTypeEnum::Build(m_appdomain, m_appdomain->GetLongExitNeuterList(), this->m_inst.m_cInst, this->m_inst.m_ppInst);
+ if ( icdTPE == NULL )
+ {
+ (*ppTypeParameterEnum) = NULL;
+ return E_OUTOFMEMORY;
+ }
+
+ (*ppTypeParameterEnum) = static_cast<ICorDebugTypeEnum*> (icdTPE);
+ icdTPE->ExternalAddRef();
+ return S_OK;
+}
+
+
+//-----------------------------------------------------------------------------
+// CordbType::GetBase
+// Public convenience method to get the instantiated base type.
+//
+// Parameters:
+// ppType - OUT: yields the base type for the current type.
+//
+// Returns:
+// S_OK if succeeded.
+//
+HRESULT CordbType::GetBase(ICorDebugType ** ppType)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ ATT_ALLOW_LIVE_DO_STOPGO(this->GetProcess()); // @todo - can this by RequiredStopped?
+
+ HRESULT hr = S_OK;
+
+ LOG((LF_CORDB, LL_EVERYTHING, "CordbType::GetBase called\n"));
+
+ VALIDATE_POINTER_TO_OBJECT(ppType, ICorDebugType **);
+
+ if (m_elementType != ELEMENT_TYPE_CLASS)
+ {
+ return E_INVALIDARG;
+ }
+
+ EX_TRY
+ {
+ CordbType * pType = NULL;
+
+ _ASSERTE(m_pClass != NULL);
+
+ // Get the supertype from metadata for m_class
+ mdToken extendsToken;
+
+ IMetaDataImport * pImport = m_pClass->GetModule()->GetMetaDataImporter(); // throws
+
+ hr = pImport->GetTypeDefProps(m_pClass->MDToken(), NULL, 0, NULL, NULL, &extendsToken);
+ IfFailThrow(hr);
+
+ // Now create a CordbType instance for the base type that has the same type parameters as the derived type.
+ if ((extendsToken == mdTypeDefNil) || (extendsToken == mdTypeRefNil) || (extendsToken == mdTokenNil))
+ {
+ // No base class.
+ pType = NULL;
+ }
+ else if (TypeFromToken(extendsToken) == mdtTypeSpec)
+ {
+ // TypeSpec has a signature. So get the sig and convert it to a CordbType.
+ // generic base class of a generic type is a TypeSpec.
+ // If we have:
+ // class Triple<T,U,V> derives from Pair<T,V>,
+ // then the base class for Triple would be a TypeSpec:
+ // Class(Pair<T,V>), 2 args, ELEMENT_TYPE_VAR #0, ELEMENT_TYPE_VAR#2.
+ // m_inst provides the type-parameters to resolve the ELEMENT_TYPE_VAR types.
+
+ PCCOR_SIGNATURE pSig;
+ ULONG sigSize;
+
+ // Get the signature for the constructed supertype...
+ hr = pImport->GetTypeSpecFromToken(extendsToken, &pSig, &sigSize);
+ IfFailThrow(hr);
+
+ _ASSERTE(pSig != NULL);
+
+ SigParser sigParser(pSig, sigSize);
+
+ // Instantiate the signature of the supertype using the type instantiation for
+ // the current type....
+ hr = SigToType(m_pClass->GetModule(), &sigParser, &m_inst, &pType);
+ IfFailThrow(hr);
+ }
+ else if ((TypeFromToken(extendsToken) == mdtTypeRef) || (TypeFromToken(extendsToken) == mdtTypeDef))
+ {
+ // TypeDef/TypeRef for non-generic base-class class.
+ CordbClass * pSuperClass;
+
+ hr = m_pClass->GetModule()->ResolveTypeRefOrDef(extendsToken, &pSuperClass);
+ IfFailThrow(hr);
+
+ _ASSERTE(pSuperClass != NULL);
+
+ hr = MkUnparameterizedType(m_appdomain, ELEMENT_TYPE_CLASS, pSuperClass, &pType);
+ IfFailThrow(hr);
+ }
+ else
+ {
+ pType = NULL;
+ _ASSERTE(!"unexpected token!");
+ }
+
+ // At this point, we've succeeded
+ _ASSERTE(SUCCEEDED(hr));
+
+ (*ppType) = pType;
+
+ if (*ppType)
+ {
+ pType->AddRef();
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+
+//-----------------------------------------------------------------------------
+// Get rich field information given a token.
+//
+// Parameters:
+// fldToken - metadata field token specifying a field on this Type.
+// ppFieldData - OUT: get the rich field information for the given field
+//
+// Returns:
+// S_OK on success. CORDBG_E_ENC_HANGING_FIELD for EnC fields (common case)
+// Other errors on failure case.
+//-----------------------------------------------------------------------------
+HRESULT CordbType::GetFieldInfo(mdFieldDef fldToken, FieldData ** ppFieldData)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+ HRESULT hr = S_OK;
+
+ *ppFieldData = NULL;
+
+ EX_TRY
+ {
+ if (m_elementType != ELEMENT_TYPE_CLASS)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ // Initialize so that the field information is up-to-date.
+ hr = Init(FALSE);
+ IfFailThrow(hr);
+
+ if (m_pClass->HasTypeParams())
+ {
+ if (m_fieldList.IsEmpty())
+ {
+ ThrowHR(CORDBG_E_FIELD_NOT_AVAILABLE);
+ }
+ else
+ {
+ // Use a static helper function in CordbClass, though we're really
+ // searching through this->m_fields
+ hr = CordbClass::SearchFieldInfo(m_pClass->GetModule(),
+ &m_fieldList,
+ m_pClass->MDToken(),
+ fldToken,
+ ppFieldData);
+ // fall through and return.
+ // Let possible CORDBG_E_ENC_HANGING_FIELD errors propogate
+ }
+ }
+ else
+ {
+ hr = m_pClass->GetFieldInfo(fldToken, ppFieldData); // this is for non-generic types....
+ // Let possible CORDBG_E_ENC_HANGING_FIELD errors propogate
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ _ASSERTE(SUCCEEDED(hr) == (*ppFieldData != NULL));
+ return hr;
+}
+
+
+//-----------------------------------------------------------------------------
+// Class is a class somewhere on the hierarchy for m_type. Search for
+// a CordbType corresponding to the CordbClass, but which has the type-parameters
+// from the current CordbType.
+// In other words, instantiate a CordbType from baseClass, using the type-params
+// in the current Type.
+//
+// For example, given:
+// class C<T>
+// class D : C<int>
+// then if the CordbObjectValue is of type D and pClass is the class
+// for "C", then searching will set relevantType to C<int>. This
+// type is then used to fetch fields from the object.
+//
+// Adds a reference to the resulting type. Since this is for internal
+// use only we probably don't need todo this...
+//
+// Parameters:
+// baseClass - open Type that needs to be instantiated with this CordbType's params.
+// ppRes - OUT: out-parameter to get CordbType. ppRes->GetClass() should equal baseClass.
+//
+// Returns:
+// S_OK on success. CORDBG_E_OBJECT_NEUTERED, CORDBG_E_CLASS_NOT_LOADED, E_INVALIDARG, OOM
+//-----------------------------------------------------------------------------
+HRESULT CordbType::GetParentType(CordbClass *baseClass, CordbType **ppRes)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+
+ // Ensure that we're not trying to match up against a neutered class.
+ if (baseClass->IsNeutered())
+ {
+ return CORDBG_E_OBJECT_NEUTERED;
+ }
+
+ HRESULT hr = S_OK;
+ _ASSERTE(ppRes);
+ *ppRes = NULL;
+ CordbType *res = this;
+ res->AddRef();
+ int safety = 20000; // no inheritance hierarchy is 20000 deep... we include this just in case there's a issue below and we don't terminate
+ while (safety--)
+ {
+ if (res->m_pClass == NULL)
+ {
+ if (FAILED(hr = res->Init(FALSE)))
+ {
+ res->Release();
+ return hr;
+ }
+ }
+ _ASSERTE(res->m_pClass);
+ if (res->m_pClass == baseClass)
+ {
+ // Found it!
+ break;
+ }
+
+ // Another way to determine if we're talking about the
+ // same class... Compare tokens and module.
+ mdTypeDef tok;
+ mdTypeDef targetTok;
+ if (FAILED(hr = res->m_pClass->GetToken(&tok))
+ || FAILED(hr = baseClass->GetToken(&targetTok)))
+ {
+ res->Release();
+ return hr;
+ }
+ if (tok == targetTok && res->m_pClass->GetModule() == baseClass->GetModule())
+ {
+ // Found it!
+ break;
+ }
+
+ // OK, this is not the right class so look up the inheritance chain
+ ICorDebugType *nextType = NULL;
+ if (FAILED(hr = res->GetBase(&nextType)))
+ {
+ res->Release();
+ return hr;
+ }
+
+ res->Release(); // matches the AddRef above and/or the one implicit in GetBase, for all but last time around the loop
+ res = static_cast<CordbType *> (nextType);
+ if (!res || res->m_elementType == ELEMENT_TYPE_OBJECT)
+ {
+ // Did not find it...
+ break;
+ }
+ }
+ // We exit the loop above owning one reference to res.
+ // Upon exit res will either be the appropriate type for the
+ // class we're looking for or will be the CordbType for System.Object
+ // or will be NULL
+
+ // If it's System.Object then assume something's gone wrong with
+ // the way we did the search and bail out to an old fashioned
+ // MkUnparameterizedType on the class given originally
+ if (!res || res->m_elementType == ELEMENT_TYPE_OBJECT)
+ {
+ if (res)
+ res->Release(); // matches the one left over from the loop
+ IfFailRet(CordbType::MkUnparameterizedType(baseClass->GetAppDomain(), ELEMENT_TYPE_CLASS, baseClass, &res));
+ res->AddRef();
+ }
+
+
+ *ppRes = res;
+ return hr;
+}
+
+
+//-----------------------------------------------------------------------------
+// Walk a type tree, writing the number of type args including internal nodes.
+//
+// Parameters:
+// count - IN/OUT: counter to update.
+//-----------------------------------------------------------------------------
+void CordbType::CountTypeDataNodes(unsigned int *count)
+{
+ (*count)++;
+ for (unsigned int i = 0; i < this->m_inst.m_cClassTyPars; i++)
+ {
+ this->m_inst.m_ppInst[i]->CountTypeDataNodes(count);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Internal helper method.
+// Counts the total generic args (including sub-args) for an Instantiation.
+// Eg, for List<int, Pair<string, float>>, it would return 3.
+//
+// Parameters:
+// genericArgsCount - size of the genericArgs array in elements.
+// genericArgs - array of type parameters.
+// count - IN/OUT - will increment with total number of generic args.
+// caller must intialize this (likely to 0).
+//-----------------------------------------------------------------------------
+void CordbType::CountTypeDataNodesForInstantiation(unsigned int genericArgsCount, ICorDebugType *genericArgs[], unsigned int *count)
+{
+ for (unsigned int i = 0; i < genericArgsCount; i++)
+ {
+ (static_cast<CordbType *>(genericArgs[i]))->CountTypeDataNodes(count);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Recursively walk a type tree, writing the type args into a linear.
+// Eg, for List<A, Pair<B, C>>, this will write the TypeArgData buffer
+// for { A, B, C }.
+//
+// Parameters:
+// curr_tyargData - IN/OUT: Pointer into buffer of TypeArgData structures.
+// Caller must ensure this buffer is large enough (probably by calling
+// CountTypeDataNodes).
+// On output, set to the next element in the buffer.
+//-----------------------------------------------------------------------------
+void CordbType::GatherTypeData(CordbType *type, DebuggerIPCE_TypeArgData **curr_tyargData)
+{
+ type->TypeToTypeArgData(*curr_tyargData);
+ (*curr_tyargData)++;
+ for (unsigned int i = 0; i < type->m_inst.m_cClassTyPars; i++)
+ {
+ GatherTypeData(type->m_inst.m_ppInst[i], curr_tyargData);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Flatten Instantiation into a linear buffer of TypeArgData
+// Use CountTypeDataNodesForInstantiation on the instantiation to get a large
+// enough buffer.
+//
+// Parameters:
+// genericArgsCount - size of genericArgs array in elements.
+// genericArgs - incoming array to walk
+// curr_tyargData - IN/OUT: Pointer into buffer of TypeArgData structures.
+// Caller must ensure this buffer is large enough (probably by calling
+// CountTypeDataNodes).
+// On output, set to the next element in the buffer.
+//
+//-----------------------------------------------------------------------------
+void CordbType::GatherTypeDataForInstantiation(unsigned int genericArgsCount, ICorDebugType *genericArgs[], DebuggerIPCE_TypeArgData **curr_tyargData)
+{
+ for (unsigned int i = 0; i < genericArgsCount; i++)
+ {
+ GatherTypeData(static_cast<CordbType *> (genericArgs[i]), curr_tyargData);
+ }
+}
+
+#ifdef FEATURE_64BIT_ALIGNMENT
+// checks if the type requires 8-byte alignment. the algorithm used here
+// was adapted from AdjustArgPtrForAlignment() in bcltype/VarArgsNative.cpp
+HRESULT CordbType::RequiresAlign8(BOOL* isRequired)
+{
+ if (isRequired == NULL)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ *isRequired = FALSE;
+
+ ULONG32 size = 0;
+ GetUnboxedObjectSize(&size);
+
+ if (size >= 8)
+ {
+ CorElementType type;
+ GetType(&type);
+
+ if (type != ELEMENT_TYPE_TYPEDBYREF)
+ {
+ if (type == ELEMENT_TYPE_VALUETYPE)
+ {
+ if (m_typeHandleExact.IsNull())
+ InitInstantiationTypeHandle(FALSE);
+
+ *isRequired = GetProcess()->GetDAC()->RequiresAlign8(m_typeHandleExact);
+ }
+ else
+ {
+ *isRequired = TRUE;
+ }
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+#endif
+
+/* ------------------------------------------------------------------------- *
+ * TypeParameter Enumerator class
+ * ------------------------------------------------------------------------- */
+
+// Factory methods
+CordbTypeEnum* CordbTypeEnum::Build(CordbAppDomain * pAppDomain, NeuterList * pNeuterList, unsigned int cTypars, CordbType **ppTypars)
+{
+ return BuildImpl( pAppDomain, pNeuterList, cTypars, ppTypars );
+}
+
+CordbTypeEnum* CordbTypeEnum::Build(CordbAppDomain * pAppDomain, NeuterList * pNeuterList, unsigned int cTypars, RSSmartPtr<CordbType> *ppTypars)
+{
+ return BuildImpl( pAppDomain, pNeuterList, cTypars, ppTypars );
+}
+
+//-----------------------------------------------------------------------------
+// We need to support taking both an array of CordbType* and an array of RSSmartPtr<CordbType>,
+// but the code is identical in both cases. Rather than duplicate any code explicity, it's better to
+// have the compiler do it for us using this template method.
+// Another option would be to create an IList<T> interface and implementations for both arrays
+// of T* and arrays of RSSmartPtr<T>. This would be more generally useful, but much more code.
+//-----------------------------------------------------------------------------
+template<class T> CordbTypeEnum* CordbTypeEnum::BuildImpl(CordbAppDomain * pAppDomain, NeuterList * pNeuterList, unsigned int cTypars, T* ppTypars)
+{
+ CordbTypeEnum* newEnum = new (nothrow) CordbTypeEnum( pAppDomain, pNeuterList );
+ if( NULL == newEnum )
+ {
+ return NULL;
+ }
+
+ _ASSERTE( newEnum->m_ppTypars == NULL );
+ newEnum->m_ppTypars = new (nothrow) RSSmartPtr<CordbType> [cTypars];
+ if( newEnum->m_ppTypars == NULL )
+ {
+ delete newEnum;
+ return NULL;
+ }
+
+ newEnum->m_iMax = cTypars;
+ for (unsigned int i = 0; i < cTypars; i++)
+ {
+ newEnum->m_ppTypars[i].Assign(ppTypars[i]);
+ }
+
+ return newEnum;
+}
+
+// Private, called only by Build above
+CordbTypeEnum::CordbTypeEnum(CordbAppDomain * pAppDomain, NeuterList * pNeuterList) :
+ CordbBase(pAppDomain->GetProcess(), 0),
+ m_ppTypars(NULL),
+ m_iCurrent(0),
+ m_iMax(0)
+{
+ _ASSERTE(pAppDomain != NULL);
+ _ASSERTE(pNeuterList != NULL);
+
+ m_pAppDomain = pAppDomain;
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ pNeuterList->Add(GetProcess(), this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+}
+
+CordbTypeEnum::~CordbTypeEnum()
+{
+ _ASSERTE(this->IsNeutered());
+}
+
+void CordbTypeEnum::Neuter()
+{
+ delete [] m_ppTypars;
+ m_ppTypars = NULL;
+ m_pAppDomain = NULL;
+
+ CordbBase::Neuter();
+}
+
+
+HRESULT CordbTypeEnum::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugEnum)
+ *pInterface = static_cast<ICorDebugEnum*>(this);
+ else if (id == IID_ICorDebugTypeEnum)
+ *pInterface = static_cast<ICorDebugTypeEnum*>(this);
+ else if (id == IID_IUnknown)
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugTypeEnum*>(this));
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbTypeEnum::Skip(ULONG celt)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = E_FAIL;
+ if ( (m_iCurrent+celt) < m_iMax ||
+ celt == 0)
+ {
+ m_iCurrent += celt;
+ hr = S_OK;
+ }
+
+ return hr;
+}
+
+HRESULT CordbTypeEnum::Reset(void)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ m_iCurrent = 0;
+ return S_OK;
+}
+
+HRESULT CordbTypeEnum::Clone(ICorDebugEnum **ppEnum)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+
+ VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorDebugEnum **);
+
+ HRESULT hr = S_OK;
+
+ CordbTypeEnum *pCVE = CordbTypeEnum::Build( m_pAppDomain, m_pAppDomain->GetLongExitNeuterList(), m_iMax, m_ppTypars );
+ if ( pCVE == NULL )
+ {
+ (*ppEnum) = NULL;
+ hr = E_OUTOFMEMORY;
+ goto LExit;
+ }
+
+ pCVE->AddRef();
+ (*ppEnum) = (ICorDebugEnum*)pCVE;
+
+LExit:
+ return hr;
+}
+
+HRESULT CordbTypeEnum::GetCount(ULONG *pcelt)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT(pcelt, ULONG *);
+
+ if( pcelt == NULL)
+ return E_INVALIDARG;
+
+ (*pcelt) = m_iMax;
+ return S_OK;
+}
+
+//
+// In the event of failure, the current pointer will be left at
+// one element past the troublesome element. Thus, if one were
+// to repeatedly ask for one element to iterate through the
+// array, you would iterate exactly m_iMax times, regardless
+// of individual failures.
+HRESULT CordbTypeEnum::Next(ULONG celt, ICorDebugType *values[], ULONG *pceltFetched)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(values, ICorDebugClass *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched, ULONG *);
+
+ if ((pceltFetched == NULL) && (celt != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ if (celt == 0)
+ {
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = 0;
+ }
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+
+ int iMax = min( m_iMax, m_iCurrent+celt);
+ int i;
+
+ for (i = m_iCurrent; i < iMax; i++)
+ {
+ //printf("CordbTypeEnum::Next, returning = 0x%08x.\n", m_ppTypars[i]);
+ values[i-m_iCurrent] = m_ppTypars[i];
+ values[i-m_iCurrent]->AddRef();
+ }
+
+ int count = (i - m_iCurrent);
+
+ if ( FAILED( hr ) )
+ { //we failed: +1 pushes us past troublesome element
+ m_iCurrent += 1 + count;
+ }
+ else
+ {
+ m_iCurrent += count;
+ }
+
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = count;
+ }
+
+ //
+ // If we reached the end of the enumeration, but not the end
+ // of the number of requested items, we return S_FALSE.
+ //
+ if (((ULONG)count) < celt)
+ {
+ return S_FALSE;
+ }
+
+ return hr;
+}
+
diff --git a/src/debug/di/shared.cpp b/src/debug/di/shared.cpp
new file mode 100644
index 0000000000..7d1e858316
--- /dev/null
+++ b/src/debug/di/shared.cpp
@@ -0,0 +1,16 @@
+// 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.
+
+//
+
+/*
+ *
+ * Common source file for all files in ..\shared for compiling into the right-side
+ *
+ */
+#include "stdafx.h"
+
+#include "../shared/utils.cpp"
+#include "../shared/dbgtransportsession.cpp"
+#include "../shared/stringcopyholder.cpp"
diff --git a/src/debug/di/shimcallback.cpp b/src/debug/di/shimcallback.cpp
new file mode 100644
index 0000000000..f134df703a
--- /dev/null
+++ b/src/debug/di/shimcallback.cpp
@@ -0,0 +1,1317 @@
+// 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.
+//*****************************************************************************
+// File: ShimCallback.cpp
+//
+
+//
+// The V3 ICD debugging APIs have a lower abstraction level than V2.
+// This provides V2 ICD debugging functionality on top of the V3 debugger object.
+//*****************************************************************************
+
+#include "stdafx.h"
+
+#include "safewrap.h"
+#include "check.h"
+
+#include <limits.h>
+#include "shimpriv.h"
+
+
+//
+// Callback that shim provides, which then queues up the events.
+//
+ShimProxyCallback::ShimProxyCallback(ShimProcess * pShim)
+ : m_cRef(0)
+{
+ m_pShim = pShim;
+}
+
+// Implement IUnknown
+ULONG ShimProxyCallback::AddRef()
+{
+ InterlockedIncrement(&m_cRef);
+ return m_cRef;
+}
+ULONG ShimProxyCallback::Release()
+{
+ LONG ref = InterlockedDecrement(&m_cRef);
+ if (ref == 0)
+ {
+ delete this;
+ return 0;
+ }
+ return ref;
+
+}
+HRESULT ShimProxyCallback::QueryInterface(REFIID riid, void **ppInterface)
+{
+ if (riid == IID_ICorDebugManagedCallback)
+ {
+ *ppInterface = static_cast<ICorDebugManagedCallback*>(this);
+ }
+ else if (riid == IID_ICorDebugManagedCallback2)
+ {
+ *ppInterface = static_cast<ICorDebugManagedCallback2*>(this);
+ }
+ else if (riid == IID_ICorDebugManagedCallback3)
+ {
+ *ppInterface = static_cast<ICorDebugManagedCallback3*>(this);
+ }
+ else if (riid == IID_IUnknown)
+ {
+ *ppInterface = static_cast<IUnknown*>(static_cast<ICorDebugManagedCallback*>(this));
+ }
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ this->AddRef();
+ return S_OK;
+}
+
+//
+// Map from an old frame to a new one.
+//
+// Arguments:
+// pThread - thread that frame is on
+// pOldFrame - old frame before the continue, may have gotten neutered.
+//
+// Returns:
+// a new, non-neutered frame that matches the old frame.
+//
+// Notes:
+// Called by event handlers below (which are considered Outside the RS).
+// No adjust of reference, Thread already has reference.
+// @dbgtodo shim-stackwalks: this is used for exception callbacks, which may change for V3.
+ICorDebugFrame * UpdateFrame(ICorDebugThread * pThread, ICorDebugFrame * pOldFrame)
+{
+ PUBLIC_API_ENTRY_FOR_SHIM(NULL);
+
+ RSExtSmartPtr<ICorDebugFrame> pNewFrame;
+
+ EX_TRY
+ {
+ CordbFrame * pFrame = static_cast<CordbFrame *> (pOldFrame);
+ if (pFrame != NULL)
+ {
+ FramePointer fp = pFrame->GetFramePointer();
+
+ CordbThread * pThread2 = static_cast<CordbThread *> (pThread);
+ pThread2->FindFrame(&pNewFrame, fp);
+
+ //
+ }
+ }
+ EX_CATCH
+ {
+ // Do not throw out of this function. Doing so means that the debugger never gets a chance to
+ // continue the debuggee process. This will lead to a hang. Instead, try to make a best effort to
+ // continue with a NULL ICDFrame. VS is able to handle this gracefully.
+ pNewFrame.Assign(NULL);
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+
+ return pNewFrame;
+}
+
+
+
+//
+// Below this was autogenerated
+//
+
+// Implementation of ICorDebugManagedCallback::Breakpoint
+HRESULT ShimProxyCallback::Breakpoint(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugBreakpoint * pBreakpoint)
+{
+ m_pShim->PreDispatchEvent();
+ class BreakpointEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ RSExtSmartPtr<ICorDebugBreakpoint > m_pBreakpoint;
+
+ public:
+ // Ctor
+ BreakpointEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugBreakpoint * pBreakpoint) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_pBreakpoint.Assign(pBreakpoint);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->Breakpoint(m_pAppDomain, m_pThread, m_pBreakpoint);
+ }
+ }; // end class BreakpointEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new BreakpointEvent(pAppDomain, pThread, pBreakpoint));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::Breakpoint
+
+
+// Implementation of ICorDebugManagedCallback::StepComplete
+HRESULT ShimProxyCallback::StepComplete(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugStepper * pStepper, CorDebugStepReason reason)
+{
+ m_pShim->PreDispatchEvent();
+ class StepCompleteEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ RSExtSmartPtr<ICorDebugStepper > m_pStepper;
+ CorDebugStepReason m_reason;
+
+ public:
+ // Ctor
+ StepCompleteEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugStepper * pStepper, CorDebugStepReason reason) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_pStepper.Assign(pStepper);
+ this->m_reason = reason;
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->StepComplete(m_pAppDomain, m_pThread, m_pStepper, m_reason);
+ }
+ }; // end class StepCompleteEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new StepCompleteEvent(pAppDomain, pThread, pStepper, reason));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::StepComplete
+
+
+// Implementation of ICorDebugManagedCallback::Break
+HRESULT ShimProxyCallback::Break(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread)
+{
+ m_pShim->PreDispatchEvent();
+ class BreakEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+
+ public:
+ // Ctor
+ BreakEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->Break(m_pAppDomain, m_pThread);
+ }
+ }; // end class BreakEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new BreakEvent(pAppDomain, pThread));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::Break
+
+
+// Implementation of ICorDebugManagedCallback::Exception
+HRESULT ShimProxyCallback::Exception(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, BOOL fUnhandled)
+{
+ m_pShim->PreDispatchEvent();
+ class ExceptionEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ BOOL m_fUnhandled;
+
+ public:
+ // Ctor
+ ExceptionEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, BOOL fUnhandled) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_fUnhandled = fUnhandled;
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->Exception(m_pAppDomain, m_pThread, m_fUnhandled);
+ }
+ }; // end class ExceptionEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new ExceptionEvent(pAppDomain, pThread, fUnhandled));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::Exception
+
+
+// Implementation of ICorDebugManagedCallback::EvalComplete
+HRESULT ShimProxyCallback::EvalComplete(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugEval * pEval)
+{
+ m_pShim->PreDispatchEvent();
+ class EvalCompleteEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ RSExtSmartPtr<ICorDebugEval > m_pEval;
+
+ public:
+ // Ctor
+ EvalCompleteEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugEval * pEval) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_pEval.Assign(pEval);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->EvalComplete(m_pAppDomain, m_pThread, m_pEval);
+ }
+ }; // end class EvalCompleteEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new EvalCompleteEvent(pAppDomain, pThread, pEval));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::EvalComplete
+
+
+// Implementation of ICorDebugManagedCallback::EvalException
+HRESULT ShimProxyCallback::EvalException(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugEval * pEval)
+{
+ m_pShim->PreDispatchEvent();
+ class EvalExceptionEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ RSExtSmartPtr<ICorDebugEval > m_pEval;
+
+ public:
+ // Ctor
+ EvalExceptionEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugEval * pEval) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_pEval.Assign(pEval);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->EvalException(m_pAppDomain, m_pThread, m_pEval);
+ }
+ }; // end class EvalExceptionEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new EvalExceptionEvent(pAppDomain, pThread, pEval));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::EvalException
+
+
+// Implementation of ICorDebugManagedCallback::CreateProcess
+// This will only be called for a Real create-process event.
+HRESULT ShimProxyCallback::CreateProcess(ICorDebugProcess * pProcess)
+{
+ m_pShim->PreDispatchEvent(true);
+ QueueCreateProcess(pProcess);
+ return S_OK;
+}
+
+void ShimProxyCallback::QueueCreateProcess(ICorDebugProcess * pProcess)
+{
+ class CreateProcessEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugProcess > m_pProcess;
+
+ public:
+ // Ctor
+ CreateProcessEvent(ICorDebugProcess * pProcess, ShimProcess * pShim) :
+ ManagedEvent(),
+ m_pShim(pShim)
+ {
+ this->m_pProcess.Assign(pProcess);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ // signal that we are in the callback--this will be cleared in code:CordbProcess::ContinueInternal
+ m_pShim->SetInCreateProcess(true);
+ return args.GetCallback1()->CreateProcess(m_pProcess);
+ }
+
+ // we need access to the shim in Dispatch so we can set the InCreateProcess flag to keep track of
+ // when we are actually in the callback. We need this information to be able to emulate
+ // the hresult logic in v2.0.
+ ShimProcess * m_pShim;
+ }; // end class CreateProcessEvent
+
+ if (!m_pShim->RemoveDuplicateCreationEventIfPresent(pProcess))
+ {
+ m_pShim->GetManagedEventQueue()->QueueEvent(new CreateProcessEvent(pProcess, m_pShim));
+ }
+} // end of methodICorDebugManagedCallback::CreateProcess
+
+
+// Implementation of ICorDebugManagedCallback::ExitProcess
+HRESULT ShimProxyCallback::ExitProcess(ICorDebugProcess * pProcess)
+{
+ m_pShim->PreDispatchEvent();
+ class ExitProcessEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugProcess > m_pProcess;
+
+ public:
+ // Ctor
+ ExitProcessEvent(ICorDebugProcess * pProcess) :
+ ManagedEvent()
+ {
+ this->m_pProcess.Assign(pProcess);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->ExitProcess(m_pProcess);
+ }
+ }; // end class ExitProcessEvent
+
+ m_pShim->RemoveDuplicateCreationEventIfPresent(pProcess);
+ m_pShim->GetManagedEventQueue()->QueueEvent(new ExitProcessEvent(pProcess));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::ExitProcess
+
+
+// Implementation of ICorDebugManagedCallback::CreateThread
+HRESULT ShimProxyCallback::CreateThread(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread)
+{
+ m_pShim->PreDispatchEvent();
+ class CreateThreadEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+
+ public:
+ // Ctor
+ CreateThreadEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->CreateThread(m_pAppDomain, m_pThread);
+ }
+ }; // end class CreateThreadEvent
+
+ if (!m_pShim->RemoveDuplicateCreationEventIfPresent(pThread))
+ {
+ m_pShim->GetManagedEventQueue()->QueueEvent(new CreateThreadEvent(pAppDomain, pThread));
+ }
+ return S_OK;
+} // end of methodICorDebugManagedCallback::CreateThread
+
+
+// Implementation of ICorDebugManagedCallback::ExitThread
+HRESULT ShimProxyCallback::ExitThread(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread)
+{
+ m_pShim->PreDispatchEvent();
+ class ExitThreadEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+
+ public:
+ // Ctor
+ ExitThreadEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->ExitThread(m_pAppDomain, m_pThread);
+ }
+ }; // end class ExitThreadEvent
+
+ m_pShim->RemoveDuplicateCreationEventIfPresent(pThread);
+ m_pShim->GetManagedEventQueue()->QueueEvent(new ExitThreadEvent(pAppDomain, pThread));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::ExitThread
+
+
+// Called from fake attach events.
+//
+// Arguments:
+// pAppDomain - appdomain for the LoadModule debug event
+// pModule - module being loaded.
+//
+// Notes:
+// See code:ShimProcess::QueueFakeAttachEvents
+// This is the fake version of code:ShimProxyCallback::LoadModule.
+// It sends an IPC event to go in process to collect information that we can't yet get via
+// DAC from out-of-proc.
+void ShimProxyCallback::FakeLoadModule(ICorDebugAppDomain *pAppDomain, ICorDebugModule *pModule)
+{
+ class FakeLoadModuleEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugModule > m_pModule;
+
+ public:
+ // Ctor
+ FakeLoadModuleEvent(ICorDebugAppDomain * pAppDomain, ICorDebugModule * pModule, ShimProcess * pShim) :
+ ManagedEvent(),
+ m_pShim(pShim)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pModule.Assign(pModule);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ // signal that we are in the callback--this will be cleared in code:CordbProcess::ContinueInternal
+ m_pShim->SetInLoadModule(true);
+ return args.GetCallback1()->LoadModule(m_pAppDomain, m_pModule);
+ }
+
+ // we need access to the shim in Dispatch so we can set the InLoadModule flag to keep track
+ // when we are actually in the callback. We need this information to be able to emulate
+ // the hresult logic in v2.0.
+ ShimProcess * m_pShim;
+ }; // end class LoadModuleEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new FakeLoadModuleEvent(pAppDomain, pModule, m_pShim));
+} // end of methodICorDebugManagedCallback::LoadModule
+
+
+// Implementation of ICorDebugManagedCallback::LoadModule
+HRESULT ShimProxyCallback::LoadModule(ICorDebugAppDomain * pAppDomain, ICorDebugModule * pModule)
+{
+ m_pShim->PreDispatchEvent();
+ class LoadModuleEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugModule > m_pModule;
+
+ public:
+ // Ctor
+ LoadModuleEvent(ICorDebugAppDomain * pAppDomain, ICorDebugModule * pModule) :
+ ManagedEvent()
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pModule.Assign(pModule);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->LoadModule(m_pAppDomain, m_pModule);
+ }
+ }; // end class LoadModuleEvent
+
+ if (!m_pShim->RemoveDuplicateCreationEventIfPresent(pModule))
+ {
+ m_pShim->GetManagedEventQueue()->QueueEvent(new LoadModuleEvent(pAppDomain, pModule));
+ }
+ return S_OK;
+} // end of methodICorDebugManagedCallback::LoadModule
+
+
+// Implementation of ICorDebugManagedCallback::UnloadModule
+HRESULT ShimProxyCallback::UnloadModule(ICorDebugAppDomain * pAppDomain, ICorDebugModule * pModule)
+{
+ m_pShim->PreDispatchEvent();
+ class UnloadModuleEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugModule > m_pModule;
+
+ public:
+ // Ctor
+ UnloadModuleEvent(ICorDebugAppDomain * pAppDomain, ICorDebugModule * pModule) :
+ ManagedEvent()
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pModule.Assign(pModule);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->UnloadModule(m_pAppDomain, m_pModule);
+ }
+ }; // end class UnloadModuleEvent
+
+ m_pShim->RemoveDuplicateCreationEventIfPresent(pModule);
+ m_pShim->GetManagedEventQueue()->QueueEvent(new UnloadModuleEvent(pAppDomain, pModule));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::UnloadModule
+
+
+// Implementation of ICorDebugManagedCallback::LoadClass
+HRESULT ShimProxyCallback::LoadClass(ICorDebugAppDomain * pAppDomain, ICorDebugClass * pClass)
+{
+ m_pShim->PreDispatchEvent();
+ class LoadClassEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugClass > m_pClass;
+
+ public:
+ // Ctor
+ LoadClassEvent(ICorDebugAppDomain * pAppDomain, ICorDebugClass * pClass) :
+ ManagedEvent()
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pClass.Assign(pClass);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->LoadClass(m_pAppDomain, m_pClass);
+ }
+ }; // end class LoadClassEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new LoadClassEvent(pAppDomain, pClass));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::LoadClass
+
+
+// Implementation of ICorDebugManagedCallback::UnloadClass
+HRESULT ShimProxyCallback::UnloadClass(ICorDebugAppDomain * pAppDomain, ICorDebugClass * pClass)
+{
+ m_pShim->PreDispatchEvent();
+ class UnloadClassEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugClass > m_pClass;
+
+ public:
+ // Ctor
+ UnloadClassEvent(ICorDebugAppDomain * pAppDomain, ICorDebugClass * pClass) :
+ ManagedEvent()
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pClass.Assign(pClass);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->UnloadClass(m_pAppDomain, m_pClass);
+ }
+ }; // end class UnloadClassEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new UnloadClassEvent(pAppDomain, pClass));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::UnloadClass
+
+
+// Implementation of ICorDebugManagedCallback::DebuggerError
+HRESULT ShimProxyCallback::DebuggerError(ICorDebugProcess * pProcess, HRESULT errorHR, DWORD errorCode)
+{
+ m_pShim->PreDispatchEvent();
+ class DebuggerErrorEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugProcess > m_pProcess;
+ HRESULT m_errorHR;
+ DWORD m_errorCode;
+
+ public:
+ // Ctor
+ DebuggerErrorEvent(ICorDebugProcess * pProcess, HRESULT errorHR, DWORD errorCode) :
+ ManagedEvent()
+ {
+ this->m_pProcess.Assign(pProcess);
+ this->m_errorHR = errorHR;
+ this->m_errorCode = errorCode;
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->DebuggerError(m_pProcess, m_errorHR, m_errorCode);
+ }
+ }; // end class DebuggerErrorEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new DebuggerErrorEvent(pProcess, errorHR, errorCode));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::DebuggerError
+
+
+// Implementation of ICorDebugManagedCallback::LogMessage
+HRESULT ShimProxyCallback::LogMessage(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, LONG lLevel, __in LPWSTR pLogSwitchName, __in LPWSTR pMessage)
+{
+ m_pShim->PreDispatchEvent();
+ class LogMessageEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ LONG m_lLevel;
+ StringCopyHolder m_pLogSwitchName;
+ StringCopyHolder m_pMessage;
+
+ public:
+ // Ctor
+ LogMessageEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, LONG lLevel, LPCWSTR pLogSwitchName, LPCWSTR pMessage) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_lLevel = lLevel;
+ this->m_pLogSwitchName.AssignCopy(pLogSwitchName);
+ this->m_pMessage.AssignCopy(pMessage);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->LogMessage(m_pAppDomain, m_pThread, m_lLevel, const_cast<WCHAR*>((const WCHAR*)m_pLogSwitchName), const_cast<WCHAR*>((const WCHAR*)m_pMessage));
+ }
+ }; // end class LogMessageEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new LogMessageEvent(pAppDomain, pThread, lLevel, pLogSwitchName, pMessage));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::LogMessage
+
+
+// Implementation of ICorDebugManagedCallback::LogSwitch
+HRESULT ShimProxyCallback::LogSwitch(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, LONG lLevel, ULONG ulReason, __in LPWSTR pLogSwitchName, __in LPWSTR pParentName)
+{
+ m_pShim->PreDispatchEvent();
+ class LogSwitchEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ LONG m_lLevel;
+ ULONG m_ulReason;
+ StringCopyHolder m_pLogSwitchName;
+ StringCopyHolder m_pParentName;
+
+ public:
+ // Ctor
+ LogSwitchEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, LONG lLevel, ULONG ulReason, LPCWSTR pLogSwitchName, LPCWSTR pParentName) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_lLevel = lLevel;
+ this->m_ulReason = ulReason;
+ this->m_pLogSwitchName.AssignCopy(pLogSwitchName);
+ this->m_pParentName.AssignCopy(pParentName);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->LogSwitch(m_pAppDomain, m_pThread, m_lLevel, m_ulReason, const_cast<WCHAR*>((const WCHAR*)m_pLogSwitchName), const_cast<WCHAR*>((const WCHAR*)m_pParentName));
+ }
+ }; // end class LogSwitchEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new LogSwitchEvent(pAppDomain, pThread, lLevel, ulReason, pLogSwitchName, pParentName));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::LogSwitch
+
+
+// Implementation of ICorDebugManagedCallback::CreateAppDomain
+HRESULT ShimProxyCallback::CreateAppDomain(ICorDebugProcess * pProcess, ICorDebugAppDomain * pAppDomain)
+{
+ m_pShim->PreDispatchEvent();
+ class CreateAppDomainEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugProcess > m_pProcess;
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+
+ public:
+ // Ctor
+ CreateAppDomainEvent(ICorDebugProcess * pProcess, ICorDebugAppDomain * pAppDomain) :
+ ManagedEvent()
+ {
+ this->m_pProcess.Assign(pProcess);
+ this->m_pAppDomain.Assign(pAppDomain);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->CreateAppDomain(m_pProcess, m_pAppDomain);
+ }
+ }; // end class CreateAppDomainEvent
+
+ if (!m_pShim->RemoveDuplicateCreationEventIfPresent(pAppDomain))
+ {
+ m_pShim->GetManagedEventQueue()->QueueEvent(new CreateAppDomainEvent(pProcess, pAppDomain));
+ }
+ return S_OK;
+} // end of methodICorDebugManagedCallback::CreateAppDomain
+
+
+// Implementation of ICorDebugManagedCallback::ExitAppDomain
+HRESULT ShimProxyCallback::ExitAppDomain(ICorDebugProcess * pProcess, ICorDebugAppDomain * pAppDomain)
+{
+ m_pShim->PreDispatchEvent();
+ class ExitAppDomainEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugProcess > m_pProcess;
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+
+ public:
+ // Ctor
+ ExitAppDomainEvent(ICorDebugProcess * pProcess, ICorDebugAppDomain * pAppDomain) :
+ ManagedEvent()
+ {
+ this->m_pProcess.Assign(pProcess);
+ this->m_pAppDomain.Assign(pAppDomain);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->ExitAppDomain(m_pProcess, m_pAppDomain);
+ }
+ }; // end class ExitAppDomainEvent
+
+ m_pShim->RemoveDuplicateCreationEventIfPresent(pAppDomain);
+ m_pShim->GetManagedEventQueue()->QueueEvent(new ExitAppDomainEvent(pProcess, pAppDomain));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::ExitAppDomain
+
+
+// Implementation of ICorDebugManagedCallback::LoadAssembly
+HRESULT ShimProxyCallback::LoadAssembly(ICorDebugAppDomain * pAppDomain, ICorDebugAssembly * pAssembly)
+{
+ m_pShim->PreDispatchEvent();
+ class LoadAssemblyEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugAssembly > m_pAssembly;
+
+ public:
+ // Ctor
+ LoadAssemblyEvent(ICorDebugAppDomain * pAppDomain, ICorDebugAssembly * pAssembly) :
+ ManagedEvent()
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pAssembly.Assign(pAssembly);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->LoadAssembly(m_pAppDomain, m_pAssembly);
+ }
+ }; // end class LoadAssemblyEvent
+
+ if (!m_pShim->RemoveDuplicateCreationEventIfPresent(pAssembly))
+ {
+ m_pShim->GetManagedEventQueue()->QueueEvent(new LoadAssemblyEvent(pAppDomain, pAssembly));
+ }
+ return S_OK;
+} // end of methodICorDebugManagedCallback::LoadAssembly
+
+
+// Implementation of ICorDebugManagedCallback::UnloadAssembly
+HRESULT ShimProxyCallback::UnloadAssembly(ICorDebugAppDomain * pAppDomain, ICorDebugAssembly * pAssembly)
+{
+ m_pShim->PreDispatchEvent();
+ class UnloadAssemblyEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugAssembly > m_pAssembly;
+
+ public:
+ // Ctor
+ UnloadAssemblyEvent(ICorDebugAppDomain * pAppDomain, ICorDebugAssembly * pAssembly) :
+ ManagedEvent()
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pAssembly.Assign(pAssembly);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->UnloadAssembly(m_pAppDomain, m_pAssembly);
+ }
+ }; // end class UnloadAssemblyEvent
+
+ m_pShim->RemoveDuplicateCreationEventIfPresent(pAssembly);
+ m_pShim->GetManagedEventQueue()->QueueEvent(new UnloadAssemblyEvent(pAppDomain, pAssembly));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::UnloadAssembly
+
+
+// Implementation of ICorDebugManagedCallback::ControlCTrap
+HRESULT ShimProxyCallback::ControlCTrap(ICorDebugProcess * pProcess)
+{
+ m_pShim->PreDispatchEvent();
+ class ControlCTrapEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugProcess > m_pProcess;
+
+ public:
+ // Ctor
+ ControlCTrapEvent(ICorDebugProcess * pProcess) :
+ ManagedEvent()
+ {
+ this->m_pProcess.Assign(pProcess);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->ControlCTrap(m_pProcess);
+ }
+ }; // end class ControlCTrapEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new ControlCTrapEvent(pProcess));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::ControlCTrap
+
+
+// Implementation of ICorDebugManagedCallback::NameChange
+HRESULT ShimProxyCallback::NameChange(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread)
+{
+ m_pShim->PreDispatchEvent();
+ class NameChangeEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+
+ public:
+ // Ctor
+ NameChangeEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->NameChange(m_pAppDomain, m_pThread);
+ }
+ }; // end class NameChangeEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new NameChangeEvent(pAppDomain, pThread));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::NameChange
+
+
+// Implementation of ICorDebugManagedCallback::UpdateModuleSymbols
+HRESULT ShimProxyCallback::UpdateModuleSymbols(ICorDebugAppDomain * pAppDomain, ICorDebugModule * pModule, IStream * pSymbolStream)
+{
+ m_pShim->PreDispatchEvent();
+ class UpdateModuleSymbolsEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugModule > m_pModule;
+ RSExtSmartPtr<IStream > m_pSymbolStream;
+
+ public:
+ // Ctor
+ UpdateModuleSymbolsEvent(ICorDebugAppDomain * pAppDomain, ICorDebugModule * pModule, IStream * pSymbolStream) :
+ ManagedEvent()
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pModule.Assign(pModule);
+ this->m_pSymbolStream.Assign(pSymbolStream);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->UpdateModuleSymbols(m_pAppDomain, m_pModule, m_pSymbolStream);
+ }
+ }; // end class UpdateModuleSymbolsEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new UpdateModuleSymbolsEvent(pAppDomain, pModule, pSymbolStream));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::UpdateModuleSymbols
+
+
+// Implementation of ICorDebugManagedCallback::EditAndContinueRemap
+HRESULT ShimProxyCallback::EditAndContinueRemap(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugFunction * pFunction, BOOL fAccurate)
+{
+ m_pShim->PreDispatchEvent();
+ class EditAndContinueRemapEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ RSExtSmartPtr<ICorDebugFunction > m_pFunction;
+ BOOL m_fAccurate;
+
+ public:
+ // Ctor
+ EditAndContinueRemapEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugFunction * pFunction, BOOL fAccurate) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_pFunction.Assign(pFunction);
+ this->m_fAccurate = fAccurate;
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->EditAndContinueRemap(m_pAppDomain, m_pThread, m_pFunction, m_fAccurate);
+ }
+ }; // end class EditAndContinueRemapEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new EditAndContinueRemapEvent(pAppDomain, pThread, pFunction, fAccurate));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::EditAndContinueRemap
+
+
+// Implementation of ICorDebugManagedCallback::BreakpointSetError
+HRESULT ShimProxyCallback::BreakpointSetError(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugBreakpoint * pBreakpoint, DWORD dwError)
+{
+ m_pShim->PreDispatchEvent();
+ class BreakpointSetErrorEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ RSExtSmartPtr<ICorDebugBreakpoint > m_pBreakpoint;
+ DWORD m_dwError;
+
+ public:
+ // Ctor
+ BreakpointSetErrorEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugBreakpoint * pBreakpoint, DWORD dwError) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_pBreakpoint.Assign(pBreakpoint);
+ this->m_dwError = dwError;
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->BreakpointSetError(m_pAppDomain, m_pThread, m_pBreakpoint, m_dwError);
+ }
+ }; // end class BreakpointSetErrorEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new BreakpointSetErrorEvent(pAppDomain, pThread, pBreakpoint, dwError));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::BreakpointSetError
+
+
+// Implementation of ICorDebugManagedCallback2::FunctionRemapOpportunity
+HRESULT ShimProxyCallback::FunctionRemapOpportunity(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugFunction * pOldFunction, ICorDebugFunction * pNewFunction, ULONG32 oldILOffset)
+{
+ m_pShim->PreDispatchEvent();
+ class FunctionRemapOpportunityEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ RSExtSmartPtr<ICorDebugFunction > m_pOldFunction;
+ RSExtSmartPtr<ICorDebugFunction > m_pNewFunction;
+ ULONG32 m_oldILOffset;
+
+ public:
+ // Ctor
+ FunctionRemapOpportunityEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugFunction * pOldFunction, ICorDebugFunction * pNewFunction, ULONG32 oldILOffset) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_pOldFunction.Assign(pOldFunction);
+ this->m_pNewFunction.Assign(pNewFunction);
+ this->m_oldILOffset = oldILOffset;
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback2()->FunctionRemapOpportunity(m_pAppDomain, m_pThread, m_pOldFunction, m_pNewFunction, m_oldILOffset);
+ }
+ }; // end class FunctionRemapOpportunityEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new FunctionRemapOpportunityEvent(pAppDomain, pThread, pOldFunction, pNewFunction, oldILOffset));
+ return S_OK;
+} // end of methodICorDebugManagedCallback2::FunctionRemapOpportunity
+
+
+// Implementation of ICorDebugManagedCallback2::CreateConnection
+HRESULT ShimProxyCallback::CreateConnection(ICorDebugProcess * pProcess, CONNID dwConnectionId, __in LPWSTR pConnectionName)
+{
+ m_pShim->PreDispatchEvent();
+ class CreateConnectionEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugProcess > m_pProcess;
+ CONNID m_dwConnectionId;
+ StringCopyHolder m_pConnectionName;
+
+ public:
+ // Ctor
+ CreateConnectionEvent(ICorDebugProcess * pProcess, CONNID dwConnectionId, LPCWSTR pConnectionName) :
+ ManagedEvent()
+ {
+ this->m_pProcess.Assign(pProcess);
+ this->m_dwConnectionId = dwConnectionId;
+ this->m_pConnectionName.AssignCopy(pConnectionName);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback2()->CreateConnection(m_pProcess, m_dwConnectionId, const_cast<WCHAR*>((const WCHAR*)m_pConnectionName));
+ }
+ }; // end class CreateConnectionEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new CreateConnectionEvent(pProcess, dwConnectionId, pConnectionName));
+ return S_OK;
+} // end of methodICorDebugManagedCallback2::CreateConnection
+
+
+// Implementation of ICorDebugManagedCallback2::ChangeConnection
+HRESULT ShimProxyCallback::ChangeConnection(ICorDebugProcess * pProcess, CONNID dwConnectionId)
+{
+ m_pShim->PreDispatchEvent();
+ class ChangeConnectionEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugProcess > m_pProcess;
+ CONNID m_dwConnectionId;
+
+ public:
+ // Ctor
+ ChangeConnectionEvent(ICorDebugProcess * pProcess, CONNID dwConnectionId) :
+ ManagedEvent()
+ {
+ this->m_pProcess.Assign(pProcess);
+ this->m_dwConnectionId = dwConnectionId;
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback2()->ChangeConnection(m_pProcess, m_dwConnectionId);
+ }
+ }; // end class ChangeConnectionEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new ChangeConnectionEvent(pProcess, dwConnectionId));
+ return S_OK;
+} // end of methodICorDebugManagedCallback2::ChangeConnection
+
+
+// Implementation of ICorDebugManagedCallback2::DestroyConnection
+HRESULT ShimProxyCallback::DestroyConnection(ICorDebugProcess * pProcess, CONNID dwConnectionId)
+{
+ m_pShim->PreDispatchEvent();
+ class DestroyConnectionEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugProcess > m_pProcess;
+ CONNID m_dwConnectionId;
+
+ public:
+ // Ctor
+ DestroyConnectionEvent(ICorDebugProcess * pProcess, CONNID dwConnectionId) :
+ ManagedEvent()
+ {
+ this->m_pProcess.Assign(pProcess);
+ this->m_dwConnectionId = dwConnectionId;
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback2()->DestroyConnection(m_pProcess, m_dwConnectionId);
+ }
+ }; // end class DestroyConnectionEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new DestroyConnectionEvent(pProcess, dwConnectionId));
+ return S_OK;
+} // end of methodICorDebugManagedCallback2::DestroyConnection
+
+
+
+// Implementation of ICorDebugManagedCallback2::Exception
+HRESULT ShimProxyCallback::Exception(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugFrame * pFrame, ULONG32 nOffset, CorDebugExceptionCallbackType dwEventType, DWORD dwFlags)
+{
+ m_pShim->PreDispatchEvent();
+ class ExceptionEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ RSExtSmartPtr<ICorDebugFrame > m_pFrame;
+ ULONG32 m_nOffset;
+ CorDebugExceptionCallbackType m_dwEventType;
+ DWORD m_dwFlags;
+
+ public:
+ // Ctor
+ ExceptionEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugFrame * pFrame, ULONG32 nOffset, CorDebugExceptionCallbackType dwEventType, DWORD dwFlags) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_pFrame.Assign(pFrame);
+ this->m_nOffset = nOffset;
+ this->m_dwEventType = dwEventType;
+ this->m_dwFlags = dwFlags;
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback2()->Exception(m_pAppDomain, m_pThread, UpdateFrame(m_pThread, m_pFrame), m_nOffset, m_dwEventType, m_dwFlags);
+ }
+ }; // end class ExceptionEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new ExceptionEvent(pAppDomain, pThread, pFrame, nOffset, dwEventType, dwFlags));
+ return S_OK;
+} // end of methodICorDebugManagedCallback2::Exception
+
+
+// Implementation of ICorDebugManagedCallback2::ExceptionUnwind
+HRESULT ShimProxyCallback::ExceptionUnwind(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, CorDebugExceptionUnwindCallbackType dwEventType, DWORD dwFlags)
+{
+ m_pShim->PreDispatchEvent();
+ class ExceptionUnwindEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ CorDebugExceptionUnwindCallbackType m_dwEventType;
+ DWORD m_dwFlags;
+
+ public:
+ // Ctor
+ ExceptionUnwindEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, CorDebugExceptionUnwindCallbackType dwEventType, DWORD dwFlags) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_dwEventType = dwEventType;
+ this->m_dwFlags = dwFlags;
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback2()->ExceptionUnwind(m_pAppDomain, m_pThread, m_dwEventType, m_dwFlags);
+ }
+ }; // end class ExceptionUnwindEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new ExceptionUnwindEvent(pAppDomain, pThread, dwEventType, dwFlags));
+ return S_OK;
+} // end of methodICorDebugManagedCallback2::ExceptionUnwind
+
+
+// Implementation of ICorDebugManagedCallback2::FunctionRemapComplete
+HRESULT ShimProxyCallback::FunctionRemapComplete(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugFunction * pFunction)
+{
+ m_pShim->PreDispatchEvent();
+ class FunctionRemapCompleteEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ RSExtSmartPtr<ICorDebugFunction > m_pFunction;
+
+ public:
+ // Ctor
+ FunctionRemapCompleteEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugFunction * pFunction) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_pFunction.Assign(pFunction);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback2()->FunctionRemapComplete(m_pAppDomain, m_pThread, m_pFunction);
+ }
+ }; // end class FunctionRemapCompleteEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new FunctionRemapCompleteEvent(pAppDomain, pThread, pFunction));
+ return S_OK;
+} // end of methodICorDebugManagedCallback2::FunctionRemapComplete
+
+
+// Implementation of ICorDebugManagedCallback2::MDANotification
+HRESULT ShimProxyCallback::MDANotification(ICorDebugController * pController, ICorDebugThread * pThread, ICorDebugMDA * pMDA)
+{
+ m_pShim->PreDispatchEvent();
+ class MDANotificationEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugController > m_pController;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ RSExtSmartPtr<ICorDebugMDA > m_pMDA;
+
+ public:
+ // Ctor
+ MDANotificationEvent(ICorDebugController * pController, ICorDebugThread * pThread, ICorDebugMDA * pMDA) :
+ ManagedEvent(pThread)
+ {
+ this->m_pController.Assign(pController);
+ this->m_pThread.Assign(pThread);
+ this->m_pMDA.Assign(pMDA);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback2()->MDANotification(m_pController, m_pThread, m_pMDA);
+ }
+ }; // end class MDANotificationEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new MDANotificationEvent(pController, pThread, pMDA));
+ return S_OK;
+} // end of methodICorDebugManagedCallback2::MDANotification
+
+// Implementation of ICorDebugManagedCallback3::CustomNotification
+// Arguments:
+// input:
+// pThread - thread on which the notification occurred
+// pAppDomain - appDomain in which the notification occurred
+// Return value: S_OK
+HRESULT ShimProxyCallback::CustomNotification(ICorDebugThread * pThread, ICorDebugAppDomain * pAppDomain)
+{
+ m_pShim->PreDispatchEvent();
+ class CustomNotificationEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+
+ public:
+ // Ctor
+ CustomNotificationEvent(ICorDebugThread * pThread, ICorDebugAppDomain * pAppDomain) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback3()->CustomNotification(m_pThread, m_pAppDomain);
+ }
+ }; // end class CustomNotificationEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new CustomNotificationEvent(pThread, pAppDomain));
+ return S_OK;
+}
+
+
diff --git a/src/debug/di/shimdatatarget.cpp b/src/debug/di/shimdatatarget.cpp
new file mode 100644
index 0000000000..f3a53b8870
--- /dev/null
+++ b/src/debug/di/shimdatatarget.cpp
@@ -0,0 +1,92 @@
+// 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.
+//*****************************************************************************
+
+//
+// File: ShimDataTarget.cpp
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "safewrap.h"
+
+#include "check.h"
+
+#include <limits.h>
+
+#include "shimpriv.h"
+
+
+// Standard impl of IUnknown::QueryInterface
+HRESULT STDMETHODCALLTYPE ShimDataTarget::QueryInterface(
+ REFIID InterfaceId,
+ PVOID* pInterface
+ )
+{
+ if (InterfaceId == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugDataTarget *>(this));
+ }
+ else if (InterfaceId == IID_ICorDebugDataTarget)
+ {
+ *pInterface = static_cast<ICorDebugDataTarget *>(this);
+ }
+ else if (InterfaceId == IID_ICorDebugMutableDataTarget)
+ {
+ *pInterface = static_cast<ICorDebugMutableDataTarget *>(this);
+ }
+ else if (InterfaceId == IID_ICorDebugDataTarget4)
+ {
+ *pInterface = static_cast<ICorDebugDataTarget4 *>(this);
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+// Standard impl of IUnknown::AddRef
+ULONG STDMETHODCALLTYPE ShimDataTarget::AddRef()
+{
+ LONG ref = InterlockedIncrement(&m_ref);
+ return ref;
+}
+
+// Standard impl of IUnknown::Release
+ULONG STDMETHODCALLTYPE ShimDataTarget::Release()
+{
+ LONG ref = InterlockedDecrement(&m_ref);
+ if (ref == 0)
+ {
+ delete this;
+ }
+ return ref;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Get the OS Process ID that this DataTarget is for.
+//
+// Return Value:
+// The OS PID of the process this data target is representing.
+DWORD ShimDataTarget::GetPid()
+{
+ return m_processId;
+}
+
+//---------------------------------------------------------------------------------------
+// Hook a custom function to handle ICorDebugMutableDataTarget::ContinueStatusChanged
+//
+// Arguments:
+// fpContinueStatusChanged - callback function to invoke.
+// pUserData - user data to pass to callback
+//
+void ShimDataTarget::HookContinueStatusChanged(FPContinueStatusChanged fpContinueStatusChanged, void * pUserData)
+{
+ m_fpContinueStatusChanged = fpContinueStatusChanged;
+ m_pContinueStatusChangedUserData = pUserData;
+}
diff --git a/src/debug/di/shimdatatarget.h b/src/debug/di/shimdatatarget.h
new file mode 100644
index 0000000000..adcbae8056
--- /dev/null
+++ b/src/debug/di/shimdatatarget.h
@@ -0,0 +1,133 @@
+// 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.
+//*****************************************************************************
+// ShimDataTarget.h
+//
+
+//
+// header for liveproc data targets
+//*****************************************************************************
+
+#ifndef SHIMDATATARGET_H_
+#define SHIMDATATARGET_H_
+
+
+// Function to invoke for
+typedef HRESULT (*FPContinueStatusChanged)(void * pUserData, DWORD dwThreadId, CORDB_CONTINUE_STATUS dwContinueStatus);
+
+
+//---------------------------------------------------------------------------------------
+// Data target for a live process. This is used by Shim.
+//
+class ShimDataTarget : public ICorDebugMutableDataTarget, ICorDebugDataTarget4
+{
+public:
+ virtual ~ShimDataTarget() {}
+
+ // Allow hooking an implementation for ContinueStatusChanged.
+ void HookContinueStatusChanged(FPContinueStatusChanged fpContinueStatusChanged, void * pUserData);
+
+ // Release any resources. Also called by destructor.
+ virtual void Dispose() = 0;
+
+ // Set data-target into an error mode. This can be used to mark that the process
+ // is unavailable because it's running
+ void SetError(HRESULT hr);
+
+ // Get the OS Process ID that this DataTarget is for.
+ DWORD GetPid();
+
+ //
+ // IUnknown.
+ //
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(
+ REFIID InterfaceId,
+ PVOID* Interface);
+
+ virtual ULONG STDMETHODCALLTYPE AddRef();
+
+ virtual ULONG STDMETHODCALLTYPE Release();
+
+ //
+ // ICorDebugMutableDataTarget.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE GetPlatform(
+ CorDebugPlatform * pPlatform) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE ReadVirtual(
+ CORDB_ADDRESS address,
+ BYTE * pBuffer,
+ ULONG32 request,
+ ULONG32 * pcbRead) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE WriteVirtual(
+ CORDB_ADDRESS address,
+ const BYTE * pBuffer,
+ ULONG32 request) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE GetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextFlags,
+ ULONG32 contextSize,
+ BYTE * context) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE SetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextSize,
+ const BYTE * context) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE ContinueStatusChanged(
+ DWORD dwThreadId,
+ CORDB_CONTINUE_STATUS dwContinueStatus) = 0;
+
+ // @dbgtodo - add Native Patch Table support
+
+ //
+ // ICorDebugDataTarget4
+ //
+
+ // Unwind to the next stack frame
+ virtual HRESULT STDMETHODCALLTYPE VirtualUnwind(
+ DWORD threadId, ULONG32 contextSize, PBYTE context) = 0;
+
+protected:
+ // Pid of the target process.
+ DWORD m_processId;
+
+ // If this HRESULT != S_OK, then all interface methods will return this.
+ // This provides a way to mark the debugggee as stopped / dead.
+ HRESULT m_hr;
+
+ FPContinueStatusChanged m_fpContinueStatusChanged;
+ void * m_pContinueStatusChangedUserData;
+
+ // Reference count.
+ LONG m_ref;
+};
+
+//---------------------------------------------------------------------------------------
+//
+// Construction method for data-target
+//
+// Arguments:
+// machineInfo - used for Mac debugging; uniquely identifies the debugger proxy on the remote machine
+// processId - (input) live OS process ID to build a data-target for.
+// ppDataTarget - (output) new data-target instance. This gets addreffed.
+//
+// Return Value:
+// S_OK on success.
+//
+// Assumptions:
+// pid must be for local, same architecture, process.
+// Caller must have security permissions for OpenProcess()
+// Caller must release *ppDataTarget.
+//
+
+HRESULT BuildPlatformSpecificDataTarget(MachineInfo machineInfo,
+ DWORD processId,
+ ShimDataTarget ** ppDataTarget);
+
+#endif // SHIMDATATARGET_H_
+
diff --git a/src/debug/di/shimevents.cpp b/src/debug/di/shimevents.cpp
new file mode 100644
index 0000000000..e54b1bd7f2
--- /dev/null
+++ b/src/debug/di/shimevents.cpp
@@ -0,0 +1,292 @@
+// 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.
+//*****************************************************************************
+// File: ShimEvents.cpp
+//
+
+//
+// The V3 ICD debugging APIs have a lower abstraction level than V2.
+// This provides V2 ICD debugging functionality on top of the V3 debugger object.
+//*****************************************************************************
+
+#include "stdafx.h"
+
+#include "safewrap.h"
+#include "check.h"
+
+#include <limits.h>
+#include "shimpriv.h"
+
+//---------------------------------------------------------------------------------------
+// Need virtual dtor since this is a base class.
+// Derived classes will do real work
+//---------------------------------------------------------------------------------------
+ManagedEvent::~ManagedEvent()
+{
+}
+
+#ifdef _DEBUG
+//---------------------------------------------------------------------------------------
+// For debugging, get a pointer value that can identify the type of this event.
+//
+// Returns:
+// persistent pointer value that can be used as cookie to identify this event type.
+//---------------------------------------------------------------------------------------
+void * ManagedEvent::GetDebugCookie()
+{
+ // Return vtable, first void* in the structure.
+ return *(reinterpret_cast<void**> (this));
+}
+#endif
+
+//---------------------------------------------------------------------------------------
+// Ctor for DispatchArgs
+//
+// Arguments:
+// pCallback1 - 1st callback, for debug events in V1.0, V1.1
+// pCallback2 - 2nd callback, for debug events added in V2
+//
+// Notes:
+// We'll have a lot of derived classes of ManagedEvent, and so encapsulating the arguments
+// for the Dispatch() function lets us juggle them around easily without hitting every signature.
+//---------------------------------------------------------------------------------------
+ManagedEvent::DispatchArgs::DispatchArgs(ICorDebugManagedCallback * pCallback1, ICorDebugManagedCallback2 * pCallback2, ICorDebugManagedCallback3 * pCallback3)
+{
+ m_pCallback1 = pCallback1;
+ m_pCallback2 = pCallback2;
+ m_pCallback3 = pCallback3;
+}
+
+
+// trivial accessor to get Callback 1
+ICorDebugManagedCallback * ManagedEvent::DispatchArgs::GetCallback1()
+{
+ return m_pCallback1;
+}
+
+// trivial accessor to get callback 2
+ICorDebugManagedCallback2 * ManagedEvent::DispatchArgs::GetCallback2()
+{
+ return m_pCallback2;
+}
+
+// trivial accessor to get callback 3
+ICorDebugManagedCallback3 * ManagedEvent::DispatchArgs::GetCallback3()
+{
+ return m_pCallback3;
+}
+
+// Returns OS Thread Id that this event occurred on, 0 if no thread affinity.
+DWORD ManagedEvent::GetOSTid()
+{
+ return m_dwThreadId;
+}
+
+//---------------------------------------------------------------------------------------
+// Constructore for events with thread affinity
+//
+// Arguments:
+// pThread - thread that this event is associated with.
+//
+// Notes:
+// Thread affinity is used with code:ManagedEventQueue::HasQueuedCallbacks
+// This includes event callbacks that have a thread parameter
+//---------------------------------------------------------------------------------------
+ManagedEvent::ManagedEvent(ICorDebugThread * pThread)
+{
+ m_dwThreadId = 0;
+ if (pThread != NULL)
+ {
+ pThread->GetID(&m_dwThreadId);
+ }
+
+ m_pNext = NULL;
+}
+
+//---------------------------------------------------------------------------------------
+// Constructor for events with no thread affinity
+//---------------------------------------------------------------------------------------
+ManagedEvent::ManagedEvent()
+{
+ m_dwThreadId = 0;
+ m_pNext = NULL;
+}
+
+
+
+
+
+
+// Ctor
+ManagedEventQueue::ManagedEventQueue()
+{
+ m_pFirstEvent = NULL;
+ m_pLastEvent = NULL;
+ m_pLock = NULL;
+}
+
+//---------------------------------------------------------------------------------------
+// Initialize
+//
+// Arguments:
+// pLock - lock that protects this event queue. This takes a weak ref to the lock,
+// so caller ensures lock stays alive for lifespan of this object
+//
+// Notes:
+// Event queue locks itself using this lock.
+// Only call this once.
+//---------------------------------------------------------------------------------------
+void ManagedEventQueue::Init(RSLock * pLock)
+{
+ _ASSERTE(m_pLock == NULL);
+ m_pLock = pLock;
+}
+
+//---------------------------------------------------------------------------------------
+// Remove event from the top.
+//
+// Returns:
+// Event that was just dequeued.
+//
+// Notes:
+// Caller then takes ownership of Event and will call Delete on it.
+// If IsEmpty() function returns NULL.
+//
+// It is an error to call Dequeue when the only elements in the queue are suspended.
+// Suspending the queue implies there are going to be new events added which should come before
+// the elements that are suspended. Trying to deqeue when there are only suspended elements
+// left is error-prone - if it were allowed, the order may be non-deterministic.
+// In practice we could probably ban calling Dequeue at all when any elements are suspended,
+// but this seems overly restrictive - there is nothing wrong with allowing these "new"
+// events to be dequeued since we know they come first (you can't nest suspensions).
+//---------------------------------------------------------------------------------------
+ManagedEvent * ManagedEventQueue::Dequeue()
+{
+ RSLockHolder lockHolder(m_pLock);
+ if (m_pFirstEvent == NULL)
+ {
+ return NULL;
+ }
+
+ ManagedEvent * pEvent = m_pFirstEvent;
+ m_pFirstEvent = m_pFirstEvent->m_pNext;
+ if (m_pFirstEvent == NULL)
+ {
+ m_pLastEvent = NULL;
+ }
+
+ pEvent->m_pNext = NULL;
+ return pEvent;
+}
+
+//---------------------------------------------------------------------------------------
+// Append the event to the end of the queue.
+// Queue owns the event and will delete it (unless it's dequeued first).
+//
+// Note that this can be called when a suspended queue is active. Events are pushed onto
+// the currently active queue (ahead of the suspended queue).
+//
+// Arguments:
+// pEvent - event to queue.
+//
+//---------------------------------------------------------------------------------------
+void ManagedEventQueue::QueueEvent(ManagedEvent * pEvent)
+{
+ RSLockHolder lockHolder(m_pLock);
+ _ASSERTE(pEvent != NULL);
+ _ASSERTE(pEvent->m_pNext == NULL);
+
+ if (m_pLastEvent == NULL)
+ {
+ _ASSERTE(m_pFirstEvent == NULL);
+ m_pFirstEvent = m_pLastEvent = pEvent;
+ }
+ else
+ {
+ m_pLastEvent->m_pNext = pEvent;
+ m_pLastEvent = pEvent;
+ }
+}
+
+
+//---------------------------------------------------------------------------------------
+// Returns true iff the event queue is empty (including any suspended queue elements)
+//---------------------------------------------------------------------------------------
+bool ManagedEventQueue::IsEmpty()
+{
+ RSLockHolder lockHolder(m_pLock);
+ if (m_pFirstEvent != NULL)
+ {
+ _ASSERTE(m_pLastEvent != NULL);
+ return false;
+ }
+
+ _ASSERTE(m_pLastEvent == NULL);
+ return true;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Delete all events and empty the queue (including any suspended queue elements)
+//
+// Notes:
+// This is like calling { while(!IsEmpty()) delete Dequeue(); }
+//---------------------------------------------------------------------------------------
+void ManagedEventQueue::DeleteAll()
+{
+ RSLockHolder lockHolder(m_pLock);
+
+ while (m_pFirstEvent != NULL)
+ {
+ // verify that the last event in the queue is actually the one stored as the last event
+ _ASSERTE( m_pFirstEvent->m_pNext != NULL || m_pFirstEvent == m_pLastEvent );
+
+ ManagedEvent * pNext = m_pFirstEvent->m_pNext;
+ delete m_pFirstEvent;
+ m_pFirstEvent = pNext;
+ }
+ m_pLastEvent = NULL;
+
+ _ASSERTE(IsEmpty());
+};
+
+//---------------------------------------------------------------------------------------
+// Worker to implement ICorDebugProcess::HasQueuedCallbacks for shim
+//---------------------------------------------------------------------------------------
+BOOL ManagedEventQueue::HasQueuedCallbacks(ICorDebugThread * pThread)
+{
+ // This is from the public paths of ICorDebugProcess::HasQueuedCallbacks.
+ // In V2, this would fail in cases, notably including if the process is not synchronized.
+ // In arrowhead, it always succeeds.
+
+ // No thread - look process wide.
+ if (pThread == NULL)
+ {
+ return !IsEmpty();
+ }
+
+ // If we have a thread, look for events with thread affinity.
+ DWORD dwThreadID = 0;
+ HRESULT hr = pThread->GetID(&dwThreadID);
+ (void)hr; //prevent "unused variable" error from GCC
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+
+ // Don't take lock until after we don't call any ICorDebug APIs.
+ RSLockHolder lockHolder(m_pLock);
+
+ ManagedEvent * pCurrent = m_pFirstEvent;
+ while (pCurrent != NULL)
+ {
+ if (pCurrent->GetOSTid() == dwThreadID)
+ {
+ return true;
+ }
+ pCurrent = pCurrent->m_pNext;
+ }
+ return false;
+}
+
+
+
+
diff --git a/src/debug/di/shimlocaldatatarget.cpp b/src/debug/di/shimlocaldatatarget.cpp
new file mode 100644
index 0000000000..c4a5263810
--- /dev/null
+++ b/src/debug/di/shimlocaldatatarget.cpp
@@ -0,0 +1,471 @@
+// 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.
+//*****************************************************************************
+
+//
+// File: ShimLocalDataTarget.cpp
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "safewrap.h"
+
+#include "check.h"
+
+#include <limits.h>
+
+#include "shimpriv.h"
+#include "shimdatatarget.h"
+
+
+// The Shim's Live data-target is allowed to call OS APIs directly.
+// see code:RSDebuggingInfo#UseDataTarget.
+#undef ReadProcessMemory
+#undef WriteProcessMemory
+
+
+class ShimLocalDataTarget : public ShimDataTarget
+{
+public:
+ ShimLocalDataTarget(DWORD processId, HANDLE hProcess);
+
+ ~ShimLocalDataTarget();
+
+ virtual void Dispose();
+
+ //
+ // ICorDebugMutableDataTarget.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE GetPlatform(
+ CorDebugPlatform *pPlatform);
+
+ virtual HRESULT STDMETHODCALLTYPE ReadVirtual(
+ CORDB_ADDRESS address,
+ BYTE * pBuffer,
+ ULONG32 request,
+ ULONG32 *pcbRead);
+
+ virtual HRESULT STDMETHODCALLTYPE WriteVirtual(
+ CORDB_ADDRESS address,
+ const BYTE * pBuffer,
+ ULONG32 request);
+
+ virtual HRESULT STDMETHODCALLTYPE GetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextFlags,
+ ULONG32 contextSize,
+ BYTE * context);
+
+ virtual HRESULT STDMETHODCALLTYPE SetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextSize,
+ const BYTE * context);
+
+ virtual HRESULT STDMETHODCALLTYPE ContinueStatusChanged(
+ DWORD dwThreadId,
+ CORDB_CONTINUE_STATUS dwContinueStatus);
+
+ virtual HRESULT STDMETHODCALLTYPE VirtualUnwind(
+ DWORD threadId, ULONG32 contextSize, PBYTE context);
+
+private:
+ // Handle to the process. We own this.
+ HANDLE m_hProcess;
+};
+
+
+// Determines whether the target and host are running on compatible platforms.
+// Arguments:
+// input: hTargetProcess - handle for the target process
+// Return Value: TRUE iff both target and host are both Wow64 or neither is.
+// Note: throws
+BOOL CompatibleHostAndTargetPlatforms(HANDLE hTargetProcess)
+{
+#if defined(FEATURE_PAL)
+ return TRUE;
+#else
+ // get the platform for the host process
+ BOOL fHostProcessIsWow64 = FALSE;
+ BOOL fSuccess = FALSE;
+ HANDLE hHostProcess = GetCurrentProcess();
+
+ fSuccess = IsWow64Process(hHostProcess, &fHostProcessIsWow64);
+ CloseHandle(hHostProcess);
+ hHostProcess = NULL;
+
+ if (!fSuccess)
+ {
+ ThrowHR(HRESULT_FROM_GetLastError());
+ }
+
+ // get the platform for the target process
+ if (hTargetProcess == NULL)
+ {
+ ThrowHR(HRESULT_FROM_GetLastError());
+ }
+
+ BOOL fTargetProcessIsWow64 = FALSE;
+ fSuccess = IsWow64Process(hTargetProcess, &fTargetProcessIsWow64);
+
+ if (!fSuccess)
+ {
+ ThrowHR(HRESULT_FROM_GetLastError());
+ }
+
+ // We don't want to expose the IPC block if one process is x86 and
+ // the other is ia64 or amd64
+ if (fTargetProcessIsWow64 != fHostProcessIsWow64)
+ {
+ return FALSE;
+ }
+ else
+ {
+ return TRUE;
+ }
+#endif
+} // CompatibleHostAndTargetPlatforms
+
+// Helper macro to check for failure conditions at the start of data-target methods.
+#define ReturnFailureIfStateNotOk() \
+ if (m_hr != S_OK) \
+ { \
+ return m_hr; \
+ }
+
+//---------------------------------------------------------------------------------------
+//
+// ctor for ShimLocalDataTarget.
+//
+// Arguments:
+// processId - pid of live process.
+// hProcess - handle to kernel process object.
+//
+// Assumptions:
+// Shim takes ownership of handle hProcess.
+//
+
+ShimLocalDataTarget::ShimLocalDataTarget(DWORD processId, HANDLE hProcess)
+{
+ m_ref = 0;
+
+ m_processId = processId;
+ m_hProcess = hProcess;
+
+ m_hr = S_OK;
+
+ m_fpContinueStatusChanged = NULL;
+ m_pContinueStatusChangedUserData = NULL;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// dctor for ShimLocalDataTarget.
+//
+ShimLocalDataTarget::~ShimLocalDataTarget()
+{
+ Dispose();
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Dispose all resources and neuter the object.
+//
+//
+//
+// Notes:
+// Release all resources (such as the handle to the process we got in the ctor).
+// May be called multiple times.
+// All other non-trivial APIs (eg, not IUnknown) will fail after this.
+//
+
+void ShimLocalDataTarget::Dispose()
+{
+ if (m_hProcess != NULL)
+ {
+ CloseHandle(m_hProcess);
+ m_hProcess = NULL;
+ }
+ m_hr = CORDBG_E_OBJECT_NEUTERED;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Construction method for data-target
+//
+// Arguments:
+// processId - (input) live OS process ID to build a data-target for.
+// ppDataTarget - (output) new data-target instance. This gets addreffed.
+//
+// Return Value:
+// S_OK on success.
+//
+// Assumptions:
+// pid must be for local, same architecture, process.
+// Caller must have security permissions for OpenProcess()
+// Caller must release *ppDataTarget.
+//
+
+HRESULT BuildPlatformSpecificDataTarget(MachineInfo machineInfo,
+ DWORD processId,
+ ShimDataTarget ** ppDataTarget)
+{
+ HRESULT hr = S_OK;
+ HANDLE hProcess = NULL;
+ ShimLocalDataTarget * pLocalDataTarget = NULL;
+
+ *ppDataTarget = NULL;
+
+ hProcess = OpenProcess(
+ PROCESS_DUP_HANDLE |
+ PROCESS_QUERY_INFORMATION |
+ PROCESS_TERMINATE |
+ PROCESS_VM_OPERATION |
+ PROCESS_VM_READ |
+ PROCESS_VM_WRITE |
+ SYNCHRONIZE,
+ FALSE,
+ processId);
+
+ if (hProcess == NULL)
+ {
+ hr = HRESULT_FROM_GetLastError();
+ goto Label_Exit;
+ }
+
+ EX_TRY
+ {
+ if (!CompatibleHostAndTargetPlatforms(hProcess))
+ {
+ hr = CORDBG_E_UNCOMPATIBLE_PLATFORMS;
+ goto Label_Exit;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ goto Label_Exit;
+ }
+ pLocalDataTarget = new (nothrow) ShimLocalDataTarget(processId, hProcess);
+ if (pLocalDataTarget == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ goto Label_Exit;
+ }
+
+ // ShimLocalDataTarget now has ownership of Handle.
+ hProcess = NULL;
+
+ _ASSERTE(SUCCEEDED(hr));
+ *ppDataTarget = pLocalDataTarget;
+ pLocalDataTarget->AddRef(); // must addref out-parameters
+
+Label_Exit:
+ if (FAILED(hr))
+ {
+ if (hProcess != NULL)
+ {
+ CloseHandle(hProcess);
+ }
+ delete pLocalDataTarget;
+ }
+
+ return hr;
+}
+
+// impl of interface method ICorDebugDataTarget::GetPlatform
+HRESULT STDMETHODCALLTYPE
+ShimLocalDataTarget::GetPlatform(
+ CorDebugPlatform *pPlatform)
+{
+#ifdef FEATURE_PAL
+#error ShimLocalDataTarget is not implemented on PAL systems yet
+#endif
+ // Assume that we're running on Windows for now.
+#if defined(DBG_TARGET_X86)
+ *pPlatform = CORDB_PLATFORM_WINDOWS_X86;
+#elif defined(DBG_TARGET_AMD64)
+ *pPlatform = CORDB_PLATFORM_WINDOWS_AMD64;
+#elif defined(DBG_TARGET_ARM)
+ *pPlatform = CORDB_PLATFORM_WINDOWS_ARM;
+#elif defined(DBG_TARGET_ARM64)
+ *pPlatform = CORDB_PLATFORM_WINDOWS_ARM64;
+#else
+#error Unknown Processor.
+#endif
+ return S_OK;
+}
+
+// impl of interface method ICorDebugDataTarget::ReadVirtual
+HRESULT STDMETHODCALLTYPE
+ShimLocalDataTarget::ReadVirtual(
+ CORDB_ADDRESS address,
+ PBYTE pBuffer,
+ ULONG32 cbRequestSize,
+ ULONG32 *pcbRead)
+{
+ ReturnFailureIfStateNotOk();
+
+
+ // ReadProcessMemory will fail if any part of the
+ // region to read does not have read access. This
+ // routine attempts to read the largest valid prefix
+ // so it has to break up reads on page boundaries.
+
+ HRESULT hrStatus = S_OK;
+ ULONG32 totalDone = 0;
+ SIZE_T read;
+ ULONG32 readSize;
+
+ while (cbRequestSize > 0)
+ {
+ // Calculate bytes to read and don't let read cross
+ // a page boundary.
+ readSize = OS_PAGE_SIZE - (ULONG32)(address & (OS_PAGE_SIZE - 1));
+ readSize = min(cbRequestSize, readSize);
+
+ if (!ReadProcessMemory(m_hProcess, (PVOID)(ULONG_PTR)address,
+ pBuffer, readSize, &read))
+ {
+ if (totalDone == 0)
+ {
+ // If we haven't read anything indicate failure.
+ hrStatus = HRESULT_FROM_GetLastError();
+ }
+ break;
+ }
+
+ totalDone += (ULONG32)read;
+ address += read;
+ pBuffer += read;
+ cbRequestSize -= (ULONG32)read;
+ }
+
+ *pcbRead = totalDone;
+ return hrStatus;
+}
+
+// impl of interface method ICorDebugMutableDataTarget::WriteVirtual
+HRESULT STDMETHODCALLTYPE
+ShimLocalDataTarget::WriteVirtual(
+ CORDB_ADDRESS pAddress,
+ const BYTE * pBuffer,
+ ULONG32 cbRequestSize)
+{
+ ReturnFailureIfStateNotOk();
+
+ SIZE_T cbWritten;
+ BOOL fWriteOk = WriteProcessMemory(m_hProcess, CORDB_ADDRESS_TO_PTR(pAddress), pBuffer, cbRequestSize, &cbWritten);
+ if (fWriteOk)
+ {
+ _ASSERTE(cbWritten == cbRequestSize); // MSDN docs say this must always be true
+ return S_OK;
+ }
+ else
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+}
+
+HRESULT STDMETHODCALLTYPE
+ShimLocalDataTarget::GetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextFlags,
+ ULONG32 contextSize,
+ BYTE * pContext)
+{
+ ReturnFailureIfStateNotOk();
+ // @dbgtodo - Ideally we should cache the thread handles so that we don't need to
+ // open and close the thread handles every time.
+
+ HRESULT hr = E_FAIL;
+
+ if (!CheckContextSizeForBuffer(contextSize, pContext))
+ {
+ return E_INVALIDARG;
+ }
+
+ HandleHolder hThread = OpenThread(
+ THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_QUERY_INFORMATION ,
+ FALSE, // thread handle is not inheritable.
+ dwThreadID);
+
+ if (hThread != NULL)
+ {
+ DT_CONTEXT * pCtx = reinterpret_cast<DT_CONTEXT *>(pContext);
+ pCtx->ContextFlags = contextFlags;
+
+ if (DbiGetThreadContext(hThread, pCtx))
+ {
+ hr = S_OK;
+ }
+ }
+
+ // hThread destructed automatically
+ return hr;
+}
+
+// impl of interface method ICorDebugMutableDataTarget::SetThreadContext
+HRESULT STDMETHODCALLTYPE
+ShimLocalDataTarget::SetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextSize,
+ const BYTE * pContext)
+{
+ ReturnFailureIfStateNotOk();
+ HRESULT hr = E_FAIL;
+
+ if (!CheckContextSizeForBuffer(contextSize, pContext))
+ {
+ return E_INVALIDARG;
+ }
+
+
+ HandleHolder hThread = OpenThread(
+ THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_QUERY_INFORMATION,
+ FALSE, // thread handle is not inheritable.
+ dwThreadID);
+
+ if (hThread != NULL)
+ {
+ if (DbiSetThreadContext(hThread, reinterpret_cast<const DT_CONTEXT *>(pContext)))
+ {
+ hr = S_OK;
+ }
+ }
+
+ // hThread destructed automatically
+ return hr;
+}
+
+// Public implementation of ICorDebugMutableDataTarget::ContinueStatusChanged
+HRESULT STDMETHODCALLTYPE
+ShimLocalDataTarget::ContinueStatusChanged(
+ DWORD dwThreadId,
+ CORDB_CONTINUE_STATUS dwContinueStatus)
+{
+ ReturnFailureIfStateNotOk();
+ if (m_fpContinueStatusChanged != NULL)
+ {
+ return m_fpContinueStatusChanged(m_pContinueStatusChangedUserData, dwThreadId, dwContinueStatus);
+ }
+ return E_NOTIMPL;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Unwind the stack to the next frame.
+//
+// Return Value:
+// context filled in with the next frame
+//
+HRESULT STDMETHODCALLTYPE
+ShimLocalDataTarget::VirtualUnwind(DWORD threadId, ULONG32 contextSize, PBYTE context)
+{
+#ifndef FEATURE_PAL
+ _ASSERTE(!"ShimLocalDataTarget::VirtualUnwind NOT IMPLEMENTED");
+#endif
+ return E_NOTIMPL;
+}
+
diff --git a/src/debug/di/shimpriv.h b/src/debug/di/shimpriv.h
new file mode 100644
index 0000000000..9c83301009
--- /dev/null
+++ b/src/debug/di/shimpriv.h
@@ -0,0 +1,1056 @@
+// 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.
+//*****************************************************************************
+// shimprivate.h
+//
+
+//
+// private header for RS shim which bridges from V2 to V3.
+//*****************************************************************************
+
+#ifndef SHIMPRIV_H
+#define SHIMPRIV_H
+
+#include "helpers.h"
+
+#include "shimdatatarget.h"
+
+#include <shash.h>
+
+// Forward declarations
+class CordbWin32EventThread;
+class Cordb;
+
+class ShimStackWalk;
+class ShimChain;
+class ShimChainEnum;
+class ShimFrameEnum;
+
+// This struct specifies that it's a hash table of ShimStackWalk * using ICorDebugThread as the key.
+struct ShimStackWalkHashTableTraits : public PtrSHashTraits<ShimStackWalk, ICorDebugThread *> {};
+typedef SHash<ShimStackWalkHashTableTraits> ShimStackWalkHashTable;
+
+
+//---------------------------------------------------------------------------------------
+//
+// Simple struct for storing a void *. This is to be used with a SHash hash table.
+//
+
+struct DuplicateCreationEventEntry
+{
+public:
+ DuplicateCreationEventEntry(void * pKey) : m_pKey(pKey) {};
+
+ // These functions must be defined for DuplicateCreationEventsHashTableTraits.
+ void * GetKey() {return m_pKey;};
+ static UINT32 Hash(void * pKey) {return (UINT32)(size_t)pKey;};
+
+private:
+ void * m_pKey;
+};
+
+// This struct specifies that it's a hash table of DuplicateCreationEventEntry * using a void * as the key.
+// The void * is expected to be an ICDProcess/ICDAppDomain/ICDThread/ICDAssembly/ICDThread interface pointer.
+struct DuplicateCreationEventsHashTableTraits : public PtrSHashTraits<DuplicateCreationEventEntry, void *> {};
+typedef SHash<DuplicateCreationEventsHashTableTraits> DuplicateCreationEventsHashTable;
+
+//
+// Callback that shim provides, which then queues up the events.
+//
+class ShimProxyCallback :
+ public ICorDebugManagedCallback,
+ public ICorDebugManagedCallback2,
+ public ICorDebugManagedCallback3
+{
+ ShimProcess * m_pShim; // weak reference
+ LONG m_cRef;
+
+public:
+ ShimProxyCallback(ShimProcess * pShim);
+ virtual ~ShimProxyCallback() {}
+
+ // Implement IUnknown
+ ULONG STDMETHODCALLTYPE AddRef();
+ ULONG STDMETHODCALLTYPE Release();
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //
+ // Implementation of ICorDebugManagedCallback
+ //
+
+ COM_METHOD Breakpoint( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugBreakpoint *pBreakpoint);
+
+ COM_METHOD StepComplete( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugStepper *pStepper,
+ CorDebugStepReason reason);
+
+ COM_METHOD Break( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *thread);
+
+ COM_METHOD Exception( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ BOOL unhandled);
+
+ COM_METHOD EvalComplete( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugEval *pEval);
+
+ COM_METHOD EvalException( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugEval *pEval);
+
+ COM_METHOD CreateProcess( ICorDebugProcess *pProcess);
+ void QueueCreateProcess( ICorDebugProcess *pProcess);
+
+ COM_METHOD ExitProcess( ICorDebugProcess *pProcess);
+
+ COM_METHOD CreateThread( ICorDebugAppDomain *pAppDomain, ICorDebugThread *thread);
+
+
+ COM_METHOD ExitThread( ICorDebugAppDomain *pAppDomain, ICorDebugThread *thread);
+
+ COM_METHOD LoadModule( ICorDebugAppDomain *pAppDomain, ICorDebugModule *pModule);
+
+ void FakeLoadModule(ICorDebugAppDomain *pAppDomain, ICorDebugModule *pModule);
+
+ COM_METHOD UnloadModule( ICorDebugAppDomain *pAppDomain, ICorDebugModule *pModule);
+
+ COM_METHOD LoadClass( ICorDebugAppDomain *pAppDomain, ICorDebugClass *c);
+
+ COM_METHOD UnloadClass( ICorDebugAppDomain *pAppDomain, ICorDebugClass *c);
+
+ COM_METHOD DebuggerError( ICorDebugProcess *pProcess, HRESULT errorHR, DWORD errorCode);
+
+ COM_METHOD LogMessage( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ LONG lLevel,
+ __in LPWSTR pLogSwitchName,
+ __in LPWSTR pMessage);
+
+ COM_METHOD LogSwitch( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ LONG lLevel,
+ ULONG ulReason,
+ __in LPWSTR pLogSwitchName,
+ __in LPWSTR pParentName);
+
+ COM_METHOD CreateAppDomain(ICorDebugProcess *pProcess,
+ ICorDebugAppDomain *pAppDomain);
+
+ COM_METHOD ExitAppDomain(ICorDebugProcess *pProcess,
+ ICorDebugAppDomain *pAppDomain);
+
+ COM_METHOD LoadAssembly(ICorDebugAppDomain *pAppDomain,
+ ICorDebugAssembly *pAssembly);
+
+ COM_METHOD UnloadAssembly(ICorDebugAppDomain *pAppDomain,
+ ICorDebugAssembly *pAssembly);
+
+ COM_METHOD ControlCTrap(ICorDebugProcess *pProcess);
+
+ COM_METHOD NameChange(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread);
+
+
+ COM_METHOD UpdateModuleSymbols( ICorDebugAppDomain *pAppDomain,
+ ICorDebugModule *pModule,
+ IStream *pSymbolStream);
+
+ COM_METHOD EditAndContinueRemap( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugFunction *pFunction,
+ BOOL fAccurate);
+
+ COM_METHOD BreakpointSetError( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugBreakpoint *pBreakpoint,
+ DWORD dwError);
+
+ ///
+ /// Implementation of ICorDebugManagedCallback2
+ ///
+ COM_METHOD FunctionRemapOpportunity( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugFunction *pOldFunction,
+ ICorDebugFunction *pNewFunction,
+ ULONG32 oldILOffset);
+
+ COM_METHOD CreateConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId, __in LPWSTR pConnName);
+
+ COM_METHOD ChangeConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId );
+
+
+ COM_METHOD DestroyConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId);
+
+ COM_METHOD Exception(ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugFrame *pFrame,
+ ULONG32 nOffset,
+ CorDebugExceptionCallbackType dwEventType,
+ DWORD dwFlags );
+
+ COM_METHOD ExceptionUnwind(ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ CorDebugExceptionUnwindCallbackType dwEventType,
+ DWORD dwFlags);
+
+ COM_METHOD FunctionRemapComplete( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugFunction *pFunction);
+
+ COM_METHOD MDANotification(ICorDebugController * pController, ICorDebugThread *pThread, ICorDebugMDA * pMDA);
+
+ ///
+ /// Implementation of ICorDebugManagedCallback3
+ ///
+
+ // Implementation of ICorDebugManagedCallback3::CustomNotification
+ COM_METHOD CustomNotification(ICorDebugThread * pThread, ICorDebugAppDomain * pAppDomain);
+
+};
+
+
+//
+// Base class for event queue. These are nested into a singly linked list.
+// Shim maintains event queue
+//
+class ManagedEvent
+{
+public:
+ // Need virtual dtor since this is a base class.
+ virtual ~ManagedEvent();
+
+#ifdef _DEBUG
+ // For debugging, get a pointer value that can identify the type of this event.
+ void * GetDebugCookie();
+#endif
+
+ // We'll have a lot of derived classes of ManagedEvent, and so encapsulating the arguments
+ // for the Dispatch() function lets us juggle them around easily without hitting every signature.
+ class DispatchArgs
+ {
+ public:
+ DispatchArgs(ICorDebugManagedCallback * pCallback1, ICorDebugManagedCallback2 * pCallback2, ICorDebugManagedCallback3 * pCallback3);
+
+ ICorDebugManagedCallback * GetCallback1();
+ ICorDebugManagedCallback2 * GetCallback2();
+ ICorDebugManagedCallback3 * GetCallback3();
+
+
+ protected:
+ ICorDebugManagedCallback * m_pCallback1;
+ ICorDebugManagedCallback2 * m_pCallback2;
+ ICorDebugManagedCallback3 * m_pCallback3;
+ };
+
+ // Returns: value of callback from end-user
+ virtual HRESULT Dispatch(DispatchArgs args) = 0;
+
+
+ // Returns 0 if none.
+ DWORD GetOSTid();
+
+protected:
+ // Ctor for events with thread-affinity
+ ManagedEvent(ICorDebugThread * pThread);
+
+ // Ctor for events without thread affinity.
+ ManagedEvent();
+
+ friend class ManagedEventQueue;
+ ManagedEvent * m_pNext;
+
+ DWORD m_dwThreadId;
+};
+
+//
+// Queue of managed events.
+// Shim can use this to collect managed debug events, queue them, and then drain the event
+// queue when a sync-complete occurs.
+// Event queue gets initialized with a lock and will lock internally.
+class ManagedEventQueue
+{
+public:
+ ManagedEventQueue();
+
+
+ void Init(RSLock * pLock);
+
+ // Remove event from the top. Caller then takes ownership of Event and will call Delete on it.
+ // Caller checks IsEmpty() first.
+ ManagedEvent * Dequeue();
+
+ // Queue owns the event and will delete it (unless it's dequeued first).
+ void QueueEvent(ManagedEvent * pEvent);
+
+ // Test if event queue is empty
+ bool IsEmpty();
+
+ // Empty event queue and delete all objects
+ void DeleteAll();
+
+ // Nothrows
+ BOOL HasQueuedCallbacks(ICorDebugThread * pThread);
+
+ // Save the current queue and start with a new empty queue
+ void SuspendQueue();
+
+ // Restore the saved queue onto the end of the current queue
+ void RestoreSuspendedQueue();
+
+protected:
+ // The lock to be used for synchronizing all access to the queue
+ RSLock * m_pLock;
+
+ // If empty, First + Last are both NULL.
+ // Else first points to the head of the queue; and Last points to the end of the queue.
+ ManagedEvent * m_pFirstEvent;
+ ManagedEvent * m_pLastEvent;
+
+};
+
+
+//---------------------------------------------------------------------------------------
+//
+// Shim's layer on top of a process.
+//
+// Notes:
+// This contains a V3 ICorDebugProcess, and provides V2 ICDProcess functionality.
+//
+class ShimProcess
+{
+ // Delete via Ref count semantics.
+ ~ShimProcess();
+public:
+ // Initialize ref count is 0.
+ ShimProcess();
+
+ // Lifetime semantics handled by reference counting.
+ void AddRef();
+ void Release();
+
+ // Release all resources. Can be called multiple times.
+ void Dispose();
+
+ // Initialization phases.
+ // 1. allocate new ShimProcess(). This lets us spin up a Win32 EventThread, which can then
+ // be used to
+ // 2. Call ShimProcess::CreateProcess/DebugActiveProcess. This will call CreateAndStartWin32ET to
+ // craete the w32et.
+ // 3. Create OS-debugging pipeline. This establishes the physical OS process and gets us a pid/handle
+ // 4. pShim->InitializeDataTarget - this creates a reader/writer abstraction around the OS process.
+ // 5. pShim->SetProcess() - this connects the Shim to the ICDProcess object.
+ HRESULT InitializeDataTarget(DWORD processId);
+ void SetProcess(ICorDebugProcess * pProcess);
+
+ //-----------------------------------------------------------
+ // Creation
+ //-----------------------------------------------------------
+
+ static HRESULT CreateProcess(
+ Cordb * pCordb,
+ ICorDebugRemoteTarget * pRemoteTarget,
+ LPCWSTR programName,
+ __in_z LPWSTR programArgs,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ PVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation,
+ CorDebugCreateProcessFlags corDebugFlags
+ );
+
+ static HRESULT DebugActiveProcess(
+ Cordb * pCordb,
+ ICorDebugRemoteTarget * pRemoteTarget,
+ DWORD pid,
+ BOOL win32Attach
+
+ );
+
+ // Locates the DAC module adjacent to DBI
+ static HMODULE GetDacModule();
+
+ //
+ // Functions used by CordbProcess
+ //
+
+ // Determine if the calling thread is the win32 event thread.
+ bool IsWin32EventThread();
+
+
+ // Expose the W32ET thread to the CordbProcess so that it can emulate V2 behavior
+ CordbWin32EventThread * GetWin32EventThread();
+
+ // Accessor wrapper to mark whether we're interop-debugging.
+ void SetIsInteropDebugging(bool fIsInteropDebugging);
+
+ // Handle a debug event.
+ HRESULT HandleWin32DebugEvent(const DEBUG_EVENT * pEvent);
+
+ ManagedEventQueue * GetManagedEventQueue();
+
+ ManagedEvent * DequeueManagedEvent();
+
+ ShimProxyCallback * GetShimCallback();
+
+ // Begin Queing the fake attach events.
+ void BeginQueueFakeAttachEvents();
+
+ // Queue fake attach events if needed
+ void QueueFakeAttachEventsIfNeeded(bool fRealCreateProcessEvent);
+
+ // Actually do the work to queue the fake attach events.
+ void QueueFakeAttachEvents();
+
+ // Helper to queue fake assembly and mdule events
+ void QueueFakeAssemblyAndModuleEvent(ICorDebugAssembly * pAssembly);
+
+ // Queue fake thread-create events on attach. Order via native threads.
+ HRESULT QueueFakeThreadAttachEventsNativeOrder();
+
+ // Queue fake thread-create events on attach. No ordering.
+ HRESULT QueueFakeThreadAttachEventsNoOrder();
+
+ bool IsThreadSuspendedOrHijacked(ICorDebugThread * pThread);
+
+ // Expose m_attached to CordbProcess.
+ bool GetAttached();
+
+ // We need to know whether we are in the CreateProcess callback to be able to
+ // return the v2.0 hresults from code:CordbProcess::SetDesiredNGENCompilerFlags
+ // when we are using the shim.
+ //
+ // Expose m_fInCreateProcess
+ bool GetInCreateProcess();
+ void SetInCreateProcess(bool value);
+
+ // We need to know whether we are in the FakeLoadModule callback to be able to
+ // return the v2.0 hresults from code:CordbModule::SetJITCompilerFlags when
+ // we are using the shim.
+ //
+ // Expose m_fInLoadModule
+ bool GetInLoadModule();
+ void SetInLoadModule(bool value);
+
+ // When we get a continue, we need to clear the flags indicating we're still in a callback
+ void NotifyOnContinue ();
+
+ // The RS calls this function when the stack is about to be changed in any way, e.g. continue, SetIP,
+ // etc.
+ void NotifyOnStackInvalidate();
+
+ // Helpers to filter HRs to emulate V2 error codes.
+ HRESULT FilterSetNgenHresult(HRESULT hr);
+ HRESULT FilterSetJitFlagsHresult(HRESULT hr);
+
+ //.............................................................
+
+
+ // Lookup or create a ShimStackWalk for the specified thread. ShimStackWalk and ICorDebugThread has
+ // a 1:1 relationship.
+ ShimStackWalk * LookupOrCreateShimStackWalk(ICorDebugThread * pThread);
+
+ // Clear all ShimStackWalks and flush all the caches.
+ void ClearAllShimStackWalk();
+
+ // Get the corresponding ICDProcess object.
+ ICorDebugProcess * GetProcess();
+
+ // Get the data target to access the debuggee.
+ ICorDebugMutableDataTarget * GetDataTarget();
+
+ // Get the native event pipeline
+ INativeEventPipeline * GetNativePipeline();
+
+ // Are we interop-debugging?
+ bool IsInteropDebugging();
+
+
+ // Finish all the necessary initialization work and queue up any necessary fake attach events before
+ // dispatching an event.
+ void PreDispatchEvent(bool fRealCreateProcessEvent = false);
+
+ // Look for a CLR in the process and if found, return it's instance ID
+ HRESULT FindLoadedCLR(CORDB_ADDRESS * pClrInstanceId);
+
+ // Retrieve the IP address and the port number of the debugger proxy.
+ MachineInfo GetMachineInfo();
+
+ // Add an entry in the duplicate creation event hash table for the specified key.
+ void AddDuplicateCreationEvent(void * pKey);
+
+ // Check if a duplicate creation event entry exists for the specified key. If so, remove it.
+ bool RemoveDuplicateCreationEventIfPresent(void * pKey);
+
+ void SetMarkAttachPendingEvent();
+
+ void SetTerminatingEvent();
+
+ RSLock * GetShimLock();
+
+protected:
+
+ // Reference count.
+ LONG m_ref;
+
+ //
+ // Helper functions
+ //
+ HRESULT CreateAndStartWin32ET(Cordb * pCordb);
+
+ //
+ // Synchronization events to ensure that AttachPending bit is marked before DebugActiveProcess
+ // returns or debugger is detaching
+ //
+ HANDLE m_markAttachPendingEvent;
+ HANDLE m_terminatingEvent;
+
+ // Finds the base address of [core]clr.dll
+ CORDB_ADDRESS GetCLRInstanceBaseAddress();
+
+ //
+ // Event Queues
+ //
+
+ // Shim maintains event queue to emulate V2 semantics.
+ // In V2, IcorDebug internally queued debug events and dispatched them
+ // once the debuggee was synchronized. In V3, ICorDebug dispatches events immediately.
+ // The event queue is moved into the shim to build V2 semantics of V3 behavior.
+ ManagedEventQueue m_eventQueue;
+
+ // Lock to protect Shim data structures. This is currently a small lock that
+ // protects leaf-level structures, but it may grow to protect larger things.
+ RSLock m_ShimLock;
+
+ // Serializes ShimProcess:Dispose() with other ShimProcess functions. For now, this
+ // cannot be the same as m_ShimLock. See LL_SHIM_PROCESS_DISPOSE_LOCK for more
+ // information
+ RSLock m_ShimProcessDisposeLock;
+
+ // Sticky bit to do lazy-initialization on the first managed event.
+ bool m_fFirstManagedEvent;
+
+ RSExtSmartPtr<ShimProxyCallback> m_pShimCallback;
+
+
+ // This is for emulating V2 Attach. Initialized to false, and then set to true if we ened to send fake attach events.
+ // Reset to false once the events are sent. See code:ShimProcess::QueueFakeAttachEventsIfNeeded
+ bool m_fNeedFakeAttachEvents;
+
+ // True if the process was created from an attach (DebugActiveProcess); False if it was launched (CreateProcess)
+ // This is used to send an Attach IPC event, and also used to provide more specific error codes.
+ bool m_attached;
+
+ // True iff we are in the shim's CreateProcess callback. This is used to determine which hresult to
+ // return from code:CordbProcess::SetDesiredNGENCompilerFlags so we correctly emulate the behavior of v2.0.
+ // This is set at the beginning of the callback and cleared in code:CordbProcess::ContinueInternal.
+ bool m_fInCreateProcess;
+
+ // True iff we are in the shim's FakeLoadModule callback. This is used to determine which hresult to
+ // return from code:CordbModule::SetJITCompilerFlags so we correctly emulate the behavior of v2.0.
+ // This is set at the beginning of the callback and cleared in code:CordbProcess::ContinueInternal.
+ bool m_fInLoadModule;
+ //
+ // Data
+ //
+
+ // Pointer to CordbProcess.
+ // @dbgtodo shim: We'd like this to eventually go through public interfaces (ICorDebugProcess)
+ IProcessShimHooks * m_pProcess; // Reference is kept by m_pIProcess;
+ RSExtSmartPtr<ICorDebugProcess> m_pIProcess;
+
+ // Win32EvenThread, which is the thread that uses the native debug API.
+ CordbWin32EventThread * m_pWin32EventThread;
+
+ // Actual data-target. Since we're shimming V2 scenarios, and V3 is always
+ // live-debugging, this is always a live data-target.
+ RSExtSmartPtr<ShimDataTarget> m_pLiveDataTarget;
+
+
+ // If true, the shim is emulating interop-debugging
+ // If false, the shim is emulating managed-only debugging.
+ // Both managed and native debugging have the same underlying pipeline (built
+ // on native-debug events). So the only difference is how they handle those events.
+ bool m_fIsInteropDebugging;
+
+ // true iff Dispose() was called. Consult this and do your work under m_ShimProcessDisposeLock
+ // to serialize yourself against a call to Dispose(). This protects your work
+ // from the user doing a Debugger Detach in the middle.
+ bool m_fIsDisposed;
+
+ //.............................................................................
+ //
+ // Members used for handling native events when managed-only debugging.
+ //
+ //.............................................................................
+
+ // Default handler for native events when managed-only debugging.
+ void DefaultEventHandler(const DEBUG_EVENT * pEvent, DWORD * pdwContinueStatus);
+
+ // Given a debug event, track the file handles.
+ void TrackFileHandleForDebugEvent(const DEBUG_EVENT * pEvent);
+
+ // Have we gotten the loader breakpoint yet?
+ // A Debugger needs to do special work to skip the loader breakpoint,
+ // and that's also when it should dispatch the faked managed attach events.
+ bool m_loaderBPReceived;
+
+ // Raw callback for ContinueStatusChanged from Data-target.
+ static HRESULT ContinueStatusChanged(void * pUserData, DWORD dwThreadId, CORDB_CONTINUE_STATUS dwContinueStatus);
+
+ // Real worker to update ContinueStatusChangedData
+ HRESULT ContinueStatusChangedWorker(DWORD dwThreadId, CORDB_CONTINUE_STATUS dwContinueStatus);
+
+ struct ContinueStatusChangedData
+ {
+ void Clear();
+ bool IsSet();
+ // Tid of Thread changed
+ DWORD m_dwThreadId;
+
+ // New continue status.
+ CORDB_CONTINUE_STATUS m_status;
+ } m_ContinueStatusChangedData;
+
+ // the hash table of ShimStackWalks
+ ShimStackWalkHashTable * m_pShimStackWalkHashTable;
+
+ // the hash table of duplicate creation events
+ DuplicateCreationEventsHashTable * m_pDupeEventsHashTable;
+
+ MachineInfo m_machineInfo;
+};
+
+
+//---------------------------------------------------------------------------------------
+//
+// This is the container class of ShimChains, ICorDebugFrames, ShimChainEnums, and ShimFrameEnums.
+// It has a 1:1 relationship with ICorDebugThreads. Upon creation, this class walks the entire stack and
+// caches all the stack frames and chains. The enumerators are created on demand.
+//
+
+class ShimStackWalk
+{
+public:
+ ShimStackWalk(ShimProcess * pProcess, ICorDebugThread * pThread);
+ ~ShimStackWalk();
+
+ // These functions do not adjust the reference count.
+ ICorDebugThread * GetThread();
+ ShimChain * GetChain(UINT32 index);
+ ICorDebugFrame * GetFrame(UINT32 index);
+
+ // Get the number of frames and chains.
+ ULONG GetChainCount();
+ ULONG GetFrameCount();
+
+ RSLock * GetShimLock();
+
+ // Add ICDChainEnum and ICDFrameEnum.
+ void AddChainEnum(ShimChainEnum * pChainEnum);
+ void AddFrameEnum(ShimFrameEnum * pFrameEnum);
+
+ // The next two functions are for ShimStackWalkHashTableTraits.
+ ICorDebugThread * GetKey();
+ static UINT32 Hash(ICorDebugThread * pThread);
+
+ // Check if the specified frame is the leaf frame according to the V2 definition.
+ BOOL IsLeafFrame(ICorDebugFrame * pFrame);
+
+ // Check if the two specified frames are the same. This function checks the SPs, frame address, etc.
+ // instead of just checking for pointer equality.
+ BOOL IsSameFrame(ICorDebugFrame * pLeft, ICorDebugFrame * pRight);
+
+ // The following functions are entry point into the ShimStackWalk. They are called by the RS.
+ void EnumerateChains(ICorDebugChainEnum ** ppChainEnum);
+
+ void GetActiveChain(ICorDebugChain ** ppChain);
+ void GetActiveFrame(ICorDebugFrame ** ppFrame);
+ void GetActiveRegisterSet(ICorDebugRegisterSet ** ppRegisterSet);
+
+ void GetChainForFrame(ICorDebugFrame * pFrame, ICorDebugChain ** ppChain);
+ void GetCallerForFrame(ICorDebugFrame * pFrame, ICorDebugFrame ** ppCallerFrame);
+ void GetCalleeForFrame(ICorDebugFrame * pFrame, ICorDebugFrame ** ppCalleeFrame);
+
+private:
+ //---------------------------------------------------------------------------------------
+ //
+ // This is a helper class used to store the information of a chain during a stackwalk. A chain is marked
+ // by the CONTEXT on the leaf boundary and a FramePointer on the root boundary. Also, notice that we
+ // are keeping two CONTEXTs. This is because some chain types may cancel a previous unmanaged chain.
+ // For example, a CHAIN_FUNC_EVAL chain cancels any CHAIN_ENTER_UNMANAGED chain immediately preceding
+ // it. In this case, the leaf boundary of the CHAIN_FUNC_EVAL chain is marked by the CONTEXT of the
+ // previous CHAIN_ENTER_MANAGED, not the previous CHAIN_ENTER_UNMANAGED.
+ //
+
+ struct ChainInfo
+ {
+ public:
+ ChainInfo() : m_rootFP(LEAF_MOST_FRAME), m_reason(CHAIN_NONE), m_fNeedEnterManagedChain(FALSE), m_fLeafNativeContextIsValid(FALSE) {}
+
+ void CancelUMChain() { m_reason = CHAIN_NONE; }
+ BOOL IsTrackingUMChain() { return (m_reason == CHAIN_ENTER_UNMANAGED); }
+
+ DT_CONTEXT m_leafNativeContext;
+ DT_CONTEXT m_leafManagedContext;
+ FramePointer m_rootFP;
+ CorDebugChainReason m_reason;
+ bool m_fNeedEnterManagedChain;
+ bool m_fLeafNativeContextIsValid;
+ };
+
+ //---------------------------------------------------------------------------------------
+ //
+ // This is a helper class used to store information during a stackwalk. Conceptually it is a simplified
+ // version of FrameInfo used on the LS in V2.
+ //
+
+ struct StackWalkInfo
+ {
+ public:
+ StackWalkInfo();
+ ~StackWalkInfo();
+
+ // Reset all the per-frame information.
+ void ResetForNextFrame();
+
+ // During the stackwalk, we need to find out whether we should process the next stack frame or the
+ // next internal frame. These functions help us determine whether we have exhausted one or both
+ // types of frames. The stackwalk is finished when both types are exhausted.
+ bool ExhaustedAllFrames();
+ bool ExhaustedAllStackFrames();
+ bool ExhaustedAllInternalFrames();
+
+ // Simple helper function to get the current internal frame.
+ ICorDebugInternalFrame2 * GetCurrentInternalFrame();
+
+ // Check whether we are processing the first frame.
+ BOOL IsLeafFrame();
+
+ // Check whether we are skipping frames because of a child frame.
+ BOOL IsSkippingFrame();
+
+ // Indicates whether we are dealing with a converted frame.
+ // See code:CordbThread::ConvertFrameForILMethodWithoutMetadata.
+ BOOL HasConvertedFrame();
+
+ // Store the child frame we are currently trying to find the parent frame for.
+ // If this is NULL, then we are not skipping frames.
+ RSExtSmartPtr<ICorDebugNativeFrame2> m_pChildFrame;
+
+ // Store the converted frame, if any.
+ RSExtSmartPtr<ICorDebugInternalFrame2> m_pConvertedInternalFrame2;
+
+ // Store the array of internal frames. This is an array of RSExtSmartPtrs, and so each element
+ // is protected, and we only need to call Clear() to release each element and free all the memory.
+ RSExtPtrArray<ICorDebugInternalFrame2> m_ppInternalFrame2;
+
+ UINT32 m_cChain; // number of chains
+ UINT32 m_cFrame; // number of frames
+ UINT32 m_firstFrameInChain; // the index of the first frame in the current chain
+ UINT32 m_cInternalFrames; // number of internal frames
+ UINT32 m_curInternalFrame; // the index of the current internal frame being processed
+
+ CorDebugInternalFrameType m_internalFrameType;
+
+ bool m_fExhaustedAllStackFrames;
+
+ // Indicate whether we are processing an internal frame or a stack frame.
+ bool m_fProcessingInternalFrame;
+
+ // Indicate whether we should skip the current chain because it's a chain derived from a leaf frame
+ // of type TYPE_INTERNAL. This is the behaviour in V2.
+ // See code:DebuggerWalkStackProc.
+ bool m_fSkipChain;
+
+ // Indicate whether the current frame is the first frame we process.
+ bool m_fLeafFrame;
+
+ // Indicate whether we are processing a converted frame.
+ bool m_fHasConvertedFrame;
+ };
+
+ // A ShimStackWalk is deleted when a process is continued, or when the stack is changed in any way
+ // (e.g. SetIP, EnC, etc.).
+ void Populate();
+ void Clear();
+
+ // Get a FramePointer to mark the root boundary of a chain.
+ FramePointer GetFramePointerForChain(DT_CONTEXT * pContext);
+ FramePointer GetFramePointerForChain(ICorDebugInternalFrame2 * pInternalFrame2);
+
+ CorDebugInternalFrameType GetInternalFrameType(ICorDebugInternalFrame2 * pFrame2);
+
+ // Append a frame to the array.
+ void AppendFrame(ICorDebugFrame * pFrame, StackWalkInfo * pStackWalkInfo);
+ void AppendFrame(ICorDebugInternalFrame2 * pInternalFrame2, StackWalkInfo * pStackWalkInfo);
+
+ // Append a chain to the array.
+ void AppendChainWorker(StackWalkInfo * pStackWalkInfo,
+ DT_CONTEXT * pLeafContext,
+ FramePointer fpRoot,
+ CorDebugChainReason chainReason,
+ BOOL fIsManagedChain);
+ void AppendChain(ChainInfo * pChainInfo, StackWalkInfo * pStackWalkInfo);
+
+ // Save information on the ChainInfo regarding the current chain.
+ void SaveChainContext(ICorDebugStackWalk * pSW, ChainInfo * pChainInfo, DT_CONTEXT * pContext);
+
+ // Check what we are process next, a internal frame or a stack frame.
+ BOOL CheckInternalFrame(ICorDebugFrame * pNextStackFrame,
+ StackWalkInfo * pStackWalkInfo,
+ ICorDebugThread3 * pThread3,
+ ICorDebugStackWalk * pSW);
+
+ // Convert an ICDInternalFrame to another ICDInternalFrame due to IL methods without metadata.
+ // See code:CordbThread::ConvertFrameForILMethodWithoutMetadata.
+ BOOL ConvertInternalFrameToDynamicMethod(StackWalkInfo * pStackWalkInfo);
+
+ // Convert an ICDNativeFrame to an ICDInternalFrame due to IL methods without metadata.
+ // See code:CordbThread::ConvertFrameForILMethodWithoutMetadata.
+ BOOL ConvertStackFrameToDynamicMethod(ICorDebugFrame * pFrame, StackWalkInfo * pStackWalkInfo);
+
+ // Process an unmanaged chain.
+ BOOL ShouldTrackUMChain(StackWalkInfo * pswInfo);
+ void TrackUMChain(ChainInfo * pChainInfo, StackWalkInfo * pStackWalkInfo);
+
+ // Check whether the internal frame is a newly exposed type in Arrowhead. If so, then the shim should
+ // not expose it.
+ BOOL IsV3FrameType(CorDebugInternalFrameType type);
+
+ // Check whether the specified frame represents a dynamic method.
+ BOOL IsILFrameWithoutMetadata(ICorDebugFrame * pFrame);
+
+ CDynArray<ShimChain *> m_stackChains; // growable ordered array of chains and frames
+ CDynArray<ICorDebugFrame *> m_stackFrames;
+
+ ShimChainEnum * m_pChainEnumList; // linked list of ShimChainEnum and ShimFrameEnum
+ ShimFrameEnum * m_pFrameEnumList;
+
+ // the thread on which we are doing a stackwalk, i.e. the "owning" thread
+ RSExtSmartPtr<ShimProcess> m_pProcess;
+ RSExtSmartPtr<ICorDebugThread> m_pThread;
+};
+
+
+//---------------------------------------------------------------------------------------
+//
+// This class implements the deprecated ICDChain interface.
+//
+
+class ShimChain : public ICorDebugChain
+{
+public:
+ ShimChain(ShimStackWalk * pSW,
+ DT_CONTEXT * pContext,
+ FramePointer fpRoot,
+ UINT32 chainIndex,
+ UINT32 frameStartIndex,
+ UINT32 frameEndIndex,
+ CorDebugChainReason chainReason,
+ BOOL fIsManaged,
+ RSLock * pShimLock);
+ virtual ~ShimChain();
+
+ void Neuter();
+ BOOL IsNeutered();
+
+ //
+ // IUnknown
+ //
+
+ ULONG STDMETHODCALLTYPE AddRef();
+ ULONG STDMETHODCALLTYPE Release();
+ COM_METHOD QueryInterface(REFIID riid, void ** ppInterface);
+
+ //
+ // ICorDebugChain
+ //
+
+ COM_METHOD GetThread(ICorDebugThread ** ppThread);
+ COM_METHOD GetStackRange(CORDB_ADDRESS * pStart, CORDB_ADDRESS * pEnd);
+ COM_METHOD GetContext(ICorDebugContext ** ppContext);
+ COM_METHOD GetCaller(ICorDebugChain ** ppChain);
+ COM_METHOD GetCallee(ICorDebugChain ** ppChain);
+ COM_METHOD GetPrevious(ICorDebugChain ** ppChain);
+ COM_METHOD GetNext(ICorDebugChain ** ppChain);
+ COM_METHOD IsManaged(BOOL * pManaged);
+ COM_METHOD EnumerateFrames(ICorDebugFrameEnum ** ppFrames);
+ COM_METHOD GetActiveFrame(ICorDebugFrame ** ppFrame);
+ COM_METHOD GetRegisterSet(ICorDebugRegisterSet ** ppRegisters);
+ COM_METHOD GetReason(CorDebugChainReason * pReason);
+
+ //
+ // accessors
+ //
+
+ // Get the owning ShimStackWalk.
+ ShimStackWalk * GetShimStackWalk();
+
+ // Get the first and last index of the frame owned by this chain. This class itself doesn't store the
+ // frames. Rather, the frames are stored on the ShimStackWalk. This class just stores the indices.
+ // Note that the indices are [firstIndex, lastIndex), i.e. the last index is exclusive.
+ UINT32 GetFirstFrameIndex();
+ UINT32 GetLastFrameIndex();
+
+private:
+ // A chain describes a stack range within the stack. This includes a CONTEXT at the start (leafmost)
+ // end of the chain, and a frame pointer where the chain ends (rootmost). This stack range is exposed
+ // publicly via ICDChain::GetStackRange(), and can be used to stitch managed and native stack frames
+ // together into a unified stack.
+ DT_CONTEXT m_context; // the leaf end of the chain
+ FramePointer m_fpRoot; // the root end of the chain
+
+ ShimStackWalk * m_pStackWalk; // the owning ShimStackWalk
+ Volatile<ULONG> m_refCount;
+
+ // The 0-based index of this chain in the ShimStackWalk's chain array (m_pStackWalk->m_stackChains).
+ UINT32 m_chainIndex;
+
+ // The 0-based index of the first frame owned by this chain in the ShimStackWalk's frame array
+ // (m_pStackWalk->m_stackFrames). See code::ShimChain::GetFirstFrameIndex().
+ UINT32 m_frameStartIndex;
+
+ // The 0-based index of the last frame owned by this chain in the ShimStackWalk's frame array
+ // (m_pStackWalk->m_stackFrames). This index is exlusive. See code::ShimChain::GetLastFrameIndex().
+ UINT32 m_frameEndIndex;
+
+ CorDebugChainReason m_chainReason;
+ BOOL m_fIsManaged; // indicates whether this chain contains managed frames
+ BOOL m_fIsNeutered;
+
+ RSLock * m_pShimLock; // shim lock from ShimProcess to protect neuteredness checks
+};
+
+
+//---------------------------------------------------------------------------------------
+//
+// This class implements the deprecated ICDChainEnum interface.
+//
+
+class ShimChainEnum : public ICorDebugChainEnum
+{
+public:
+ ShimChainEnum(ShimStackWalk * pSW, RSLock * pShimLock);
+ virtual ~ShimChainEnum();
+
+ void Neuter();
+ BOOL IsNeutered();
+
+ //
+ // IUnknown
+ //
+
+ ULONG STDMETHODCALLTYPE AddRef();
+ ULONG STDMETHODCALLTYPE Release();
+ COM_METHOD QueryInterface(REFIID riid, void ** ppInterface);
+
+ //
+ // ICorDebugEnum
+ //
+
+ COM_METHOD Skip(ULONG celt);
+ COM_METHOD Reset();
+ COM_METHOD Clone(ICorDebugEnum ** ppEnum);
+ COM_METHOD GetCount(ULONG * pcChains);
+
+ //
+ // ICorDebugChainEnum
+ //
+
+ COM_METHOD Next(ULONG cChains, ICorDebugChain * rgpChains[], ULONG * pcChainsFetched);
+
+ //
+ // accessors
+ //
+
+ // used to link ShimChainEnums in a list
+ ShimChainEnum * GetNext();
+ void SetNext(ShimChainEnum * pNext);
+
+private:
+ ShimStackWalk * m_pStackWalk; // the owning ShimStackWalk
+
+ // This points to the next ShimChainEnum in the linked list of ShimChainEnums to be cleaned up.
+ // The head of the list is on the ShimStackWalk (m_pStackWalk->m_pChainEnumList).
+ ShimChainEnum * m_pNext;
+
+ UINT32 m_currentChainIndex; // the index of the current ShimChain being enumerated
+ Volatile<ULONG> m_refCount;
+ BOOL m_fIsNeutered;
+
+ RSLock * m_pShimLock; // shim lock from ShimProcess to protect neuteredness checks
+};
+
+
+//---------------------------------------------------------------------------------------
+//
+// This class implements the deprecated ICDFrameEnum interface.
+//
+
+class ShimFrameEnum : public ICorDebugFrameEnum
+{
+public:
+ ShimFrameEnum(ShimStackWalk * pSW, ShimChain * pChain, UINT32 frameStartIndex, UINT32 frameEndIndex, RSLock * pShimLock);
+ virtual ~ShimFrameEnum();
+
+ void Neuter();
+ BOOL IsNeutered();
+
+ //
+ // IUnknown
+ //
+
+ ULONG STDMETHODCALLTYPE AddRef();
+ ULONG STDMETHODCALLTYPE Release();
+ COM_METHOD QueryInterface(REFIID riid, void ** ppInterface);
+
+ //
+ // ICorDebugEnum
+ //
+
+ COM_METHOD Skip(ULONG celt);
+ COM_METHOD Reset();
+ COM_METHOD Clone(ICorDebugEnum ** ppEnum);
+ COM_METHOD GetCount(ULONG * pcFrames);
+
+ //
+ // ICorDebugFrameEnum
+ //
+
+ COM_METHOD Next(ULONG cFrames, ICorDebugFrame * rgpFrames[], ULONG * pcFramesFetched);
+
+ //
+ // accessors
+ //
+
+ // used to link ShimChainEnums in a list
+ ShimFrameEnum * GetNext();
+ void SetNext(ShimFrameEnum * pNext);
+
+private:
+ ShimStackWalk * m_pStackWalk; // the owning ShimStackWalk
+ ShimChain * m_pChain; // the owning ShimChain
+ RSLock * m_pShimLock; // shim lock from ShimProcess to protect neuteredness checks
+
+ // This points to the next ShimFrameEnum in the linked list of ShimFrameEnums to be cleaned up.
+ // The head of the list is on the ShimStackWalk (m_pStackWalk->m_pFrameEnumList).
+ ShimFrameEnum * m_pNext;
+
+ UINT32 m_currentFrameIndex; // the current ICDFrame being enumerated
+ UINT32 m_endFrameIndex; // the last index (exclusive) of the frame owned by the chain;
+ // see code:ShimChain::GetLastFrameIndex
+ Volatile<ULONG> m_refCount;
+ BOOL m_fIsNeutered;
+};
+
+
+#endif // SHIMPRIV_H
+
diff --git a/src/debug/di/shimprocess.cpp b/src/debug/di/shimprocess.cpp
new file mode 100644
index 0000000000..a6fc15407e
--- /dev/null
+++ b/src/debug/di/shimprocess.cpp
@@ -0,0 +1,1904 @@
+// 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.
+//*****************************************************************************
+// File: ShimProcess.cpp
+//
+
+//
+// The V3 ICD debugging APIs have a lower abstraction level than V2.
+// This provides V2 ICD debugging functionality on top of the V3 debugger object.
+//*****************************************************************************
+
+#include "stdafx.h"
+
+#include "safewrap.h"
+#include "check.h"
+
+#include <limits.h>
+#include "shimpriv.h"
+
+#if !defined(FEATURE_CORESYSTEM)
+#include <tlhelp32.h>
+#endif
+
+//---------------------------------------------------------------------------------------
+//
+// Ctor for a ShimProcess
+//
+// Notes:
+// See InitializeDataTarget in header for details of how to instantiate a ShimProcess and hook it up.
+// Initial ref count is 0. This is the convention used int the RS, and it plays well with semantics
+// like immediately assigning to a smart pointer (which will bump the count up to 1).
+
+ShimProcess::ShimProcess() :
+ m_ref(0),
+ m_fFirstManagedEvent(false),
+ m_fInCreateProcess(false),
+ m_fInLoadModule(false),
+ m_fIsInteropDebugging(false),
+ m_fIsDisposed(false),
+ m_loaderBPReceived(false)
+{
+ m_ShimLock.Init("ShimLock", RSLock::cLockReentrant, RSLock::LL_SHIM_LOCK);
+ m_ShimProcessDisposeLock.Init(
+ "ShimProcessDisposeLock",
+ RSLock::cLockReentrant | RSLock::cLockNonDbgApi,
+ RSLock::LL_SHIM_PROCESS_DISPOSE_LOCK);
+ m_eventQueue.Init(&m_ShimLock);
+ m_pShimCallback.Assign(new ShimProxyCallback(this)); // Throws
+
+ m_fNeedFakeAttachEvents = false;
+ m_ContinueStatusChangedData.Clear();
+
+ m_pShimStackWalkHashTable = new ShimStackWalkHashTable();
+
+ m_pDupeEventsHashTable = new DuplicateCreationEventsHashTable();
+
+ m_machineInfo.Clear();
+
+ m_markAttachPendingEvent = WszCreateEvent(NULL, TRUE, FALSE, NULL);
+ if (m_markAttachPendingEvent == NULL)
+ {
+ ThrowLastError();
+ }
+
+ m_terminatingEvent = WszCreateEvent(NULL, TRUE, FALSE, NULL);
+ if (m_terminatingEvent == NULL)
+ {
+ ThrowLastError();
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// ShimProcess dtor. Invoked when reference count goes to 0.
+//
+// Assumptions:
+// Dtors should not do any interesting work. If this object has been initialized,
+// then call Dispose() first.
+//
+//
+ShimProcess::~ShimProcess()
+{
+ // Expected that this was either already disposed first, or not initialized.
+ _ASSERTE(m_pWin32EventThread == NULL);
+
+ _ASSERTE(m_ShimProcessDisposeLock.IsInit());
+ m_ShimProcessDisposeLock.Destroy();
+
+ if (m_markAttachPendingEvent != NULL)
+ {
+ CloseHandle(m_markAttachPendingEvent);
+ m_markAttachPendingEvent = NULL;
+ }
+
+ if (m_terminatingEvent != NULL)
+ {
+ CloseHandle(m_terminatingEvent);
+ m_terminatingEvent = NULL;
+ }
+
+ // Dtor will release m_pLiveDataTarget
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Part of initialization to hook up to process.
+//
+// Arguments:
+// pProcess - debuggee object to connect to. Maybe null if part of shutdown.
+//
+// Notes:
+// This will take a strong reference to the process object.
+// This is part of the initialization phase.
+// This should only be called once.
+//
+//
+void ShimProcess::SetProcess(ICorDebugProcess * pProcess)
+{
+ PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(NULL);
+
+ // Data-target should already be setup before we try to connect to a process.
+ _ASSERTE(m_pLiveDataTarget != NULL);
+
+ // Reference is kept by m_pProcess;
+ m_pIProcess.Assign(pProcess);
+
+ // Get the private shim hooks. This just exists to access private functionality that has not
+ // yet been promoted to the ICorDebug interfaces.
+ m_pProcess = static_cast<CordbProcess *>(pProcess);
+
+ if (pProcess != NULL)
+ {
+ // Verify that DataTarget + new process have the same pid?
+ _ASSERTE(m_pProcess->GetPid() == m_pLiveDataTarget->GetPid());
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Create a Data-Target around the live process.
+//
+// Arguments:
+// processId - OS process ID to connect to. Must be a local, same platform, process.
+//
+// Return Value:
+// S_OK on success.
+//
+// Assumptions:
+// This is part of the initialization dance.
+//
+// Notes:
+// Only call this once, during the initialization dance.
+//
+HRESULT ShimProcess::InitializeDataTarget(DWORD processId)
+{
+ _ASSERTE(m_pLiveDataTarget == NULL);
+
+
+ HRESULT hr = BuildPlatformSpecificDataTarget(GetMachineInfo(), processId, &m_pLiveDataTarget);
+ if (FAILED(hr))
+ {
+ _ASSERTE(m_pLiveDataTarget == NULL);
+ return hr;
+ }
+ m_pLiveDataTarget->HookContinueStatusChanged(ShimProcess::ContinueStatusChanged, this);
+
+ // Ref on pDataTarget is now 1.
+ _ASSERTE(m_pLiveDataTarget != NULL);
+
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Determines if current thread is the Win32 Event Thread
+//
+// Return Value:
+// True iff current thread is win32 event thread, else false.
+//
+// Notes:
+// The win32 event thread is created by code:ShimProcess::CreateAndStartWin32ET
+//
+bool ShimProcess::IsWin32EventThread()
+{
+ return (m_pWin32EventThread != NULL) && m_pWin32EventThread->IsWin32EventThread();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Add a reference
+//
+void ShimProcess::AddRef()
+{
+ InterlockedIncrement(&m_ref);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Release a reference.
+//
+// Notes:
+// When ref goes to 0, object is deleted.
+//
+void ShimProcess::Release()
+{
+ LONG ref = InterlockedDecrement(&m_ref);
+ if (ref == 0)
+ {
+ delete this;
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Dispose (Neuter) the object.
+//
+//
+// Assumptions:
+// This is called to gracefully shutdown the ShimProcess object.
+// This must be called before destruction if the object was initialized.
+//
+// Notes:
+// This will release all external resources, including getting the win32 event thread to exit.
+// This can safely be called multiple times.
+//
+void ShimProcess::Dispose()
+{
+ // Serialize Dispose with any other locked access to the shim. This helps
+ // protect against the debugger detaching while we're in the middle of
+ // doing stuff on the ShimProcess
+ RSLockHolder lockHolder(&m_ShimProcessDisposeLock);
+
+ m_fIsDisposed = true;
+
+ // Can't shut down the W32ET if we're on it.
+ _ASSERTE(!IsWin32EventThread());
+
+ m_eventQueue.DeleteAll();
+
+ if (m_pWin32EventThread != NULL)
+ {
+ // This will block waiting for the thread to exit gracefully.
+ m_pWin32EventThread->Stop();
+
+ delete m_pWin32EventThread;
+ m_pWin32EventThread = NULL;
+ }
+
+ if (m_pLiveDataTarget != NULL)
+ {
+ m_pLiveDataTarget->Dispose();
+ m_pLiveDataTarget.Clear();
+ }
+
+ m_pIProcess.Clear();
+ m_pProcess = NULL;
+
+ _ASSERTE(m_ShimLock.IsInit());
+ m_ShimLock.Destroy();
+
+ if (m_pShimStackWalkHashTable != NULL)
+ {
+ // The hash table should be empty by now. ClearAllShimStackWalk() should have been called.
+ _ASSERTE(m_pShimStackWalkHashTable->GetCount() == 0);
+
+ delete m_pShimStackWalkHashTable;
+ m_pShimStackWalkHashTable = NULL;
+ }
+
+ if (m_pDupeEventsHashTable != NULL)
+ {
+ if (m_pDupeEventsHashTable->GetCount() > 0)
+ {
+ // loop through all the entries in the hash table, remove them, and delete them
+ for (DuplicateCreationEventsHashTable::Iterator pCurElem = m_pDupeEventsHashTable->Begin(),
+ pEndElem = m_pDupeEventsHashTable->End();
+ pCurElem != pEndElem;
+ pCurElem++)
+ {
+ DuplicateCreationEventEntry * pEntry = *pCurElem;
+ delete pEntry;
+ }
+ m_pDupeEventsHashTable->RemoveAll();
+ }
+
+ delete m_pDupeEventsHashTable;
+ m_pDupeEventsHashTable = NULL;
+ }
+}
+
+
+
+//---------------------------------------------------------------------------------------
+// Track (and close) file handles from debug events.
+//
+// Arguments:
+// pEvent - debug event
+//
+// Notes:
+// Some debug events introduce file handles that the debugger needs to track and
+// close on other debug events. For example, the LoadDll,CreateProcess debug
+// events both give back a file handle that the debugger must close. This is generally
+// done on the corresponding UnloadDll/ExitProcess debug events.
+//
+// Since we won't use the file handles, we'll just close them as soon as we get them.
+// That way, we don't need to remember any state.
+void ShimProcess::TrackFileHandleForDebugEvent(const DEBUG_EVENT * pEvent)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ HANDLE hFile = NULL;
+
+ switch(pEvent->dwDebugEventCode)
+ {
+ //
+ // Events that add a file handle
+ //
+ case CREATE_PROCESS_DEBUG_EVENT:
+ hFile = pEvent->u.CreateProcessInfo.hFile;
+ CloseHandle(hFile);
+ break;
+
+ case LOAD_DLL_DEBUG_EVENT:
+ hFile = pEvent->u.LoadDll.hFile;
+ CloseHandle(hFile);
+ break;
+
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// ThreadProc helper to drain event queue.
+//
+// Arguments:
+// parameter - thread proc parameter, an ICorDebugProcess*
+//
+// Returns
+// 0.
+//
+// Notes:
+// This is useful when the shim queued a fake managed event (such as Control+C)
+// and needs to get the debuggee to synchronize in order to start dispatching events.
+// @dbgtodo sync: this will likely change as we iron out the Synchronization feature crew.
+//
+// We do this in a new thread proc to avoid thread restrictions:
+// Can't call this on win32 event thread because that can't send the IPC event to
+// make the aysnc-break request.
+// Can't call this on the RCET because that can't send an async-break (see SendIPCEvent for details)
+// So we just spin up a new thread to do the work.
+//---------------------------------------------------------------------------------------
+DWORD WINAPI CallStopGoThreadProc(LPVOID parameter)
+{
+ ICorDebugProcess* pProc = reinterpret_cast<ICorDebugProcess *>(parameter);
+
+ // We expect these operations to succeed; but if they do fail, there's nothing we can really do about it.
+ // If it fails on process exit/neuter/detach, then it would be ignorable.
+ HRESULT hr;
+
+
+ // Calling Stop + Continue will synchronize the process and force any queued events to be called.
+ // Stop is synchronous and will block until debuggee is synchronized.
+ hr = pProc->Stop(INFINITE);
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+
+ // Continue will resume the debuggee. If there are queued events (which we expect in this case)
+ // then continue will drain the event queue instead of actually resuming the process.
+ hr = pProc->Continue(FALSE);
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+
+ // This thread just needs to trigger an event dispatch. Now that it's done that, it can exit.
+ return 0;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Does default event handling for native debug events.
+//
+// Arguments:
+// pEvent - IN event ot handle
+// pdwContinueStatus - IN /OUT - continuation status for event.
+//
+// Assumptions:
+// Called when target is stopped. Caller still needs to Continue the debug event.
+// This is called on the win32 event thread.
+//
+// Notes:
+// Some native events require extra work before continuing. Eg, skip loader
+// breakpoint, close certain handles, etc.
+// This is only called in the manage-only case. In the interop-case, the
+// debugger will get and handle these native debug events.
+void ShimProcess::DefaultEventHandler(
+ const DEBUG_EVENT * pEvent,
+ DWORD * pdwContinueStatus)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+
+ //
+ // Loader breakpoint
+ //
+
+ BOOL fFirstChance;
+ const EXCEPTION_RECORD * pRecord = NULL;
+
+ if (IsExceptionEvent(pEvent, &fFirstChance, &pRecord))
+ {
+ DWORD dwThreadId = GetThreadId(pEvent);
+
+ switch(pRecord->ExceptionCode)
+ {
+ case STATUS_BREAKPOINT:
+ {
+ if (!m_loaderBPReceived)
+ {
+ m_loaderBPReceived = true;
+
+ // Clear the loader breakpoint
+ *pdwContinueStatus = DBG_CONTINUE;
+
+ // After loader-breakpoint, notify that managed attach can begin.
+ // This is done to trigger a synchronization. The shim
+ // can then send the fake attach events once the target
+ // is synced.
+ // @dbgtodo sync: not needed once shim can
+ // work on sync APIs.
+ m_pProcess->QueueManagedAttachIfNeeded(); // throws
+ }
+ }
+ break;
+
+ /*
+ // If we handle the Ctlr-C event here and send the notification to the debugger, then we may break pre-V4
+ // behaviour because the debugger may handle the event and intercept the handlers registered in the debuggee
+ // process. So don't handle the event here and let the debuggee process handle it instead. See Dev10 issue
+ // 846455 for more info.
+ //
+ // However, when the re-arch is completed, we will need to work with VS to define what the right behaviour
+ // should be. We don't want to rely on in-process code to handle the Ctrl-C event.
+ case DBG_CONTROL_C:
+ {
+ // Queue a fake managed Ctrl+C event.
+ m_pShimCallback->ControlCTrap(GetProcess());
+
+ // Request an Async Break
+ // This is on Win32 Event Thread, so we can't call Stop / Continue.
+ // Instead, spawn a new threead, and have that call Stop/Continue, which
+ // will get the RCET to drain the event queue and dispatch the ControlCTrap we just queued.
+ {
+ DWORD dwDummyId;
+ CreateThread(NULL,
+ 0,
+ CallStopGoThreadProc,
+ (LPVOID) GetProcess(),
+ 0,
+ &dwDummyId);
+ }
+
+ // We don't worry about suspending the Control-C thread right now. The event is
+ // coming asynchronously, and so it's ok if the debuggee slips forward while
+ // we try to do a managed async break.
+
+
+ // Clear the control-C event.
+ *pdwContinueStatus = DBG_CONTINUE;
+ }
+ break;
+
+*/
+ }
+
+
+ }
+
+
+ // Native debugging APIs have an undocumented expectation that you clear for OutputDebugString.
+ if (pEvent->dwDebugEventCode == OUTPUT_DEBUG_STRING_EVENT)
+ {
+ *pdwContinueStatus = DBG_CONTINUE;
+ }
+
+ //
+ // File handles.
+ //
+ TrackFileHandleForDebugEvent(pEvent);
+}
+
+//---------------------------------------------------------------------------------------
+// Determine if we need to change the continue status
+//
+// Returns:
+// True if the continue status was changed. Else false.
+//
+// Assumptions:
+// This is single-threaded, which is enforced by it only be called on the win32et.
+// The shim guarnatees only 1 outstanding debug-event at a time.
+//
+// Notes:
+// See code:ShimProcess::ContinueStatusChangedWorker for big picture.
+// Continue status is changed from a data-target callback which invokes
+// code:ShimProcess::ContinueStatusChangedWorker.
+// Call code:ShimProcess::ContinueStatusChangedData::Clear to clear the 'IsSet' bit.
+//
+bool ShimProcess::ContinueStatusChangedData::IsSet()
+{
+
+ return m_dwThreadId != 0;
+}
+
+//---------------------------------------------------------------------------------------
+// Clears the bit marking
+//
+// Assumptions:
+// This is single-threaded, which is enforced by it only be called on the win32et.
+// The shim guarantees only 1 outstanding debug-event at a time.
+//
+// Notes:
+// See code:ShimProcess::ContinueStatusChangedWorker for big picture.
+// This makes code:ShimProcess::ContinueStatusChangedData::IsSet return false.
+// This can safely be called multiple times in a row.
+//
+void ShimProcess::ContinueStatusChangedData::Clear()
+{
+ m_dwThreadId = 0;
+}
+
+//---------------------------------------------------------------------------------------
+// Callback invoked from data-target when continue status is changed.
+//
+// Arguments:
+// pUserData - data we supplied to the callback. a 'this' pointer.
+// dwThreadId - the tid whose continue status is changing
+// dwContinueStatus - the new continue status.
+//
+// Notes:
+//
+
+// Static
+HRESULT ShimProcess::ContinueStatusChanged(void * pUserData, DWORD dwThreadId, CORDB_CONTINUE_STATUS dwContinueStatus)
+{
+ ShimProcess * pThis = reinterpret_cast<ShimProcess *>(pUserData);
+ return pThis->ContinueStatusChangedWorker(dwThreadId, dwContinueStatus);
+}
+
+//---------------------------------------------------------------------------------------
+// Real worker callback invoked from data-target when continue status is changed.
+//
+// Arguments:
+// dwThreadId - the tid whose continue status is changing
+// dwContinueStatus - the new continue status.
+//
+// Notes:
+// ICorDebugProcess4::Filter returns an initial continue status (heavily biased to 'gn').
+// Some ICorDebug operations may need to change the continue status that filter returned.
+// For example, on windows, hijacking a thread at an unhandled exception would need to
+// change the status to 'gh' (since continuing 2nd chance exception 'gn' will tear down the
+// process and the hijack would never execute).
+//
+// Such operations will invoke into the data-target (code:ICorDebugMutableDataTarget::ContinueStatusChanged)
+// to notify the debugger that the continue status was changed.
+//
+// The shim only executes such operations on the win32-event thread in a small window between
+// WaitForDebugEvent and Continue. Therefore, we know:
+// * the callback must come on the Win32EventThread (which means our handling the callback is
+// single-threaded.
+// * We only have 1 outstanding debug event to worry about at a time. This simplifies our tracking.
+//
+// The shim tracks the outstanding change request in m_ContinueStatusChangedData.
+
+HRESULT ShimProcess::ContinueStatusChangedWorker(DWORD dwThreadId, CORDB_CONTINUE_STATUS dwContinueStatus)
+{
+ // Should only be set once. This is only called on the win32 event thread, which protects against races.
+ _ASSERTE(IsWin32EventThread());
+ _ASSERTE(!m_ContinueStatusChangedData.IsSet());
+
+ m_ContinueStatusChangedData.m_dwThreadId = dwThreadId;
+ m_ContinueStatusChangedData.m_status = dwContinueStatus;
+
+ // Setting dwThreadId to non-zero should now mark this as set.
+ _ASSERTE(m_ContinueStatusChangedData.IsSet());
+ return S_OK;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Add a duplicate creation event entry for the specified key.
+//
+// Arguments:
+// pKey - the key of the entry to be added; this is expected to be an
+// ICDProcess/ICDAppDomain/ICDThread/ICDAssembly/ICDModule
+//
+// Assumptions:
+// pKey is really an interface pointer of one of the types mentioned above
+//
+// Notes:
+// We have to keep track of which creation events we have sent already because some runtime data structures
+// are discoverable through enumeration before they send their creation events. As a result, we may have
+// faked up a creation event for a data structure during attach, and then later on get another creation
+// event for the same data structure. VS is not resilient in the face of multiple creation events for
+// the same data structure.
+//
+// Needless to say this is a problem in attach scenarios only. However, keep in mind that for CoreCLR,
+// launch really is early attach. For early attach, we get three creation events up front: a create
+// process, a create appdomain, and a create thread.
+//
+
+void ShimProcess::AddDuplicateCreationEvent(void * pKey)
+{
+ NewHolder<DuplicateCreationEventEntry> pEntry(new DuplicateCreationEventEntry(pKey));
+ m_pDupeEventsHashTable->Add(pEntry);
+ pEntry.SuppressRelease();
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Check whether the specified key exists in the hash table. If so, remove it.
+//
+// Arguments:
+// pKey - the key of the entry to check; this is expected to be an
+// ICDProcess/ICDAppDomain/ICDThread/ICDAssembly/ICDModule
+//
+// Return Value:
+// Returns true if the entry exists. The entry will have been removed because we can't have more than two
+// duplicates for any given event.
+//
+// Assumptions:
+// pKey is really an interface pointer of one of the types mentioned above
+//
+// Notes:
+// See code:ShimProcess::AddDuplicateCreationEvent.
+//
+
+bool ShimProcess::RemoveDuplicateCreationEventIfPresent(void * pKey)
+{
+ // We only worry about duplicate events in attach scenarios.
+ if (GetAttached())
+ {
+ // Only do the check if the hash table actually contains entries.
+ if (m_pDupeEventsHashTable->GetCount() > 0)
+ {
+ // Check if this is a dupe.
+ DuplicateCreationEventEntry * pResult = m_pDupeEventsHashTable->Lookup(pKey);
+ if (pResult != NULL)
+ {
+ // This is a dupe. We can't have a dupe twice, so remove it.
+ // This will help as a bit of optimization, since we will no longer check the hash table if
+ // its count reaches 0.
+ m_pDupeEventsHashTable->Remove(pKey);
+ delete pResult;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Gets the exception record format of the host
+//
+// Returns:
+// The CorDebugRecordFormat for the host architecture.
+//
+// Notes:
+// This corresponds to the definition EXCEPTION_RECORD on the host-architecture.
+// It can be passed into ICorDebugProcess4::Filter.
+CorDebugRecordFormat GetHostExceptionRecordFormat()
+{
+#if defined(_WIN64)
+ return FORMAT_WINDOWS_EXCEPTIONRECORD64;
+#else
+ return FORMAT_WINDOWS_EXCEPTIONRECORD32;
+#endif
+}
+
+//---------------------------------------------------------------------------------------
+// Main event handler for native debug events. Must also ensure Continue is called.
+//
+// Arguments:
+// pEvent - debug event to handle
+//
+// Assumptions:
+// Caller did a Flush() if needed.
+//
+// Notes:
+// The main Handle native debug events.
+// This must call back into ICD to let ICD filter the debug event (in case it's a managed notification).
+//
+// If we're interop-debugging (V2), then the debugger is expecting the debug events. In that case,
+// we go through the V2 interop-debugging logic to queue / dispatch the events.
+// If we're managed-only debugging, then the shim provides a default handler for the native debug.
+// This includes some basic work (skipping the loader breakpoint, close certain handles, etc).
+//---------------------------------------------------------------------------------------
+HRESULT ShimProcess::HandleWin32DebugEvent(const DEBUG_EVENT * pEvent)
+{
+ _ASSERTE(IsWin32EventThread());
+
+ //
+ // If this is an exception event, then we need to feed it into the CLR.
+ //
+ BOOL dwFirstChance = FALSE;
+ const EXCEPTION_RECORD * pRecord = NULL;
+ const DWORD dwThreadId = GetThreadId(pEvent);
+
+ bool fContinueNow = true;
+
+ // If true, we're continuing (unhandled) a 2nd-chance exception
+ bool fExceptionGoingUnhandled = false;
+
+ //
+ const DWORD kDONTCARE = 0;
+ DWORD dwContinueStatus = kDONTCARE;
+
+ if (IsExceptionEvent(pEvent, &dwFirstChance, &pRecord))
+ {
+ // As a diagnostic aid we can configure the debugger to assert when the debuggee does DebugBreak()
+#ifdef DEBUG
+ static ConfigDWORD config;
+ DWORD fAssert = config.val(CLRConfig::INTERNAL_DbgAssertOnDebuggeeDebugBreak);
+ if (fAssert)
+ {
+ // If we got a 2nd-chance breakpoint, then it's extremely likely that it's from an
+ // _ASSERTE in the target and we really want to know about it now before we kill the
+ // target. The debuggee will exit once we continue (unless we are mixed-mode debugging), so alert now.
+ // This assert could be our only warning of various catastrophic failures in the left-side.
+ if (!dwFirstChance && (pRecord->ExceptionCode == STATUS_BREAKPOINT) && !m_fIsInteropDebugging)
+ {
+ DWORD pid = (m_pLiveDataTarget == NULL) ? 0 : m_pLiveDataTarget->GetPid();
+
+ CONSISTENCY_CHECK_MSGF(false,
+ ("Unhandled breakpoint exception in debuggee (pid=%d (0x%x)) on thread %d(0x%x)\n"
+ "This may mean there was an assert in the debuggee on that thread.\n"
+ "\n"
+ "You should attach to that process (non-invasively) and get a callstack of that thread.\n"
+ "(This assert only occurs when CLRConfig::INTERNAL_DebuggerAssertOnDebuggeeDebugBreak is set)\n",
+ pid, pid, dwThreadId,dwThreadId));
+ }
+ }
+#endif
+
+ // We pass the Shim's proxy callback object, which will just take the callbacks and queue them
+ // to an event-queue in the shim. When we get the sync-complete event, the shim
+ // will then drain the event queue and dispatch the events to the user's callback object.
+ const DWORD dwFlags = dwFirstChance ? 1 : 0;
+
+ m_ContinueStatusChangedData.Clear();
+
+ // If ICorDebug doesn't care about this exception, it will leave dwContinueStatus unchanged.
+ RSExtSmartPtr<ICorDebugProcess4> pProcess4;
+ GetProcess()->QueryInterface(IID_ICorDebugProcess4, (void**) &pProcess4);
+
+ HRESULT hrFilter = pProcess4->Filter(
+ (const BYTE*) pRecord,
+ sizeof(EXCEPTION_RECORD),
+ GetHostExceptionRecordFormat(),
+ dwFlags,
+ dwThreadId,
+ m_pShimCallback,
+ &dwContinueStatus);
+ if (FAILED(hrFilter))
+ {
+ // Filter failed (eg. DAC couldn't be loaded), return the
+ // error so it can become an unrecoverable error.
+ return hrFilter;
+ }
+
+ // For unhandled exceptions, hijacking if needed.
+ if (!dwFirstChance)
+ {
+ // May invoke data-target callback (which may call code:ShimProcess::ContinueStatusChanged) to change continue status.
+ if (!m_pProcess->HijackThreadForUnhandledExceptionIfNeeded(dwThreadId))
+ {
+ // We decided not to hijack, so this exception is going to go unhandled
+ fExceptionGoingUnhandled = true;
+ }
+
+ if (m_ContinueStatusChangedData.IsSet())
+ {
+ _ASSERTE(m_ContinueStatusChangedData.m_dwThreadId == dwThreadId);
+
+ // Claiming this now means we won't do any other processing on the exception event.
+ // This means the interop-debugging logic will never see 2nd-chance managed exceptions.
+ dwContinueStatus = m_ContinueStatusChangedData.m_status;
+ }
+ }
+ }
+
+ // Do standard event handling, including Handling loader-breakpoint,
+ // and callback into CordbProcess for Attach if needed.
+ HRESULT hrIgnore = S_OK;
+ EX_TRY
+ {
+ // For NonClr notifications, allow extra processing.
+ // This includes both non-exception events, and exception events that aren't
+ // specific CLR debugging services notifications.
+ if (dwContinueStatus == kDONTCARE)
+ {
+ if (m_fIsInteropDebugging)
+ {
+ // Interop-debugging logic will handle the continue.
+ fContinueNow = false;
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ // @dbgtodo interop: All the interop-debugging logic is still in CordbProcess.
+ // Call back into that. This will handle Continuing the debug event.
+ m_pProcess->HandleDebugEventForInteropDebugging(pEvent);
+#else
+ _ASSERTE(!"Interop debugging not supported on Rotor");
+#endif
+ }
+ else
+ {
+ dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED;
+
+ // For managed-only debugging, there's no user handler for native debug events,
+ // and so we still need to do some basic work on certain debug events.
+ DefaultEventHandler(pEvent, &dwContinueStatus);
+
+ // This is the managed-only case. No reason to keep the target win32 frozen, so continue it immediately.
+ _ASSERTE(fContinueNow);
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hrIgnore);
+ // Dont' expect errors here (but could probably return it up to become an
+ // unrecoverable error if necessary). We still want to call Continue thought.
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hrIgnore));
+
+ //
+ // Continue the debuggee if needed.
+ //
+ if (fContinueNow)
+ {
+ BOOL fContinueOk = GetNativePipeline()->ContinueDebugEvent(
+ GetProcessId(pEvent),
+ dwThreadId,
+ dwContinueStatus);
+ (void)fContinueOk; //prevent "unused variable" error from GCC
+ SIMPLIFYING_ASSUMPTION(fContinueOk);
+
+ if (fExceptionGoingUnhandled)
+ {
+ _ASSERTE(dwContinueStatus == DBG_EXCEPTION_NOT_HANDLED);
+ // We just passed a 2nd-chance exception back to the OS which may have now invoked
+ // Windows error-reporting logic which suspended all threads in the target. Since we're
+ // still debugging and may want to break, inspect state and even detach (eg. to attach
+ // a different sort of debugger that can handle the exception) we need to let our threads run.
+ // Note that when WER auto-invokes a debugger it doesn't suspend threads, so it doesn't really
+ // make sense for them to be suspended now when a debugger is already attached.
+ // A better solution may be to suspend this faulting thread before continuing the event, do an
+ // async-break and give the debugger a notification of an unhandled exception. But this will require
+ // an ICorDebug API change, and also makes it harder to reliably get the WER dialog box once we're
+ // ready for it.
+ // Unfortunately we have to wait for WerFault.exe to start and actually suspend the threads, and
+ // there doesn't appear to be any better way than to just sleep for a little here. In practice 200ms
+ // seems like more than enough, but this is so uncommon of a scenario that a half-second delay
+ // (just to be safe) isn't a problem.
+ // Provide an undocumented knob to turn this behavior off in the very rare case it's not what we want
+ // (eg. we're trying to debug something that races with crashing / terminating the process on multiple
+ // threads)
+ static ConfigDWORD config;
+ DWORD fSkipResume = config.val(CLRConfig::UNSUPPORTED_DbgDontResumeThreadsOnUnhandledException);
+ if (!fSkipResume)
+ {
+ ::Sleep(500);
+ hrIgnore = GetNativePipeline()->EnsureThreadsRunning();
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hrIgnore));
+ }
+ }
+ }
+
+ return S_OK;
+}
+
+// Trivial accessor to get the event queue.
+ManagedEventQueue * ShimProcess::GetManagedEventQueue()
+{
+ return &m_eventQueue;
+}
+
+// Combines GetManagedEventQueue() and Dequeue() into a single function
+// that holds m_ShimProcessDisposeLock for the duration
+ManagedEvent * ShimProcess::DequeueManagedEvent()
+{
+ // Serialize this function with Dispoe()
+ RSLockHolder lockHolder(&m_ShimProcessDisposeLock);
+ if (m_fIsDisposed)
+ return NULL;
+
+ return m_eventQueue.Dequeue();
+}
+
+// Trivial accessor to get Shim's proxy callback object.
+ShimProxyCallback * ShimProcess::GetShimCallback()
+{
+ return m_pShimCallback;
+}
+
+// Trivial accessor to get the ICDProcess for the debuggee.
+// A ShimProcess object can then provide V2 functionality by building it on V3 functionality
+// exposed by the ICDProcess object.
+ICorDebugProcess * ShimProcess::GetProcess()
+{
+ return m_pIProcess;
+}
+
+// Trivial accessor to get the data-target for the debuggee.
+// The data-target lets us access the debuggee, especially reading debuggee memory.
+ICorDebugMutableDataTarget * ShimProcess::GetDataTarget()
+{
+ return m_pLiveDataTarget;
+};
+
+
+// Trivial accessor to get the raw native event pipeline.
+// In V3, ICorDebug no longer owns the event thread and it does not own the event pipeline either.
+INativeEventPipeline * ShimProcess::GetNativePipeline()
+{
+ return m_pWin32EventThread->GetNativePipeline();
+}
+
+// Trivial accessor to expose the W32ET thread to the CordbProcess so that it can emulate V2 behavior.
+// In V3, ICorDebug no longer owns the event thread and it does not own the event pipeline either.
+// The Win32 Event Thread is the only thread that can use the native pipeline
+// see code:ShimProcess::GetNativePipeline.
+CordbWin32EventThread * ShimProcess::GetWin32EventThread()
+{
+ return m_pWin32EventThread;
+}
+
+
+// Trivial accessor to mark whether we're interop-debugging.
+// Retreived via code:ShimProcess::IsInteropDebugging
+void ShimProcess::SetIsInteropDebugging(bool fIsInteropDebugging)
+{
+ m_fIsInteropDebugging = fIsInteropDebugging;
+}
+
+// Trivial accessor to check if we're interop-debugging.
+// This affects how we handle native debug events.
+// The significant usage of this is in code:ShimProcess::HandleWin32DebugEvent
+bool ShimProcess::IsInteropDebugging()
+{
+ return m_fIsInteropDebugging;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Begin queueing the fake attach events.
+//
+// Notes:
+// See code:ShimProcess::QueueFakeAttachEvents for more about "fake attach events".
+//
+// This marks that we need to send fake attach events, and queus a CreateProcess.
+// Caller calls code:ShimProcess::QueueFakeAttachEventsIfNeeded to finish queuing
+// the rest of the fake attach events.
+void ShimProcess::BeginQueueFakeAttachEvents()
+{
+ m_fNeedFakeAttachEvents = true;
+
+ // Put a fake CreateProcess event in the queue.
+ // This will not be drained until we get a Sync-Complete from the Left-side.
+ GetShimCallback()->QueueCreateProcess(GetProcess());
+ AddDuplicateCreationEvent(GetProcess());
+}
+
+//---------------------------------------------------------------------------------------
+// potentially Queue fake attach events like we did in V2.
+//
+// Arguments:
+// fRealCreateProcessEvent - true if the shim is about to dispatch a real create process event (as opposed
+// to one faked up by the shim itself)
+//
+// Notes:
+// See code:ShimProcess::QueueFakeAttachEvents for details.
+void ShimProcess::QueueFakeAttachEventsIfNeeded(bool fRealCreateProcessEvent)
+{
+ // This was set high in code:ShimProcess::BeginQueueFakeAttachEvents
+ if (!m_fNeedFakeAttachEvents)
+ {
+ return;
+ }
+ m_fNeedFakeAttachEvents = false;
+
+ // If the first event we get after attaching is a create process event, then this is an early attach
+ // scenario and we don't need to queue any fake attach events.
+ if (!fRealCreateProcessEvent)
+ {
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ QueueFakeAttachEvents();
+ }
+ EX_CATCH_HRESULT(hr);
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// Send fake Thread-create events for attach, using an arbitrary order.
+//
+// Returns:
+// S_OK on success, else error.
+//
+// Notes:
+// This sends fake thread-create events, ala V2 attach.
+// See code:ShimProcess::QueueFakeAttachEvents for details
+//
+// The order of thread creates is random and at the mercy of ICorDebugProcess::EnumerateThreads.
+// Whidbey would send thread creates in the order of the OS's native thread
+// list. Since Arrowhead no longer sends fake attach events, the shim simulates
+// the fake attach events. But ICorDebug doesn't provide a way to get the
+// same order that V2 used. So without using platform-specific thread-enumeration,
+// we can't get the V2 ordering.
+//
+// Compare to code:ShimProcess::QueueFakeThreadAttachEventsNativeOrder,
+// which sends threads in the OS native thread create order.
+//
+HRESULT ShimProcess::QueueFakeThreadAttachEventsNoOrder()
+{
+ ICorDebugProcess * pProcess = GetProcess();
+
+ RSExtSmartPtr<ICorDebugThreadEnum> pThreadEnum;
+ RSExtSmartPtr<ICorDebugThread> pThread;
+
+ // V2 would only send create threads after a thread had run managed code.
+ // V3 has a discovery model where Enumeration can find threads before they've run managed code.
+ // So the emulation here may send some additional create-thread events that v2 didn't send.
+ HRESULT hr = pProcess->EnumerateThreads(&pThreadEnum);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ ULONG cDummy;
+
+ while(SUCCEEDED(pThreadEnum->Next(1, &pThread, &cDummy)) && (pThread != NULL))
+ {
+ RSExtSmartPtr<ICorDebugAppDomain> pAppDomain;
+ hr = pThread->GetAppDomain(&pAppDomain);
+
+ // Getting the appdomain shouldn't fail. If it does, we can't dispatch
+ // this callback, but we can still dispatch the other thread creates.
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ if (pAppDomain != NULL)
+ {
+ GetShimCallback()->CreateThread(pAppDomain, pThread);
+ AddDuplicateCreationEvent(pThread);
+ }
+ pThread.Clear();
+ }
+
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+// Send fake Thread-create events for attach, using the order of the OS native
+// thread list.
+//
+// Returns:
+// S_OK on success, else error.
+//
+// Notes:
+// This sends fake thread-create events, ala V2 attach.
+// See code:ShimProcess::QueueFakeAttachEvents for details
+// The order of the thread creates matches the OS's native thread list.
+// This is important because the debugger can use the order of thread-create
+// callbacks to associate logical thread-ids (0,1,2...) with threads. Users
+// may rely on thread 0 always being the main thread.
+// In contrast, the order from ICorDebugProcess::EnumerateThreads is random.
+//
+// Compare to code:ShimProcess::QueueFakeThreadAttachEventsNoOrder, which
+// sends the threads in an arbitrary order.
+HRESULT ShimProcess::QueueFakeThreadAttachEventsNativeOrder()
+{
+#ifdef FEATURE_CORESYSTEM
+ _ASSERTE("NYI");
+ return E_FAIL;
+#else
+ ICorDebugProcess * pProcess = GetProcess();
+
+ DWORD dwProcessId;
+ HRESULT hr = pProcess->GetID(&dwProcessId);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
+ THREADENTRY32 te32;
+
+ // Take a snapshot of all running threads
+ hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
+ if (hThreadSnap == INVALID_HANDLE_VALUE)
+ {
+ hr = HRESULT_FROM_GetLastError();
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ return hr;
+ }
+ // HandleHolder doesn't deal with INVALID_HANDLE_VALUE, so we only assign if we have a legal value.
+ HandleHolder hSnapshotHolder(hThreadSnap);
+
+ // Fill in the size of the structure before using it.
+ te32.dwSize = sizeof(THREADENTRY32);
+
+ // Retrieve information about the first thread, and exit if unsuccessful
+ if (!Thread32First(hThreadSnap, &te32))
+ {
+ hr = HRESULT_FROM_GetLastError();
+ return hr;
+ }
+
+ // Now walk the thread list of the system,
+ // and display information about each thread
+ // associated with the specified process
+ do
+ {
+ if (te32.th32OwnerProcessID == dwProcessId)
+ {
+ RSExtSmartPtr<ICorDebugThread> pThread;
+ pProcess->GetThread(te32.th32ThreadID, &pThread);
+ if (pThread != NULL)
+ {
+ // If we fail to get the appdomain for some reason, then then
+ // we can't dispatch this thread callback. But we can still
+ // finish enumerating.
+ RSExtSmartPtr<ICorDebugAppDomain> pAppDomain;
+ HRESULT hrGetAppDomain = pThread->GetAppDomain(&pAppDomain);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hrGetAppDomain);
+ if (pAppDomain != NULL)
+ {
+ GetShimCallback()->CreateThread(pAppDomain, pThread);
+ AddDuplicateCreationEvent(pThread);
+
+ //fix for issue DevDiv2\DevDiv 77523 - threads are switched out in SQL don't get thread create notifications
+ // mark that this thread has queued a create event
+ CordbThread* pThreadInternal = static_cast<CordbThread*>(pThread.GetValue());
+ pThreadInternal->SetCreateEventQueued();
+ }
+ }
+ }
+ } while(Thread32Next(hThreadSnap, &te32));
+
+
+ //fix for issue DevDiv2\DevDiv 77523 - threads are switched out in SQL don't get thread create notifications
+ //
+
+
+ // Threads which were switched out won't be present in the native thread order enumeration above.
+ // In order to not miss them we will enumerate all the managed thread objects and for any that we haven't
+ // already queued a notification for, we will queue a notification now.
+ RSExtSmartPtr<ICorDebugThreadEnum> pThreadEnum;
+ RSExtSmartPtr<ICorDebugThread> pThread;
+ hr = pProcess->EnumerateThreads(&pThreadEnum);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ ULONG cDummy;
+
+ while(SUCCEEDED(pThreadEnum->Next(1, &pThread, &cDummy)) && (pThread != NULL))
+ {
+ RSExtSmartPtr<ICorDebugAppDomain> pAppDomain;
+ hr = pThread->GetAppDomain(&pAppDomain);
+ CordbThread* pThreadInternal = static_cast<CordbThread*>(pThread.GetValue());
+
+ // Getting the appdomain shouldn't fail. If it does, we can't dispatch
+ // this callback, but we can still dispatch the other thread creates.
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ if (pAppDomain != NULL && !pThreadInternal->CreateEventWasQueued())
+ {
+ GetShimCallback()->CreateThread(pAppDomain, pThread);
+ AddDuplicateCreationEvent(pThread);
+ pThreadInternal->SetCreateEventQueued();
+ }
+ pThread.Clear();
+ }
+
+
+ return S_OK;
+#endif
+}
+
+//---------------------------------------------------------------------------------------
+// Queues the fake Assembly and Module load events
+//
+// Arguments:
+// pAssembly - non-null, the assembly to queue.
+//
+// Notes:
+// Helper for code:ShimProcess::QueueFakeAttachEvents
+// Queues create events for the assembly and for all modules within the
+// assembly. Most assemblies only have 1 module.
+void ShimProcess::QueueFakeAssemblyAndModuleEvent(ICorDebugAssembly * pAssembly)
+{
+ RSExtSmartPtr<ICorDebugAppDomain> pAppDomain;
+
+ HRESULT hr = pAssembly->GetAppDomain(&pAppDomain);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ //
+ // Send the fake Load Assembly event.
+ //
+ GetShimCallback()->LoadAssembly(pAppDomain, pAssembly);
+ AddDuplicateCreationEvent(pAssembly);
+
+ //
+ // Send Modules - must be in load order
+ //
+ RSExtSmartPtr<ICorDebugModuleEnum> pModuleEnum;
+ hr = pAssembly->EnumerateModules(&pModuleEnum);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ ULONG countModules;
+ hr = pModuleEnum->GetCount(&countModules);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ // ISSUE WORKAROUND 835869
+ // The CordbEnumFilter used as the implementation of CordbAssembly::EnumerateModules has
+ // a ref counting bug in it. It adds one ref to each item when it is constructed and never
+ // removes that ref. Expected behavior would be that it adds a ref at construction, another on
+ // every call to next, and releases the construction ref when the enumerator is destroyed. The
+ // user is expected to release the reference they receive from Next. Thus enumerating exactly
+ // one time and calling Release() does the correct thing regardless of whether this bug is present
+ // or not. Note that with the bug the enumerator holds 0 references at the end of this loop,
+ // however the assembly also holds references so the modules will not be prematurely released.
+ for(ULONG i = 0; i < countModules; i++)
+ {
+ ICorDebugModule* pModule = NULL;
+ ULONG countFetched = 0;
+ pModuleEnum->Next(1, &pModule, &countFetched);
+ _ASSERTE(pModule != NULL);
+ if(pModule != NULL)
+ {
+ pModule->Release();
+ }
+ }
+
+ RSExtSmartPtr<ICorDebugModule> * pModules = new RSExtSmartPtr<ICorDebugModule> [countModules];
+ m_pProcess->GetModulesInLoadOrder(pAssembly, pModules, countModules);
+ for(ULONG iModule = 0; iModule < countModules; iModule++)
+ {
+ ICorDebugModule * pModule = pModules[iModule];
+
+ GetShimCallback()->FakeLoadModule(pAppDomain, pModule);
+ AddDuplicateCreationEvent(pModule);
+
+ // V2 may send UpdatePdbStreams for certain modules (like dynamic or in-memory modules).
+ // We don't yet have this support for out-of-proc.
+ // When the LoadModule event that we just queued is actually dispatched, it will
+ // send an IPC event in-process that will collect the information and queue the event
+ // at that time.
+ // @dbgtodo : I don't think the above is true anymore - clean it up?
+
+ RSExtSmartPtr<IStream> pSymbolStream;
+
+ // ICorDebug has no public way to request raw symbols. This is by-design because we
+ // don't want people taking a dependency on a specific format (to give us the ability
+ // to innovate for the RefEmit case). So we must use a private hook here to get the
+ // symbol data.
+ CordbModule * pCordbModule = static_cast<CordbModule *>(pModule);
+ IDacDbiInterface::SymbolFormat symFormat = IDacDbiInterface::kSymbolFormatNone;
+ EX_TRY
+ {
+ symFormat = pCordbModule->GetInMemorySymbolStream(&pSymbolStream);
+ }
+ EX_CATCH_HRESULT(hr);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr); // Shouldn't be any errors trying to read symbols
+
+ // Only pass the raw symbols onto the debugger if they're in PDB format (all that was supported
+ // in V2). Note that we could have avoided creating a stream for the non-PDB case, but we'd have
+ // to refactor GetInMemorySymbolStream and the perf impact should be negligable.
+ if (symFormat == IDacDbiInterface::kSymbolFormatPDB)
+ {
+ _ASSERTE(pSymbolStream != NULL); // symFormat should have been kSymbolFormatNone if null stream
+ GetShimCallback()->UpdateModuleSymbols(pAppDomain, pModule, pSymbolStream);
+ }
+
+ }
+ delete [] pModules;
+}
+
+//---------------------------------------------------------------------------------------
+// Get an array of appdomains, sorted by increasing AppDomain ID
+//
+// Arguments:
+// pProcess - process containing the appdomains
+// ppAppDomains - array that this function will allocate to hold appdomains
+// pCount - size of ppAppDomains array
+//
+// Assumptions:
+// Caller must delete [] ppAppDomains
+//
+// Notes
+// This is used as part of code:ShimProcess::QueueFakeAttachEvents.
+// The fake attach events want appdomains in creation order. ICorDebug doesn't provide
+// this ordering in the enumerators.
+//
+// This returns the appdomains sorted in order of increasing AppDomain ID, since that's the best
+// approximation of creation order that we have.
+// @dbgtodo - determine if ICD will provide
+// ordered enumerators
+//
+HRESULT GetSortedAppDomains(ICorDebugProcess * pProcess, RSExtSmartPtr<ICorDebugAppDomain> **ppAppDomains, ULONG * pCount)
+{
+ _ASSERTE(ppAppDomains != NULL);
+
+ HRESULT hr = S_OK;
+ RSExtSmartPtr<ICorDebugAppDomainEnum> pAppEnum;
+
+ //
+ // Find the size of the array to hold all the appdomains
+ //
+ hr = pProcess->EnumerateAppDomains(&pAppEnum);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ ULONG countAppDomains = 0;
+
+ hr = pAppEnum->GetCount(&countAppDomains);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ //
+ // Allocate the array
+ //
+ RSExtSmartPtr<ICorDebugAppDomain> * pAppDomains = new RSExtSmartPtr<ICorDebugAppDomain>[countAppDomains];
+ *ppAppDomains = pAppDomains;
+ *pCount = countAppDomains;
+
+ //
+ // Load all the appdomains into the array
+ //
+ ULONG countDummy;
+ hr = pAppEnum->Next(countAppDomains, (ICorDebugAppDomain**) pAppDomains, &countDummy);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ SIMPLIFYING_ASSUMPTION(countDummy == countAppDomains);
+
+ //
+ // Now sort them based on appdomain ID.
+ // We generally expect a very low number of appdomains (usually 1). So a n^2 sort shouldn't be a perf
+ // problem here.
+ //
+ for(ULONG i = 0; i < countAppDomains; i++)
+ {
+ ULONG32 id1;
+ hr = pAppDomains[i]->GetID(&id1);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ for(ULONG j = i + 1; j < countAppDomains; j++)
+ {
+ ULONG32 id2;
+ hr = pAppDomains[j]->GetID(&id2);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ if (id1 > id2)
+ {
+ // swap values
+ ICorDebugAppDomain * pTemp = pAppDomains[i];
+ pAppDomains[i].Assign(pAppDomains[j]);
+ pAppDomains[j].Assign(pTemp);
+
+ // update id1 key since it's in the outer-loop.
+ id1 = id2;
+ }
+ }
+ }
+
+
+
+ return S_OK;
+
+}
+
+//---------------------------------------------------------------------------------------
+// To emulate the V2 attach-handshake, give the shim a chance to inject fake attach events.
+//
+// Notes:
+// Do this before the queue is empty so that HasQueuedCallbacks() doesn't toggle from false to true.
+// This is called once the process is synchronized, which emulates V2 semantics on attach.
+// This may be called on the Win32Event Thread from inside of Filter, or on another thread.
+void ShimProcess::QueueFakeAttachEvents()
+{
+ // Serialize this function with Dispose()
+ RSLockHolder lockHolder(&m_ShimProcessDisposeLock);
+ if (m_fIsDisposed)
+ return;
+
+ // The fake CreateProcess is already queued. Start queuing the rest of the events.
+ // The target is stopped (synchronized) this whole time.
+ // This will use the inspection API to look at the process and queue up the fake
+ // events that V2 would have sent in a similar situation. All of the callbacks to GetShimCallback()
+ // just queue up the events. The event queue is then drained as the V2 debugger calls continue.
+
+ HRESULT hr = S_OK;
+ ICorDebugProcess * pProcess = GetProcess();
+
+ //
+ // First, Queue all the Fake AppDomains
+ //
+ RSExtSmartPtr<ICorDebugAppDomain> * pAppDomains = NULL;
+ ULONG countAppDomains = 0;
+
+ hr = GetSortedAppDomains(pProcess, &pAppDomains, &countAppDomains);
+ if (FAILED(hr))
+ return;
+
+ for(ULONG i = 0; i < countAppDomains; i++)
+ {
+ // V2 expects that the debugger then attaches to each AppDomain during the Create-appdomain callback.
+ // This was done to allow for potential per-appdomain debugging. However, only-process
+ // wide debugging support was allowed in V2. The caller had to attach to all Appdomains.
+
+ GetShimCallback()->CreateAppDomain(pProcess, pAppDomains[i]);
+ AddDuplicateCreationEvent(pAppDomains[i]);
+ }
+
+ // V2 had a break in the callback queue at this point.
+
+ //
+ // Second, queue all Assembly and Modules events.
+ //
+
+ for(ULONG iAppDomain = 0; iAppDomain < countAppDomains; iAppDomain++)
+ {
+ ICorDebugAppDomain * pAppDomain = pAppDomains[iAppDomain];
+ //
+ // Send Assemblies. Must be in load order.
+ //
+
+ RSExtSmartPtr<ICorDebugAssemblyEnum> pAssemblyEnum;
+ hr = pAppDomain->EnumerateAssemblies(&pAssemblyEnum);
+ if (FAILED(hr))
+ break;
+
+ ULONG countAssemblies;
+ hr = pAssemblyEnum->GetCount(&countAssemblies);
+ if (FAILED(hr))
+ break;
+
+ RSExtSmartPtr<ICorDebugAssembly> * pAssemblies = new RSExtSmartPtr<ICorDebugAssembly> [countAssemblies];
+ m_pProcess->GetAssembliesInLoadOrder(pAppDomain, pAssemblies, countAssemblies);
+ for(ULONG iAssembly = 0; iAssembly < countAssemblies; iAssembly++)
+ {
+ QueueFakeAssemblyAndModuleEvent(pAssemblies[iAssembly]);
+ }
+ delete [] pAssemblies;
+
+ }
+
+ delete [] pAppDomains;
+
+
+ // V2 would have a break in the callback queue at this point.
+
+ // V2 would send all relevant ClassLoad events now.
+ //
+ // That includes class loads for all modules that:
+ // - are dynamic
+ // - subscribed to class load events via ICorDebugModule::EnableClassLoadCallbacks.
+ // We don't provide Class-loads in our emulation because:
+ // 1. "ClassLoad" doesn't actually mean anything here.
+ // 2. We have no way of enumerating "loaded" classes in the CLR. We could use the metadata to enumerate
+ // all classes, but that's offers no value.
+ // 3. ClassLoad is useful for dynamic modules to notify a debugger that the module changed and
+ // to update symbols; but the LoadModule/UpdateModule syms already do that.
+
+
+ //
+ // Third, Queue all Threads
+ //
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI) && !defined(FEATURE_CORESYSTEM)
+ // Use OS thread enumeration facilities to ensure that the managed thread
+ // thread order is the same as the corresponding native thread order.
+ QueueFakeThreadAttachEventsNativeOrder();
+#else
+ // Use ICorDebug to enumerate threads. The order of managed threads may
+ // not match the order the threads were created in.
+ QueueFakeThreadAttachEventsNoOrder();
+#endif
+
+ // Forth, Queue all Connections.
+ // Enumerate connections is not exposed through ICorDebug, so we need to go use a private hook on CordbProcess.
+ m_pProcess->QueueFakeConnectionEvents();
+
+ // For V2 jit-attach, the callback queue would also include the jit-attach event (Exception, UserBreak, MDA, etc).
+ // This was explicitly in the same callback queue so that a debugger would drain it as part of draining the attach
+ // events.
+
+ // In V3, on normal attach, the VM just sends a Sync-complete event.
+ // On jit-attach, the VM sends the jit-attach event and then the sync-complete.
+ // The shim just queues the fake attach events at the first event it gets from the left-side.
+ // In jit-attach, the shim will queue the fake events right before it queues the jit-attach event,
+ // thus keeping them in the same callback queue as V2 did.
+
+}
+
+// Accessor for m_attached.
+bool ShimProcess::GetAttached()
+{
+ return m_attached;
+}
+// We need to know whether we are in the CreateProcess callback to be able to
+// return the v2.0 hresults from code:CordbProcess::SetDesiredNGENCompilerFlags
+// when we are using the shim.
+//
+// Expose m_fInCreateProcess
+bool ShimProcess::GetInCreateProcess()
+{
+ return m_fInCreateProcess;
+}
+
+void ShimProcess::SetInCreateProcess(bool value)
+{
+ m_fInCreateProcess = value;
+}
+
+// We need to know whether we are in the FakeLoadModule callback to be able to
+// return the v2.0 hresults from code:CordbModule::SetJITCompilerFlags when
+// we are using the shim.
+//
+// Expose m_fInLoadModule
+bool ShimProcess::GetInLoadModule()
+{
+ return m_fInLoadModule;
+
+}
+
+void ShimProcess::SetInLoadModule(bool value)
+{
+ m_fInLoadModule = value;
+}
+
+// When we get a continue, we need to clear the flags indicating we're still in a callback
+void ShimProcess::NotifyOnContinue ()
+{
+ m_fInCreateProcess = false;
+ m_fInLoadModule = false;
+}
+
+// The RS calls this function when the stack is about to be changed in any way, e.g. continue, SetIP, etc.
+void ShimProcess::NotifyOnStackInvalidate()
+{
+ ClearAllShimStackWalk();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Filter HResults for ICorDebugProcess2::SetDesiredNGENCompilerFlags to emualte V2 error semantics.
+// Arguments:
+// hr - V3 hresult
+//
+// Returns:
+// hresult V2 would have returned in same situation.
+HRESULT ShimProcess::FilterSetNgenHresult(HRESULT hr)
+{
+ if ((hr == CORDBG_E_MUST_BE_IN_CREATE_PROCESS) && !m_fInCreateProcess)
+ {
+ return hr;
+ }
+ if (m_attached)
+ {
+ return CORDBG_E_CANNOT_BE_ON_ATTACH;
+ }
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Filter HRs for ICorDebugModule::EnableJITDebugging, ICorDebugModule2::SetJITCompilerFlags
+// to emulate V2 error semantics
+//
+// Arguments:
+// hr - V3 hresult
+//
+// Returns:
+// hresult V2 would have returned in same situation.
+HRESULT ShimProcess::FilterSetJitFlagsHresult(HRESULT hr)
+{
+ if ((hr == CORDBG_E_MUST_BE_IN_LOAD_MODULE) && !m_fInLoadModule)
+ {
+ return hr;
+ }
+ if (m_attached && (hr == CORDBG_E_MUST_BE_IN_LOAD_MODULE))
+ {
+ return CORDBG_E_CANNOT_BE_ON_ATTACH;
+ }
+ return hr;
+}
+
+// ----------------------------------------------------------------------------
+// ShimProcess::LookupOrCreateShimStackWalk
+//
+// Description:
+// Find the ShimStackWalk associated with the specified ICDThread. Create one if it's not found.
+//
+// Arguments:
+// * pThread - the specified thread
+//
+// Return Value:
+// Return the ShimStackWalk associated with the specified thread.
+//
+// Notes:
+// The ShimStackWalks handed back by this function is only valid until the next time the stack is changed
+// in any way. In other words, the ShimStackWalks are valid until the next time
+// code:CordbThread::CleanupStack or code:CordbThread::MarkStackFramesDirty is called.
+//
+// ShimStackWalk and ICDThread have a 1:1 relationship. Only one ShimStackWalk will be created for any
+// given ICDThread. So if two threads in the debugger are walking the same thread in the debuggee, they
+// operate on the same ShimStackWalk. This is ok because ShimStackWalks walk the stack at creation time,
+// cache all the frames, and become read-only after creation.
+//
+// Refer to code:ShimProcess::ClearAllShimStackWalk to see how ShimStackWalks are cleared.
+//
+
+ShimStackWalk * ShimProcess::LookupOrCreateShimStackWalk(ICorDebugThread * pThread)
+{
+ ShimStackWalk * pSW = NULL;
+
+ {
+ // do the lookup under the Shim lock
+ RSLockHolder lockHolder(&m_ShimLock);
+ pSW = m_pShimStackWalkHashTable->Lookup(pThread);
+ }
+
+ if (pSW == NULL)
+ {
+ // create one if it's not found and add it to the hash table
+ NewHolder<ShimStackWalk> pNewSW(new ShimStackWalk(this, pThread));
+
+ {
+ // Do the lookup again under the Shim lock, and only add the new ShimStackWalk if no other thread
+ // has beaten us to it.
+ RSLockHolder lockHolder(&m_ShimLock);
+ pSW = m_pShimStackWalkHashTable->Lookup(pThread);
+ if (pSW == NULL)
+ {
+ m_pShimStackWalkHashTable->Add(pNewSW);
+ pSW = pNewSW;
+
+ // don't release the memory if all goes well
+ pNewSW.SuppressRelease();
+ }
+ else
+ {
+ // The NewHolder will automatically delete the ShimStackWalk when it goes out of scope.
+ }
+ }
+ }
+
+ return pSW;
+}
+
+// ----------------------------------------------------------------------------
+// ShimProcess::ClearAllShimStackWalk
+//
+// Description:
+// Remove and delete all the entries in the hash table of ShimStackWalks.
+//
+// Notes:
+// Refer to code:ShimProcess::LookupOrCreateShimStackWalk to see how ShimStackWalks are created.
+//
+
+void ShimProcess::ClearAllShimStackWalk()
+{
+ RSLockHolder lockHolder(&m_ShimLock);
+
+ // loop through all the entries in the hash table, remove them, and delete them
+ for (ShimStackWalkHashTable::Iterator pCurElem = m_pShimStackWalkHashTable->Begin(),
+ pEndElem = m_pShimStackWalkHashTable->End();
+ pCurElem != pEndElem;
+ pCurElem++)
+ {
+ ShimStackWalk * pSW = *pCurElem;
+ m_pShimStackWalkHashTable->Remove(pSW->GetThread());
+ delete pSW;
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// Called before shim dispatches an event.
+//
+// Arguments:
+// fRealCreateProcessEvent - true if the shim is about to dispatch a real create process event (as opposed
+// to one faked up by the shim itself)
+// Notes:
+// This may be called from within Filter, which means we may be on the win32-event-thread.
+// This is called on all callbacks from the VM.
+// This gives us a chance to queue fake-attach events. So call it before the Jit-attach
+// event has been queued.
+void ShimProcess::PreDispatchEvent(bool fRealCreateProcessEvent /*= false*/)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ // For emulating the V2 case, we need to do additional initialization before dispatching the callback to the user.
+ if (!m_fFirstManagedEvent)
+ {
+ // Remember that we're processing the first managed event so that we only call HandleFirstRCEvent() once
+ m_fFirstManagedEvent = true;
+
+ // This can fail with the incompatable version HR. The process has already been terminated if this
+ // is the case. This will dispatch an Error callback
+ // If this fails, the process is in an undefined state.
+ // @dbgtodo ipc-block: this will go away once we get rid
+ // of the IPC block.
+ m_pProcess->FinishInitializeIPCChannel(); // throws on error
+ }
+
+ {
+ // In jit-attach cases, the first event the shim gets is the event that triggered the jit-attach.
+ // Queue up the fake events now, and then once we return, our caller will queue the jit-attach event.
+ // In the jit-attach case, this is before a sync-complete has been sent (since the sync doesn't get sent
+ // until after the jit-attach event is sent).
+ QueueFakeAttachEventsIfNeeded(fRealCreateProcessEvent);
+ }
+
+ // Always request an sync (emulates V2 behavior). If LS is not sync-ready, it will ignore the request.
+ m_pProcess->RequestSyncAtEvent();
+
+
+}
+
+// ----------------------------------------------------------------------------
+// ShimProcess::GetCLRInstanceBaseAddress
+// Finds the base address of [core]clr.dll
+// Arguments: none
+// Return value: returns the base address of [core]clr.dll if possible or NULL otherwise
+//
+CORDB_ADDRESS ShimProcess::GetCLRInstanceBaseAddress()
+{
+ CORDB_ADDRESS baseAddress = CORDB_ADDRESS(NULL);
+ DWORD dwPid = m_pLiveDataTarget->GetPid();
+
+#if defined(FEATURE_CORESYSTEM)
+ // Debugger attaching to CoreCLR via CoreCLRCreateCordbObject should have already specified CLR module address.
+ // Code that help to find it now lives in dbgshim.
+#else
+ // get a "snapshot" of all modules in the target
+ HandleHolder hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPid);
+ MODULEENTRY32 moduleEntry = { 0 };
+
+ if (hSnapshot == INVALID_HANDLE_VALUE)
+ {
+ // we haven't got a loaded CLR yet
+ baseAddress = CORDB_ADDRESS(NULL);
+ }
+ else
+ {
+ // we need to loop through the modules until we find mscorwks.dll
+ moduleEntry.dwSize = sizeof(MODULEENTRY32);
+
+ if (!Module32First(hSnapshot, &moduleEntry))
+ {
+ baseAddress = CORDB_ADDRESS(NULL);
+ }
+ else
+ {
+
+ do
+ {
+ if (!_wcsicmp(moduleEntry.szModule, MAKEDLLNAME_W(MAIN_CLR_MODULE_NAME_W)))
+ {
+ // we found it, so save the base address
+ baseAddress = PTR_TO_CORDB_ADDRESS(moduleEntry.modBaseAddr);
+ }
+ } while (Module32Next(hSnapshot, &moduleEntry));
+ }
+ }
+#endif
+ return baseAddress;
+} // ShimProcess::GetCLRInstanceBaseAddress
+
+// ----------------------------------------------------------------------------
+// ShimProcess::FindLoadedCLR
+//
+// Description:
+// Look for any CLR loaded into the process. If found, return the instance ID for it.
+//
+// Arguments:
+// * pClrInstanceId - out parameter for the instance ID of the CLR
+//
+// Return Value:
+// Returns S_OK if a CLR was found, and stores its instance ID in pClrInstanceId.
+// Otherwise returns an error code.
+//
+// Notes:
+// If there are multiple CLRs loaded in the process, the one chosen for the returned
+// instance ID is unspecified.
+//
+HRESULT ShimProcess::FindLoadedCLR(CORDB_ADDRESS * pClrInstanceId)
+{
+ *pClrInstanceId = GetCLRInstanceBaseAddress();
+
+ if (*pClrInstanceId == 0)
+ {
+ return E_UNEXPECTED;
+ }
+
+ return S_OK;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Locates DAC by finding mscordac{wks|core} next to DBI
+//
+// Return Value:
+// Returns the module handle for DAC
+// Throws on errors.
+//
+
+HMODULE ShimProcess::GetDacModule()
+{
+ HModuleHolder hDacDll;
+ PathString wszAccessDllPath;
+
+#ifdef FEATURE_PAL
+ if (!PAL_GetPALDirectoryWrapper(wszAccessDllPath))
+ {
+ ThrowLastError();
+ }
+ PCWSTR eeFlavor = MAKEDLLNAME_W(W("mscordaccore"));
+#else
+ //
+ // Load the access DLL from the same directory as the the current CLR Debugging Services DLL.
+ //
+
+ if (!WszGetModuleFileName(GetModuleInst(), wszAccessDllPath))
+ {
+ ThrowLastError();
+ }
+
+ if (!SUCCEEDED(CopySystemDirectory(wszAccessDllPath, wszAccessDllPath)))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ // Dac Dll is named:
+ // mscordaccore.dll <-- coreclr
+ // mscordacwks.dll <-- desktop
+ PCWSTR eeFlavor =
+#if defined(FEATURE_MAIN_CLR_MODULE_USES_CORE_NAME)
+ W("mscordaccore.dll");
+#else
+ W("mscordacwks.dll");
+#endif
+
+#endif // FEATURE_PAL
+ wszAccessDllPath.Append(eeFlavor);
+
+ hDacDll.Assign(WszLoadLibrary(wszAccessDllPath));
+ if (!hDacDll)
+ {
+ DWORD dwLastError = GetLastError();
+ if (dwLastError == ERROR_MOD_NOT_FOUND)
+ {
+ // Give a more specific error in the case where we can't find the DAC dll.
+ ThrowHR(CORDBG_E_DEBUG_COMPONENT_MISSING);
+ }
+ else
+ {
+ ThrowWin32(dwLastError);
+ }
+ }
+ hDacDll.SuppressRelease();
+ return (HMODULE) hDacDll;
+}
+
+MachineInfo ShimProcess::GetMachineInfo()
+{
+ return m_machineInfo;
+}
+
+void ShimProcess::SetMarkAttachPendingEvent()
+{
+ SetEvent(m_markAttachPendingEvent);
+}
+
+void ShimProcess::SetTerminatingEvent()
+{
+ SetEvent(m_terminatingEvent);
+}
+
+RSLock * ShimProcess::GetShimLock()
+{
+ return &m_ShimLock;
+}
+
+
+bool ShimProcess::IsThreadSuspendedOrHijacked(ICorDebugThread * pThread)
+{
+ return m_pProcess->IsThreadSuspendedOrHijacked(pThread);
+}
diff --git a/src/debug/di/shimremotedatatarget.cpp b/src/debug/di/shimremotedatatarget.cpp
new file mode 100644
index 0000000000..f971b0d65a
--- /dev/null
+++ b/src/debug/di/shimremotedatatarget.cpp
@@ -0,0 +1,349 @@
+// 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.
+//*****************************************************************************
+
+//
+// File: ShimRemoteDataTarget.cpp
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "safewrap.h"
+
+#include "check.h"
+
+#include <limits.h>
+
+#include "shimpriv.h"
+#include "shimdatatarget.h"
+
+#include "dbgtransportsession.h"
+#include "dbgtransportmanager.h"
+
+
+class ShimRemoteDataTarget : public ShimDataTarget
+{
+public:
+ ShimRemoteDataTarget(DWORD processId, DbgTransportTarget * pProxy, DbgTransportSession * pTransport);
+
+ virtual ~ShimRemoteDataTarget();
+
+ virtual void Dispose();
+
+ //
+ // ICorDebugMutableDataTarget.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE GetPlatform(
+ CorDebugPlatform *pPlatform);
+
+ virtual HRESULT STDMETHODCALLTYPE ReadVirtual(
+ CORDB_ADDRESS address,
+ BYTE * pBuffer,
+ ULONG32 request,
+ ULONG32 *pcbRead);
+
+ virtual HRESULT STDMETHODCALLTYPE WriteVirtual(
+ CORDB_ADDRESS address,
+ const BYTE * pBuffer,
+ ULONG32 request);
+
+ virtual HRESULT STDMETHODCALLTYPE GetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextFlags,
+ ULONG32 contextSize,
+ BYTE * context);
+
+ virtual HRESULT STDMETHODCALLTYPE SetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextSize,
+ const BYTE * context);
+
+ virtual HRESULT STDMETHODCALLTYPE ContinueStatusChanged(
+ DWORD dwThreadId,
+ CORDB_CONTINUE_STATUS dwContinueStatus);
+
+ virtual HRESULT STDMETHODCALLTYPE VirtualUnwind(
+ DWORD threadId, ULONG32 contextSize, PBYTE context);
+
+private:
+ DbgTransportTarget * m_pProxy;
+ DbgTransportSession * m_pTransport;
+};
+
+
+// Helper macro to check for failure conditions at the start of data-target methods.
+#define ReturnFailureIfStateNotOk() \
+ if (m_hr != S_OK) \
+ { \
+ return m_hr; \
+ }
+
+//---------------------------------------------------------------------------------------
+//
+// This is the ctor for ShimRemoteDataTarget.
+//
+// Arguments:
+// processId - pid of live process on the remote machine
+// pProxy - connection to the debugger proxy
+// pTransport - connection to the debuggee process
+//
+
+ShimRemoteDataTarget::ShimRemoteDataTarget(DWORD processId,
+ DbgTransportTarget * pProxy,
+ DbgTransportSession * pTransport)
+{
+ m_ref = 0;
+
+ m_processId = processId;
+ m_pProxy = pProxy;
+ m_pTransport = pTransport;
+
+ m_hr = S_OK;
+
+ m_fpContinueStatusChanged = NULL;
+ m_pContinueStatusChangedUserData = NULL;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// dtor for ShimRemoteDataTarget
+//
+
+ShimRemoteDataTarget::~ShimRemoteDataTarget()
+{
+ Dispose();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Dispose all resources and neuter the object.
+//
+// Notes:
+// Release all resources (such as the connections to the debugger proxy and the debuggee process).
+// May be called multiple times.
+// All other non-trivial APIs (eg, not IUnknown) will fail after this.
+//
+
+void ShimRemoteDataTarget::Dispose()
+{
+ if (m_pTransport != NULL)
+ {
+ m_pProxy->ReleaseTransport(m_pTransport);
+ }
+
+ m_pTransport = NULL;
+ m_hr = CORDBG_E_OBJECT_NEUTERED;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Construction method for data-target
+//
+// Arguments:
+// machineInfo - (input) the IP address of the remote machine and the port number of the debugger proxy
+// processId - (input) live OS process ID to build a data-target for.
+// ppDataTarget - (output) new data-target instance. This gets addreffed.
+//
+// Return Value:
+// S_OK on success.
+//
+// Assumptions:
+// pid is for a process on the remote machine specified by the IP address in machineInfo
+// Caller must release *ppDataTarget.
+//
+
+HRESULT BuildPlatformSpecificDataTarget(MachineInfo machineInfo,
+ DWORD processId,
+ ShimDataTarget ** ppDataTarget)
+{
+ HandleHolder hDummy;
+ HRESULT hr = E_FAIL;
+
+ ShimRemoteDataTarget * pRemoteDataTarget = NULL;
+ DbgTransportTarget * pProxy = g_pDbgTransportTarget;
+ DbgTransportSession * pTransport = NULL;
+
+ hr = pProxy->GetTransportForProcess(processId, &pTransport, &hDummy);
+ if (FAILED(hr))
+ {
+ goto Label_Exit;
+ }
+
+ if (!pTransport->WaitForSessionToOpen(10000))
+ {
+ hr = CORDBG_E_TIMEOUT;
+ goto Label_Exit;
+ }
+
+ pRemoteDataTarget = new (nothrow) ShimRemoteDataTarget(processId, pProxy, pTransport);
+ if (pRemoteDataTarget == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ goto Label_Exit;
+ }
+
+ _ASSERTE(SUCCEEDED(hr));
+ *ppDataTarget = pRemoteDataTarget;
+ pRemoteDataTarget->AddRef(); // must addref out-parameters
+
+Label_Exit:
+ if (FAILED(hr))
+ {
+ if (pRemoteDataTarget != NULL)
+ {
+ // The ShimRemoteDataTarget has ownership of the proxy and the transport,
+ // so we don't need to clean them up here.
+ delete pRemoteDataTarget;
+ }
+ else
+ {
+ if (pTransport != NULL)
+ {
+ pProxy->ReleaseTransport(pTransport);
+ }
+ }
+ }
+
+ return hr;
+}
+
+// impl of interface method ICorDebugDataTarget::GetPlatform
+HRESULT STDMETHODCALLTYPE
+ShimRemoteDataTarget::GetPlatform(
+ CorDebugPlatform *pPlatform)
+{
+#ifdef FEATURE_PAL
+ #if defined(DBG_TARGET_X86)
+ *pPlatform = CORDB_PLATFORM_POSIX_X86;
+ #elif defined(DBG_TARGET_AMD64)
+ *pPlatform = CORDB_PLATFORM_POSIX_AMD64;
+ #elif defined(DBG_TARGET_ARM)
+ *pPlatform = CORDB_PLATFORM_POSIX_ARM;
+ #elif defined(DBG_TARGET_ARM64)
+ *pPlatform = CORDB_PLATFORM_POSIX_ARM64;
+ #else
+ #error Unknown Processor.
+ #endif
+#else
+ #if defined(DBG_TARGET_X86)
+ *pPlatform = CORDB_PLATFORM_WINDOWS_X86;
+ #elif defined(DBG_TARGET_AMD64)
+ *pPlatform = CORDB_PLATFORM_WINDOWS_AMD64;
+ #elif defined(DBG_TARGET_ARM)
+ *pPlatform = CORDB_PLATFORM_WINDOWS_ARM;
+ #elif defined(DBG_TARGET_ARM64)
+ *pPlatform = CORDB_PLATFORM_WINDOWS_ARM64;
+ #else
+ #error Unknown Processor.
+ #endif
+#endif
+
+ return S_OK;
+}
+
+// impl of interface method ICorDebugDataTarget::ReadVirtual
+HRESULT STDMETHODCALLTYPE
+ShimRemoteDataTarget::ReadVirtual(
+ CORDB_ADDRESS address,
+ PBYTE pBuffer,
+ ULONG32 cbRequestSize,
+ ULONG32 *pcbRead)
+{
+ ReturnFailureIfStateNotOk();
+
+ HRESULT hr = E_FAIL;
+ hr = m_pTransport->ReadMemory(reinterpret_cast<BYTE *>(CORDB_ADDRESS_TO_PTR(address)),
+ pBuffer,
+ cbRequestSize);
+ if (pcbRead != NULL)
+ {
+ *pcbRead = (SUCCEEDED(hr) ? cbRequestSize : 0);
+ }
+ return hr;
+}
+
+// impl of interface method ICorDebugMutableDataTarget::WriteVirtual
+HRESULT STDMETHODCALLTYPE
+ShimRemoteDataTarget::WriteVirtual(
+ CORDB_ADDRESS pAddress,
+ const BYTE * pBuffer,
+ ULONG32 cbRequestSize)
+{
+ ReturnFailureIfStateNotOk();
+
+ HRESULT hr = E_FAIL;
+ hr = m_pTransport->WriteMemory(reinterpret_cast<BYTE *>(CORDB_ADDRESS_TO_PTR(pAddress)),
+ const_cast<BYTE *>(pBuffer),
+ cbRequestSize);
+ return hr;
+}
+
+// impl of interface method ICorDebugMutableDataTarget::GetThreadContext
+HRESULT STDMETHODCALLTYPE
+ShimRemoteDataTarget::GetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextFlags,
+ ULONG32 contextSize,
+ BYTE * pContext)
+{
+ ReturnFailureIfStateNotOk();
+
+ // GetThreadContext() is currently not implemented in ShimRemoteDataTarget, which is used with our pipe transport
+ // (FEATURE_DBGIPC_TRANSPORT_DI). Pipe transport is used on POSIX system, but occasionally we can turn it on for Windows for testing,
+ // and then we'd like to have same behavior as on POSIX system (zero context).
+ //
+ // We don't have a good way to implement GetThreadContext() in ShimRemoteDataTarget yet, because we have no way to convert a thread ID to a
+ // thread handle. The function to do the conversion is OpenThread(), which is not implemented in PAL. Even if we had a handle, PAL implementation
+ // of GetThreadContext() is very limited and doesn't work when we're not attached with ptrace.
+ // Instead, we just zero out the seed CONTEXT for the stackwalk. This tells the stackwalker to
+ // start the stackwalk with the first explicit frame. This won't work when we do native debugging,
+ // but that won't happen on the POSIX systems since they don't support native debugging.
+ ZeroMemory(pContext, contextSize);
+ return E_NOTIMPL;
+}
+
+// impl of interface method ICorDebugMutableDataTarget::SetThreadContext
+HRESULT STDMETHODCALLTYPE
+ShimRemoteDataTarget::SetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextSize,
+ const BYTE * pContext)
+{
+ ReturnFailureIfStateNotOk();
+
+ // ICorDebugDataTarget::GetThreadContext() and ICorDebugDataTarget::SetThreadContext() are currently only
+ // required for interop-debugging and inspection of floating point registers, both of which are not
+ // implemented on Mac.
+ _ASSERTE(!"The remote data target doesn't know how to set a thread's CONTEXT.");
+ return E_NOTIMPL;
+}
+
+// Public implementation of ICorDebugMutableDataTarget::ContinueStatusChanged
+HRESULT STDMETHODCALLTYPE
+ShimRemoteDataTarget::ContinueStatusChanged(
+ DWORD dwThreadId,
+ CORDB_CONTINUE_STATUS dwContinueStatus)
+{
+ ReturnFailureIfStateNotOk();
+
+ _ASSERTE(!"ShimRemoteDataTarget::ContinueStatusChanged() is called unexpectedly");
+ if (m_fpContinueStatusChanged != NULL)
+ {
+ return m_fpContinueStatusChanged(m_pContinueStatusChangedUserData, dwThreadId, dwContinueStatus);
+ }
+ return E_NOTIMPL;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Unwind the stack to the next frame.
+//
+// Return Value:
+// context filled in with the next frame
+//
+HRESULT STDMETHODCALLTYPE
+ShimRemoteDataTarget::VirtualUnwind(DWORD threadId, ULONG32 contextSize, PBYTE context)
+{
+ return m_pTransport->VirtualUnwind(threadId, contextSize, context);
+}
diff --git a/src/debug/di/shimstackwalk.cpp b/src/debug/di/shimstackwalk.cpp
new file mode 100644
index 0000000000..9be1ea1a78
--- /dev/null
+++ b/src/debug/di/shimstackwalk.cpp
@@ -0,0 +1,2264 @@
+// 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.
+//
+// ShimStackWalk.cpp
+//
+
+//
+// This file contains the implementation of the Arrowhead stackwalking shim. This shim builds on top of
+// the public Arrowhead ICD stackwalking API, and it is intended to be backward-compatible with the existing
+// debuggers using the V2.0 ICD API.
+//
+// ======================================================================================
+
+#include "stdafx.h"
+#include "primitives.h"
+
+#if defined(_TARGET_X86_)
+static const ULONG32 REGISTER_X86_MAX = REGISTER_X86_FPSTACK_7 + 1;
+static const ULONG32 MAX_MASK_COUNT = (REGISTER_X86_MAX + 7) >> 3;
+#elif defined(_TARGET_AMD64_)
+static const ULONG32 REGISTER_AMD64_MAX = REGISTER_AMD64_XMM15 + 1;
+static const ULONG32 MAX_MASK_COUNT = (REGISTER_AMD64_MAX + 7) >> 3;
+#endif
+
+ShimStackWalk::ShimStackWalk(ShimProcess * pProcess, ICorDebugThread * pThread)
+ : m_pChainEnumList(NULL),
+ m_pFrameEnumList(NULL)
+{
+ // The following assignments increment the ref count.
+ m_pProcess.Assign(pProcess);
+ m_pThread.Assign(pThread);
+
+ Populate();
+}
+
+ShimStackWalk::~ShimStackWalk()
+{
+ Clear();
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::Clear
+//
+// Description:
+// Clear all the memory used by this ShimStackWalk, including the array of frames, the array of chains,
+// the linked list of ShimChainEnums, and the linked list of ShimFrameEnums.
+//
+
+void ShimStackWalk::Clear()
+{
+ // call Release() on each of the ShimChains
+ for (int i = 0; i < m_stackChains.Count(); i++)
+ {
+ (*m_stackChains.Get(i))->Neuter();
+ (*m_stackChains.Get(i))->Release();
+ }
+ m_stackChains.Clear();
+
+ // call Release() on each of the ICDFrames
+ for (int i = 0; i < m_stackFrames.Count(); i++)
+ {
+ (*m_stackFrames.Get(i))->Release();
+ }
+ m_stackFrames.Clear();
+
+ // call Release() on each of the ShimChainEnums
+ while (m_pChainEnumList != NULL)
+ {
+ ShimChainEnum * pCur = m_pChainEnumList;
+ m_pChainEnumList = m_pChainEnumList->GetNext();
+ pCur->Neuter();
+ pCur->Release();
+ }
+
+ // call Release() on each of the ShimFrameEnums
+ while (m_pFrameEnumList != NULL)
+ {
+ ShimFrameEnum * pCur = m_pFrameEnumList;
+ m_pFrameEnumList = m_pFrameEnumList->GetNext();
+ pCur->Neuter();
+ pCur->Release();
+ }
+
+ // release the references
+ m_pProcess.Clear();
+ m_pThread.Clear();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Helper used by the stackwalker to determine whether a given UM chain should be tracked
+// during the stackwalk for eventual transmission to the debugger. This function is the
+// V4 equivalent of Whidbey's code:ShouldSendUMLeafChain (which ran on the LS, from
+// Debug\EE\frameinfo.cpp).
+//
+// Note that code:ShouldSendUMLeafChain still exists today (to facilitate some in-process
+// debugging stackwalks that are still necessary). So consult the comments in
+// code:ShouldSendUMLeafChain for a more thorough discussion of why we do the checks we
+// do to decide whether to track the chain.
+//
+// Arguments:
+// pswInfo - StackWalkInfo representing the frame in question
+//
+// Return Value:
+// nonzero iff the chain should be tracked
+//
+
+BOOL ShimStackWalk::ShouldTrackUMChain(StackWalkInfo * pswInfo)
+{
+ _ASSERTE (pswInfo != NULL);
+
+ // Always track chains for non-leaf UM frames
+ if (!pswInfo->IsLeafFrame())
+ return TRUE;
+
+ // Sometimes we want to track leaf UM chains, and sometimes we don't. Check all the
+ // reasons not to track the chain, and return FALSE if any of them are hit.
+
+ CorDebugUserState threadUserState;
+ HRESULT hr = m_pThread->GetUserState(&threadUserState);
+ IfFailThrow(hr);
+
+ // ShouldSendUMLeafChain checked IsInWaitSleepJoin which is just USER_WAIT_SLEEP_JOIN
+ if ((threadUserState & USER_WAIT_SLEEP_JOIN) != 0)
+ return FALSE;
+
+ // This check is the same as Thread::IsUnstarted() from ShouldSendUMLeafChain
+ if ((threadUserState & USER_UNSTARTED) != 0)
+ return FALSE;
+
+ // This check is the same as Thread::IsDead() from ShouldSendUMLeafChain
+ if ((threadUserState & USER_STOPPED) != 0)
+ return FALSE;
+
+ // #DacShimSwWorkAround
+ //
+ // This part cannot be determined using DBI alone. We must call through to the DAC
+ // because we have no other way to get at TS_Hijacked & TS_SyncSuspended. When the
+ // rearchitecture is complete, this DAC call should be able to go away, and we
+ // should be able to use DBI for all the info we need.
+ //
+ // One might think one could avoid the DAC for TS_SyncSuspended by just checking
+ // USER_SUSPENDED, but that won't work. Although USER_SUSPENDED will be returned some
+ // of the time when TS_SyncSuspended is set, that will not be the case when the
+ // debugger must suspend the thread. Example: if the given thread is in the middle of
+ // throwing a managed exception when the debugger breaks in, then TS_SyncSuspended
+ // will be set due to the debugger's breaking, but USER_SUSPENDED will not be set, so
+ // we'll think the UM chain should be tracked, resulting in a stack that differs from
+ // Whidbey.
+ if (m_pProcess->IsThreadSuspendedOrHijacked(m_pThread))
+ return FALSE;
+
+ return TRUE;
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::Populate
+//
+// Description:
+// Walk the entire stack and populate the arrays of stack frames and stack chains.
+//
+
+void ShimStackWalk::Populate()
+{
+ HRESULT hr = S_OK;
+
+ // query for the ICDThread3 interface
+ RSExtSmartPtr<ICorDebugThread3> pThread3;
+ hr = m_pThread->QueryInterface(IID_ICorDebugThread3, reinterpret_cast<void **>(&pThread3));
+ IfFailThrow(hr);
+
+ // create the ICDStackWalk
+ RSExtSmartPtr<ICorDebugStackWalk> pSW;
+ hr = pThread3->CreateStackWalk(&pSW);
+ IfFailThrow(hr);
+
+ // structs used to store information during the stackwalk
+ ChainInfo chainInfo;
+ StackWalkInfo swInfo;
+
+ // use the ICDStackWalk to retrieve the internal frames
+ hr = pThread3->GetActiveInternalFrames(0, &(swInfo.m_cInternalFrames), NULL);
+ IfFailThrow(hr);
+
+ // allocate memory for the internal frames
+ if (swInfo.m_cInternalFrames > 0)
+ {
+ // allocate memory for the array of RSExtSmartPtrs
+ swInfo.m_ppInternalFrame2.AllocOrThrow(swInfo.m_cInternalFrames);
+
+ // create a temporary buffer of raw ICDInternalFrame2 to pass to the ICD API
+ NewArrayHolder<ICorDebugInternalFrame2 *> pTmpArray(new ICorDebugInternalFrame2* [swInfo.m_cInternalFrames]);
+ hr = pThread3->GetActiveInternalFrames(swInfo.m_cInternalFrames,
+ &(swInfo.m_cInternalFrames),
+ pTmpArray);
+ IfFailThrow(hr);
+
+ // transfer the raw array to the RSExtSmartPtr array
+ for (UINT32 i = 0; i < swInfo.m_cInternalFrames; i++)
+ {
+ // Assign() increments the ref count
+ swInfo.m_ppInternalFrame2.Assign(i, pTmpArray[i]);
+ pTmpArray[i]->Release();
+ }
+ pTmpArray.Clear();
+ }
+
+ //
+ // This is basically how the loop works:
+ // 1) Determine whether we should process the next internal frame or the next stack frame.
+ // 2) If we are skipping frames, the only thing we need to do is to check whether we have reached
+ // the parent frame.
+ // 3) Process CHAIN_ENTER_MANAGED/CHAIN_ENTER_UNMANAGED chains
+ // 4) Append the frame to the cache.
+ // 5) Handle other types of chains.
+ // 6) Advance to the next frame.
+ // 7) Check if we should exit the loop.
+ //
+ while (true)
+ {
+ // reset variables used in the loop
+ swInfo.ResetForNextFrame();
+
+ // retrieve the next stack frame if it's available
+ RSExtSmartPtr<ICorDebugFrame> pFrame;
+ if (!swInfo.ExhaustedAllStackFrames())
+ {
+ hr = pSW->GetFrame(&pFrame);
+ IfFailThrow(hr);
+ }
+
+ // This next clause processes the current frame, regardless of whether it's an internal frame or a
+ // stack frame. Normally, "pFrame != NULL" is a good enough check, except for the case where we
+ // have exhausted all the stack frames but still have internal frames to process.
+ if ((pFrame != NULL) || swInfo.ExhaustedAllStackFrames())
+ {
+ // prefetch the internal frame type
+ if (!swInfo.ExhaustedAllInternalFrames())
+ {
+ swInfo.m_internalFrameType = GetInternalFrameType(swInfo.GetCurrentInternalFrame());
+ }
+
+ // We cannot have exhausted both the stack frames and the internal frames when we get to here.
+ // We should have exited the loop if we have exhausted both types of frames.
+ if (swInfo.ExhaustedAllStackFrames())
+ {
+ swInfo.m_fProcessingInternalFrame = true;
+ }
+ else if (swInfo.ExhaustedAllInternalFrames())
+ {
+ swInfo.m_fProcessingInternalFrame = false;
+ }
+ else
+ {
+ // check whether we should process the next internal frame or the next stack frame
+ swInfo.m_fProcessingInternalFrame = (CheckInternalFrame(pFrame, &swInfo, pThread3, pSW) == TRUE);
+ }
+
+ // The only thing we do while we are skipping frames is to check whether we have reached the
+ // parent frame, and we only need to check if we are processing a stack frame.
+ if (swInfo.IsSkippingFrame())
+ {
+ if (!swInfo.m_fProcessingInternalFrame)
+ {
+ // Check whether we have reached the parent frame yet.
+ RSExtSmartPtr<ICorDebugNativeFrame2> pNFrame2;
+ hr = pFrame->QueryInterface(IID_ICorDebugNativeFrame2, reinterpret_cast<void **>(&pNFrame2));
+ IfFailThrow(hr);
+
+ BOOL fIsParent = FALSE;
+ hr = swInfo.m_pChildFrame->IsMatchingParentFrame(pNFrame2, &fIsParent);
+ IfFailThrow(hr);
+
+ if (fIsParent)
+ {
+ swInfo.m_pChildFrame.Clear();
+ }
+ }
+ }
+ else if(swInfo.m_fProcessingInternalFrame && !chainInfo.m_fLeafNativeContextIsValid &&
+ swInfo.m_internalFrameType == STUBFRAME_M2U)
+ {
+ // Filter this frame out entirely
+ // This occurs because InlinedCallFrames get placed inside leaf managed methods.
+ // The frame gets erected before the native call is made and destroyed afterwards
+ // but there is a window in which the debugger could stop where the internal frame
+ // is live but we are executing jitted code. See Dev10 issue 743230
+ // It is quite possible other frames have this same pattern if the debugger were
+ // stopped right at the spot where they are being constructed. And that is
+ // just a facet of the general data structure consistency problems the debugger
+ // will always face
+ }
+ else
+ {
+ // Don't add any frame just yet. We need to deal with any unmanaged chain
+ // we are tracking first.
+
+ // track the current enter-unmanaged chain and/or enter-managed chain
+ TrackUMChain(&chainInfo, &swInfo);
+
+ if (swInfo.m_fProcessingInternalFrame)
+ {
+ // Check if this is a leaf internal frame. If so, check its frame type.
+ // In V2, code:DebuggerWalkStackProc doesn't expose chains derived from leaf internal
+ // frames of type TYPE_INTERNAL. However, V2 still exposes leaf M2U and U2M internal
+ // frames.
+ if (swInfo.IsLeafFrame())
+ {
+ if (swInfo.m_internalFrameType == STUBFRAME_EXCEPTION)
+ {
+ // We need to make sure we don't accidentally send an enter-unmanaged chain
+ // because of the leaf STUBFRAME_EXCEPTION.
+ chainInfo.CancelUMChain();
+ swInfo.m_fSkipChain = true;
+ }
+ }
+
+ _ASSERTE(!swInfo.IsSkippingFrame());
+ if (ConvertInternalFrameToDynamicMethod(&swInfo))
+ {
+ // We have just converted a STUBFRAME_JIT_COMPILATION to a
+ // STUBFRAME_LIGHTWEIGHT_FUNCTION (or to NULL). Since the latter frame type doesn't
+ // map to any chain in V2, let's skip the chain handling.
+ swInfo.m_fSkipChain = true;
+
+ // We may have converted to NULL, which means that we are dealing with an IL stub
+ // and we shouldn't expose it.
+ if (swInfo.GetCurrentInternalFrame() != NULL)
+ {
+ AppendFrame(swInfo.GetCurrentInternalFrame(), &swInfo);
+ }
+ }
+ else
+ {
+ // One more check before we append the internal frame: make sure the frame type is a
+ // V2 frame type first.
+ if (!IsV3FrameType(swInfo.m_internalFrameType))
+ {
+ AppendFrame(swInfo.GetCurrentInternalFrame(), &swInfo);
+ }
+ }
+ }
+ else
+ {
+ if (!chainInfo.m_fNeedEnterManagedChain)
+ {
+ // If we have hit any managed stack frame, then we may need to send
+ // an enter-managed chain later. Save the CONTEXT now.
+ SaveChainContext(pSW, &chainInfo, &(chainInfo.m_leafManagedContext));
+ chainInfo.m_fNeedEnterManagedChain = true;
+ }
+
+ // We are processing a stack frame.
+ // Only append the frame if it's NOT a dynamic method.
+ _ASSERTE(!swInfo.IsSkippingFrame());
+ if (ConvertStackFrameToDynamicMethod(pFrame, &swInfo))
+ {
+ // We have converted a ICDNativeFrame for an IL method without metadata to an
+ // ICDInternalFrame of type STUBFRAME_LIGHTWEIGHT_FUNCTION (or to NULL).
+ // Fortunately, we don't have to update any state here
+ // (e.g. m_fProcessingInternalFrame) because the rest of the loop doesn't care.
+ if (swInfo.GetCurrentInternalFrame() != NULL)
+ {
+ AppendFrame(swInfo.GetCurrentInternalFrame(), &swInfo);
+ }
+ }
+ else
+ {
+ AppendFrame(pFrame, &swInfo);
+ }
+
+ // If we have just processed a child frame, we should start skipping.
+ // Get the ICDNativeFrame2 pointer to check.
+ RSExtSmartPtr<ICorDebugNativeFrame2> pNFrame2;
+ hr = pFrame->QueryInterface(IID_ICorDebugNativeFrame2, reinterpret_cast<void **>(&pNFrame2));
+ IfFailThrow(hr);
+
+ if (pNFrame2 != NULL)
+ {
+ BOOL fIsChild = FALSE;
+ hr = pNFrame2->IsChild(&fIsChild);
+ IfFailThrow(hr);
+
+ if (fIsChild)
+ {
+ swInfo.m_pChildFrame.Assign(pNFrame2);
+ }
+ }
+ }
+ }
+ } // process the current frame (managed stack frame or internal frame)
+
+ // We can take care of other types of chains here, but only do so if we are not currently skipping
+ // child frames.
+ if (!swInfo.IsSkippingFrame())
+ {
+ if ((pFrame == NULL) &&
+ !swInfo.ExhaustedAllStackFrames())
+ {
+ // We are here because we are processing a native marker stack frame, not because
+ // we have exhausted all the stack frames.
+
+ // We need to save the CONTEXT to start tracking an unmanaged chain.
+ SaveChainContext(pSW, &chainInfo, &(chainInfo.m_leafNativeContext));
+ chainInfo.m_fLeafNativeContextIsValid = true;
+
+ // begin tracking UM chain if we're supposed to
+ if (ShouldTrackUMChain(&swInfo))
+ {
+ chainInfo.m_reason = CHAIN_ENTER_UNMANAGED;
+ }
+ }
+ else
+ {
+ // handle other types of chains
+ if (swInfo.m_fProcessingInternalFrame)
+ {
+ if (!swInfo.m_fSkipChain)
+ {
+ BOOL fNewChain = FALSE;
+
+ switch (swInfo.m_internalFrameType)
+ {
+ case STUBFRAME_M2U: // fall through
+ case STUBFRAME_U2M: // fall through
+ // These frame types are tracked specially.
+ break;
+
+ case STUBFRAME_APPDOMAIN_TRANSITION: // fall through
+ case STUBFRAME_LIGHTWEIGHT_FUNCTION: // fall through
+ case STUBFRAME_INTERNALCALL:
+ // These frame types don't correspond to chains.
+ break;
+
+ case STUBFRAME_FUNC_EVAL:
+ chainInfo.m_reason = CHAIN_FUNC_EVAL;
+ fNewChain = TRUE;
+ break;
+
+ case STUBFRAME_CLASS_INIT: // fall through
+ case STUBFRAME_JIT_COMPILATION:
+ // In Whidbey, these two frame types are the same.
+ chainInfo.m_reason = CHAIN_CLASS_INIT;
+ fNewChain = TRUE;
+ break;
+
+ case STUBFRAME_EXCEPTION:
+ chainInfo.m_reason = CHAIN_EXCEPTION_FILTER;
+ fNewChain = TRUE;
+ break;
+
+ case STUBFRAME_SECURITY:
+ chainInfo.m_reason = CHAIN_SECURITY;
+ fNewChain = TRUE;
+ break;
+
+ default:
+ // We can only reach this case if we have converted an IL stub to NULL.
+ _ASSERTE(swInfo.HasConvertedFrame());
+ break;
+ }
+
+ if (fNewChain)
+ {
+ chainInfo.m_rootFP = GetFramePointerForChain(swInfo.GetCurrentInternalFrame());
+ AppendChain(&chainInfo, &swInfo);
+ }
+ }
+ } // chain handling for an internl frame
+ } // chain handling for a managed stack frame or an internal frame
+ } // chain handling
+
+ // Reset the flag for leaf frame if we have processed any frame. The only case where we should
+ // not reset this flag is if the ICDStackWalk is stopped at a native stack frame on creation.
+ if (swInfo.IsLeafFrame())
+ {
+ if (swInfo.m_fProcessingInternalFrame || (pFrame != NULL))
+ {
+ swInfo.m_fLeafFrame = false;
+ }
+ }
+
+ // advance to the next frame
+ if (swInfo.m_fProcessingInternalFrame)
+ {
+ swInfo.m_curInternalFrame += 1;
+ }
+ else
+ {
+ hr = pSW->Next();
+ IfFailThrow(hr);
+
+ // check for the end of stack condition
+ if (hr == CORDBG_S_AT_END_OF_STACK)
+ {
+ // By the time we finish the stackwalk, all child frames should have been matched with their
+ // respective parent frames.
+ _ASSERTE(!swInfo.IsSkippingFrame());
+
+ swInfo.m_fExhaustedAllStackFrames = true;
+ }
+ }
+
+ // Break out of the loop if we have exhausted all the frames.
+ if (swInfo.ExhaustedAllFrames())
+ {
+ break;
+ }
+ }
+
+ // top off the stackwalk with a thread start chain
+ chainInfo.m_reason = CHAIN_THREAD_START;
+ chainInfo.m_rootFP = ROOT_MOST_FRAME; // In Whidbey, we actually use the cached stack base value.
+ AppendChain(&chainInfo, &swInfo);
+}
+
+// the caller is responsible for addref and release
+ICorDebugThread * ShimStackWalk::GetThread()
+{
+ return m_pThread;
+}
+
+// the caller is responsible for addref and release
+ShimChain * ShimStackWalk::GetChain(UINT32 index)
+{
+ if (index >= (UINT32)(m_stackChains.Count()))
+ {
+ return NULL;
+ }
+ else
+ {
+ return *(m_stackChains.Get((int)index));
+ }
+}
+
+// the caller is responsible for addref and release
+ICorDebugFrame * ShimStackWalk::GetFrame(UINT32 index)
+{
+ if (index >= (UINT32)(m_stackFrames.Count()))
+ {
+ return NULL;
+ }
+ else
+ {
+ return *(m_stackFrames.Get((int)index));
+ }
+}
+
+ULONG ShimStackWalk::GetChainCount()
+{
+ return m_stackChains.Count();
+}
+
+ULONG ShimStackWalk::GetFrameCount()
+{
+ return m_stackFrames.Count();
+}
+
+RSLock * ShimStackWalk::GetShimLock()
+{
+ return m_pProcess->GetShimLock();
+}
+
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::AddChainEnum
+//
+// Description:
+// Add the specified ShimChainEnum to the head of the linked list of ShimChainEnums on the ShimStackWalk.
+//
+// Arguments:
+// * pChainEnum - the ShimChainEnum to be added
+//
+
+void ShimStackWalk::AddChainEnum(ShimChainEnum * pChainEnum)
+{
+ pChainEnum->SetNext(m_pChainEnumList);
+ if (m_pChainEnumList != NULL)
+ {
+ m_pChainEnumList->Release();
+ }
+
+ m_pChainEnumList = pChainEnum;
+ if (m_pChainEnumList != NULL)
+ {
+ m_pChainEnumList->AddRef();
+ }
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::AddFrameEnum
+//
+// Description:
+// Add the specified ShimFrameEnum to the head of the linked list of ShimFrameEnums on the ShimStackWalk.
+//
+// Arguments:
+// * pFrameEnum - the ShimFrameEnum to be added
+//
+
+void ShimStackWalk::AddFrameEnum(ShimFrameEnum * pFrameEnum)
+{
+ pFrameEnum->SetNext(m_pFrameEnumList);
+ if (m_pFrameEnumList != NULL)
+ {
+ m_pFrameEnumList->Release();
+ }
+
+ m_pFrameEnumList = pFrameEnum;
+ if (m_pFrameEnumList != NULL)
+ {
+ m_pFrameEnumList->AddRef();
+ }
+}
+
+// Return the ICDThread associated with the current ShimStackWalk as a key for ShimStackWalkHashTableTraits.
+ICorDebugThread * ShimStackWalk::GetKey()
+{
+ return m_pThread;
+}
+
+// Hash a given ICDThread, which is used as the key for ShimStackWalkHashTableTraits.
+//static
+UINT32 ShimStackWalk::Hash(ICorDebugThread * pThread)
+{
+ // just return the pointer value
+ return (UINT32)(size_t)pThread;
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::IsLeafFrame
+//
+// Description:
+// Check whether the specified frame is the leaf frame.
+//
+// Arguments:
+// * pFrame - frame to be checked
+//
+// Return Value:
+// Return TRUE if the specified frame is the leaf frame.
+// Return FALSE otherwise.
+//
+// Notes:
+// * The definition of the leaf frame in V2 is the frame at the leaf of the leaf chain.
+//
+
+BOOL ShimStackWalk::IsLeafFrame(ICorDebugFrame * pFrame)
+{
+ // check if we have any chain
+ if (GetChainCount() > 0)
+ {
+ // check if the leaf chain has any frame
+ if (GetChain(0)->GetLastFrameIndex() > 0)
+ {
+ return IsSameFrame(pFrame, GetFrame(0));
+ }
+ }
+ return FALSE;
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::IsSameFrame
+//
+// Description:
+// Given two ICDFrames, check if they refer to the same frame.
+// This is much more than a pointer comparison. This function actually checks the frame address,
+// the stack pointer, etc. to make sure if the frames are the same.
+//
+// Arguments:
+// * pLeft - frame to be compared
+// * pRight - frame to be compared
+//
+// Return Value:
+// Return TRUE if the two ICDFrames represent the same frame.
+//
+
+BOOL ShimStackWalk::IsSameFrame(ICorDebugFrame * pLeft, ICorDebugFrame * pRight)
+{
+ HRESULT hr = E_FAIL;
+
+ // Quick check #1: If the pointers are the same then the two frames are the same (duh!).
+ if (pLeft == pRight)
+ {
+ return TRUE;
+ }
+
+ RSExtSmartPtr<ICorDebugNativeFrame> pLeftNativeFrame;
+ hr = pLeft->QueryInterface(IID_ICorDebugNativeFrame, reinterpret_cast<void **>(&pLeftNativeFrame));
+
+ if (SUCCEEDED(hr))
+ {
+ // The left frame is a stack frame.
+ RSExtSmartPtr<ICorDebugNativeFrame> pRightNativeFrame;
+ hr = pRight->QueryInterface(IID_ICorDebugNativeFrame, reinterpret_cast<void **>(&pRightNativeFrame));
+
+ if (FAILED(hr))
+ {
+ // The right frame is NOT a stack frame.
+ return FALSE;
+ }
+ else
+ {
+ // Quick check #2: If the IPs are different then the two frames are not the same (duh!).
+ ULONG32 leftOffset;
+ ULONG32 rightOffset;
+
+ hr = pLeftNativeFrame->GetIP(&leftOffset);
+ IfFailThrow(hr);
+
+ hr = pRightNativeFrame->GetIP(&rightOffset);
+ IfFailThrow(hr);
+
+ if (leftOffset != rightOffset)
+ {
+ return FALSE;
+ }
+
+ // real check
+ CORDB_ADDRESS leftStart;
+ CORDB_ADDRESS leftEnd;
+ CORDB_ADDRESS rightStart;
+ CORDB_ADDRESS rightEnd;
+
+ hr = pLeftNativeFrame->GetStackRange(&leftStart, &leftEnd);
+ IfFailThrow(hr);
+
+ hr = pRightNativeFrame->GetStackRange(&rightStart, &rightEnd);
+ IfFailThrow(hr);
+
+ return ((leftStart == rightStart) && (leftEnd == rightEnd));
+ }
+ }
+ else
+ {
+ RSExtSmartPtr<ICorDebugInternalFrame2> pLeftInternalFrame2;
+ hr = pLeft->QueryInterface(IID_ICorDebugInternalFrame2,
+ reinterpret_cast<void **>(&pLeftInternalFrame2));
+
+ if (SUCCEEDED(hr))
+ {
+ // The left frame is an internal frame.
+ RSExtSmartPtr<ICorDebugInternalFrame2> pRightInternalFrame2;
+ hr = pRight->QueryInterface(IID_ICorDebugInternalFrame2,
+ reinterpret_cast<void **>(&pRightInternalFrame2));
+
+ if (FAILED(hr))
+ {
+ return FALSE;
+ }
+ else
+ {
+ // The right frame is also an internal frame.
+
+ // Check the frame address.
+ CORDB_ADDRESS leftFrameAddr;
+ CORDB_ADDRESS rightFrameAddr;
+
+ hr = pLeftInternalFrame2->GetAddress(&leftFrameAddr);
+ IfFailThrow(hr);
+
+ hr = pRightInternalFrame2->GetAddress(&rightFrameAddr);
+ IfFailThrow(hr);
+
+ return (leftFrameAddr == rightFrameAddr);
+ }
+ }
+
+ return FALSE;
+ }
+}
+
+// This is the shim implementation of ICDThread::EnumerateChains().
+void ShimStackWalk::EnumerateChains(ICorDebugChainEnum ** ppChainEnum)
+{
+ NewHolder<ShimChainEnum> pChainEnum(new ShimChainEnum(this, GetShimLock()));
+
+ *ppChainEnum = pChainEnum;
+ (*ppChainEnum)->AddRef();
+ AddChainEnum(pChainEnum);
+
+ pChainEnum.SuppressRelease();
+}
+
+// This is the shim implementation of ICDThread::GetActiveChain().
+void ShimStackWalk::GetActiveChain(ICorDebugChain ** ppChain)
+{
+ if (GetChainCount() == 0)
+ {
+ *ppChain = NULL;
+ }
+ else
+ {
+ *ppChain = static_cast<ICorDebugChain *>(GetChain(0));
+ (*ppChain)->AddRef();
+ }
+}
+
+// This is the shim implementation of ICDThread::GetActiveFrame().
+void ShimStackWalk::GetActiveFrame(ICorDebugFrame ** ppFrame)
+{
+ //
+ // Make sure two things:
+ // 1) We have at least one frame.
+ // 2) The leaf frame is in the leaf chain, i.e. the leaf chain is not empty.
+ //
+ if ((GetFrameCount() == 0) ||
+ (GetChain(0)->GetLastFrameIndex() == 0))
+ {
+ *ppFrame = NULL;
+ }
+ else
+ {
+ *ppFrame = GetFrame(0);
+ (*ppFrame)->AddRef();
+ }
+}
+
+// This is the shim implementation of ICDThread::GetRegisterSet().
+void ShimStackWalk::GetActiveRegisterSet(ICorDebugRegisterSet ** ppRegisterSet)
+{
+ _ASSERTE(GetChainCount() != 0);
+ _ASSERTE(GetChain(0) != NULL);
+
+ // Return the register set of the leaf chain.
+ HRESULT hr = GetChain(0)->GetRegisterSet(ppRegisterSet);
+ IfFailThrow(hr);
+}
+
+// This is the shim implementation of ICDFrame::GetChain().
+void ShimStackWalk::GetChainForFrame(ICorDebugFrame * pFrame, ICorDebugChain ** ppChain)
+{
+ CORDB_ADDRESS frameStart;
+ CORDB_ADDRESS frameEnd;
+ IfFailThrow(pFrame->GetStackRange(&frameStart, &frameEnd));
+
+ for (UINT32 i = 0; i < GetChainCount(); i++)
+ {
+ ShimChain * pCurChain = GetChain(i);
+
+ CORDB_ADDRESS chainStart;
+ CORDB_ADDRESS chainEnd;
+ IfFailThrow(pCurChain->GetStackRange(&chainStart, &chainEnd));
+
+ if ((chainStart <= frameStart) && (frameEnd <= chainEnd))
+ {
+ // We need to check the next chain as well since some chains overlap at the boundary.
+ // If the current chain is the last one, no additional checking is required.
+ if (i < (GetChainCount() - 1))
+ {
+ ShimChain * pNextChain = GetChain(i + 1);
+
+ CORDB_ADDRESS nextChainStart;
+ CORDB_ADDRESS nextChainEnd;
+ IfFailThrow(pNextChain->GetStackRange(&nextChainStart, &nextChainEnd));
+
+ if ((nextChainStart <= frameStart) && (frameEnd <= nextChainEnd))
+ {
+ // The frame lies in the stack ranges of two chains. This can only happn at the boundary.
+ if (pCurChain->GetFirstFrameIndex() == pCurChain->GetLastFrameIndex())
+ {
+ // Make sure the next chain is not empty.
+ _ASSERTE(pNextChain->GetFirstFrameIndex() != pNextChain->GetLastFrameIndex());
+
+ // The current chain is empty, so the chain we want is the next one.
+ pCurChain = pNextChain;
+ }
+ // If the next chain is empty, then we'll just return the current chain and no additional
+ // work is needed. If the next chain is not empty, then we have more checking to do.
+ else if (pNextChain->GetFirstFrameIndex() != pNextChain->GetLastFrameIndex())
+ {
+ // Both chains are non-empty.
+ if (IsSameFrame(GetFrame(pNextChain->GetFirstFrameIndex()), pFrame))
+ {
+ // The same frame cannot be in both chains.
+ _ASSERTE(!IsSameFrame(GetFrame(pCurChain->GetLastFrameIndex() - 1), pFrame));
+ pCurChain = pNextChain;
+ }
+ else
+ {
+ _ASSERTE(IsSameFrame(GetFrame(pCurChain->GetLastFrameIndex() - 1), pFrame));
+ }
+ }
+ }
+ }
+
+ *ppChain = static_cast<ICorDebugChain *>(pCurChain);
+ (*ppChain)->AddRef();
+ return;
+ }
+ }
+}
+
+// This is the shim implementation of ICDFrame::GetCaller().
+void ShimStackWalk::GetCallerForFrame(ICorDebugFrame * pFrame, ICorDebugFrame ** ppCallerFrame)
+{
+ for (UINT32 i = 0; i < GetChainCount(); i++)
+ {
+ ShimChain * pCurChain = GetChain(i);
+
+ for (UINT32 j = pCurChain->GetFirstFrameIndex(); j < pCurChain->GetLastFrameIndex(); j++)
+ {
+ if (IsSameFrame(GetFrame(j), pFrame))
+ {
+ // Check whether this is the last frame in the chain.
+ UINT32 callerFrameIndex = j + 1;
+ if (callerFrameIndex < pCurChain->GetLastFrameIndex())
+ {
+ *ppCallerFrame = static_cast<ICorDebugFrame *>(GetFrame(callerFrameIndex));
+ (*ppCallerFrame)->AddRef();
+ }
+ else
+ {
+ *ppCallerFrame = NULL;
+ }
+ return;
+ }
+ }
+ }
+}
+
+// This is the shim implementation of ICDFrame::GetCallee().
+void ShimStackWalk::GetCalleeForFrame(ICorDebugFrame * pFrame, ICorDebugFrame ** ppCalleeFrame)
+{
+ for (UINT32 i = 0; i < GetChainCount(); i++)
+ {
+ ShimChain * pCurChain = GetChain(i);
+
+ for (UINT32 j = pCurChain->GetFirstFrameIndex(); j < pCurChain->GetLastFrameIndex(); j++)
+ {
+ if (IsSameFrame(GetFrame(j), pFrame))
+ {
+ // Check whether this is the first frame in the chain.
+ if (j > pCurChain->GetFirstFrameIndex())
+ {
+ UINT32 calleeFrameIndex = j - 1;
+ *ppCalleeFrame = static_cast<ICorDebugFrame *>(GetFrame(calleeFrameIndex));
+ (*ppCalleeFrame)->AddRef();
+ }
+ else
+ {
+ *ppCalleeFrame = NULL;
+ }
+ return;
+ }
+ }
+ }
+}
+
+FramePointer ShimStackWalk::GetFramePointerForChain(DT_CONTEXT * pContext)
+{
+ return FramePointer::MakeFramePointer(CORDbgGetSP(pContext));
+}
+
+FramePointer ShimStackWalk::GetFramePointerForChain(ICorDebugInternalFrame2 * pInternalFrame2)
+{
+ CORDB_ADDRESS frameAddr;
+ HRESULT hr = pInternalFrame2->GetAddress(&frameAddr);
+ IfFailThrow(hr);
+
+ return FramePointer::MakeFramePointer(reinterpret_cast<void *>(frameAddr));
+}
+
+CorDebugInternalFrameType ShimStackWalk::GetInternalFrameType(ICorDebugInternalFrame2 * pFrame2)
+{
+ HRESULT hr = E_FAIL;
+
+ // Retrieve the frame type of the internal frame.
+ RSExtSmartPtr<ICorDebugInternalFrame> pFrame;
+ hr = pFrame2->QueryInterface(IID_ICorDebugInternalFrame, reinterpret_cast<void **>(&pFrame));
+ IfFailThrow(hr);
+
+ CorDebugInternalFrameType type;
+ hr = pFrame->GetFrameType(&type);
+ IfFailThrow(hr);
+
+ return type;
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::AppendFrame
+//
+// Description:
+// Append the specified frame to the array and increment the counter.
+//
+// Arguments:
+// * pFrame - the frame to be added
+// * pStackWalkInfo - contains information of the stackwalk
+//
+
+void ShimStackWalk::AppendFrame(ICorDebugFrame * pFrame, StackWalkInfo * pStackWalkInfo)
+{
+ // grow the
+ ICorDebugFrame ** ppFrame = m_stackFrames.AppendThrowing();
+
+ // Be careful of the AddRef() below. Once we do the addref, we need to save the pointer and
+ // explicitly release it.
+ *ppFrame = pFrame;
+ (*ppFrame)->AddRef();
+
+ pStackWalkInfo->m_cFrame += 1;
+}
+
+// ----------------------------------------------------------------------------
+// Refer to comment of the overloaded function.
+//
+
+void ShimStackWalk::AppendFrame(ICorDebugInternalFrame2 * pInternalFrame2, StackWalkInfo * pStackWalkInfo)
+{
+ RSExtSmartPtr<ICorDebugFrame> pFrame;
+ HRESULT hr = pInternalFrame2->QueryInterface(IID_ICorDebugFrame, reinterpret_cast<void **>(&pFrame));
+ IfFailThrow(hr);
+
+ AppendFrame(pFrame, pStackWalkInfo);
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::AppendChainWorker
+//
+// Description:
+// Append the specified chain to the array.
+//
+// Arguments:
+// * pStackWalkInfo - contains information regarding the stackwalk
+// * pLeafContext - the leaf CONTEXT of the chain to be added
+// * fpRoot - the root boundary of the chain to be added
+// * chainReason - the chain reason of the chain to be added
+// * fIsManagedChain - whether the chain to be added is managed
+//
+
+void ShimStackWalk::AppendChainWorker(StackWalkInfo * pStackWalkInfo,
+ DT_CONTEXT * pLeafContext,
+ FramePointer fpRoot,
+ CorDebugChainReason chainReason,
+ BOOL fIsManagedChain)
+{
+ // first, create the chain
+ NewHolder<ShimChain> pChain(new ShimChain(this,
+ pLeafContext,
+ fpRoot,
+ pStackWalkInfo->m_cChain,
+ pStackWalkInfo->m_firstFrameInChain,
+ pStackWalkInfo->m_cFrame,
+ chainReason,
+ fIsManagedChain,
+ GetShimLock()));
+
+ // Grow the array and add the newly created chain.
+ // Once we call AddRef() we own the ShimChain and need to release it.
+ ShimChain ** ppChain = m_stackChains.AppendThrowing();
+ *ppChain = pChain;
+ (*ppChain)->AddRef();
+
+ // update the counters on the StackWalkInfo
+ pStackWalkInfo->m_cChain += 1;
+ pStackWalkInfo->m_firstFrameInChain = pStackWalkInfo->m_cFrame;
+
+ // If all goes well, suppress the release so that the ShimChain won't go away.
+ pChain.SuppressRelease();
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::AppendChain
+//
+// Description:
+// Append the chain to the array. This function is also smart enough to send an enter-managed chain
+// if necessary. In other words, this function may append two chains at the same time.
+//
+// Arguments:
+// * pChainInfo - information on the chain to be added
+// * pStackWalkInfo - information regarding the current stackwalk
+//
+
+void ShimStackWalk::AppendChain(ChainInfo * pChainInfo, StackWalkInfo * pStackWalkInfo)
+{
+ // Check if the chain to be added is managed or not.
+ BOOL fManagedChain = FALSE;
+ if ((pChainInfo->m_reason == CHAIN_ENTER_MANAGED) ||
+ (pChainInfo->m_reason == CHAIN_CLASS_INIT) ||
+ (pChainInfo->m_reason == CHAIN_SECURITY) ||
+ (pChainInfo->m_reason == CHAIN_FUNC_EVAL))
+ {
+ fManagedChain = TRUE;
+ }
+
+ DT_CONTEXT * pChainContext = NULL;
+ if (fManagedChain)
+ {
+ // The chain to be added is managed itself. So we don't need to send an enter-managed chain.
+ pChainInfo->m_fNeedEnterManagedChain = false;
+ pChainContext = &(pChainInfo->m_leafManagedContext);
+ }
+ else
+ {
+ // The chain to be added is unmanaged. Check if we need to send an enter-managed chain.
+ if (pChainInfo->m_fNeedEnterManagedChain)
+ {
+ // We need to send an extra enter-managed chain.
+ _ASSERTE(pChainInfo->m_fLeafNativeContextIsValid);
+ BYTE * sp = reinterpret_cast<BYTE *>(CORDbgGetSP(&(pChainInfo->m_leafNativeContext)));
+#if !defined(_TARGET_ARM_) && !defined(_TARGET_ARM64_)
+ // Dev11 324806: on ARM we use the caller's SP for a frame's ending delimiter so we cannot
+ // subtract 4 bytes from the chain's ending delimiter else the frame might never be in range.
+ // TODO: revisit overlapping ranges on ARM, it would be nice to make it consistent with the other architectures.
+ sp -= sizeof(LPVOID);
+#endif
+ FramePointer fp = FramePointer::MakeFramePointer(sp);
+
+ AppendChainWorker(pStackWalkInfo,
+ &(pChainInfo->m_leafManagedContext),
+ fp,
+ CHAIN_ENTER_MANAGED,
+ TRUE);
+
+ pChainInfo->m_fNeedEnterManagedChain = false;
+ }
+ _ASSERTE(pChainInfo->m_fLeafNativeContextIsValid);
+ pChainContext = &(pChainInfo->m_leafNativeContext);
+ }
+
+ // Add the actual chain.
+ AppendChainWorker(pStackWalkInfo,
+ pChainContext,
+ pChainInfo->m_rootFP,
+ pChainInfo->m_reason,
+ fManagedChain);
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::SaveChainContext
+//
+// Description:
+// Save the current CONTEXT on the ICDStackWalk into the specified CONTEXT. Also update the root end
+// of the chain on the ChainInfo.
+//
+// Arguments:
+// * pSW - the ICDStackWalk for the current stackwalk
+// * pChainInfo - the ChainInfo keeping track of the current chain
+// * pContext - the destination CONTEXT
+//
+
+void ShimStackWalk::SaveChainContext(ICorDebugStackWalk * pSW, ChainInfo * pChainInfo, DT_CONTEXT * pContext)
+{
+ HRESULT hr = pSW->GetContext(CONTEXT_FULL,
+ sizeof(*pContext),
+ NULL,
+ reinterpret_cast<BYTE *>(pContext));
+ IfFailThrow(hr);
+
+ pChainInfo->m_rootFP = GetFramePointerForChain(pContext);
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::CheckInternalFrame
+//
+// Description:
+// Check whether the next frame to be processed should be the next internal frame or the next stack frame.
+//
+// Arguments:
+// * pNextStackFrame - the next stack frame
+// * pStackWalkInfo - information regarding the current stackwalk; also contains the next internal frame
+// * pThread3 - the thread we are walking
+// * pSW - the current stackwalk
+//
+// Return Value:
+// Return TRUE if we should process an internal frame next.
+//
+
+BOOL ShimStackWalk::CheckInternalFrame(ICorDebugFrame * pNextStackFrame,
+ StackWalkInfo * pStackWalkInfo,
+ ICorDebugThread3 * pThread3,
+ ICorDebugStackWalk * pSW)
+{
+ _ASSERTE(pNextStackFrame != NULL);
+ _ASSERTE(!pStackWalkInfo->ExhaustedAllInternalFrames());
+
+ HRESULT hr = E_FAIL;
+ BOOL fIsInternalFrameFirst = FALSE;
+
+ // Special handling for the case where a managed method contains a M2U internal frame.
+ // Normally only IL stubs contain M2U internal frames, but we may have inlined pinvoke calls in
+ // optimized code. In that case, we would have an InlinedCallFrame in a normal managed method on x86.
+ // On WIN64, we would have a normal NDirectMethodFrame* in a normal managed method.
+ if (pStackWalkInfo->m_internalFrameType == STUBFRAME_M2U)
+ {
+ // create a temporary ICDStackWalk
+ RSExtSmartPtr<ICorDebugStackWalk> pTmpSW;
+ hr = pThread3->CreateStackWalk(&pTmpSW);
+ IfFailThrow(hr);
+
+ // retrieve the current CONTEXT
+ DT_CONTEXT ctx;
+ ctx.ContextFlags = DT_CONTEXT_FULL;
+ hr = pSW->GetContext(ctx.ContextFlags, sizeof(ctx), NULL, reinterpret_cast<BYTE *>(&ctx));
+ IfFailThrow(hr);
+
+ // set the CONTEXT on the temporary ICDStackWalk
+ hr = pTmpSW->SetContext(SET_CONTEXT_FLAG_ACTIVE_FRAME, sizeof(ctx), reinterpret_cast<BYTE *>(&ctx));
+ IfFailThrow(hr);
+
+ // unwind the temporary ICDStackWalk by one frame
+ hr = pTmpSW->Next();
+ IfFailThrow(hr);
+
+ // Unwinding from a managed stack frame will land us either in a managed stack frame or a native
+ // stack frame. In either case, we have a CONTEXT.
+ hr = pTmpSW->GetContext(ctx.ContextFlags, sizeof(ctx), NULL, reinterpret_cast<BYTE *>(&ctx));
+ IfFailThrow(hr);
+
+ // Get the SP from the CONTEXT. This is the caller SP.
+ CORDB_ADDRESS sp = PTR_TO_CORDB_ADDRESS(CORDbgGetSP(&ctx));
+
+ // get the frame address
+ CORDB_ADDRESS frameAddr = 0;
+ hr = pStackWalkInfo->GetCurrentInternalFrame()->GetAddress(&frameAddr);
+ IfFailThrow(hr);
+
+ // Compare the frame address with the caller SP of the stack frame for the IL method without metadata.
+ fIsInternalFrameFirst = (frameAddr < sp);
+ }
+ else
+ {
+ hr = pStackWalkInfo->GetCurrentInternalFrame()->IsCloserToLeaf(pNextStackFrame, &fIsInternalFrameFirst);
+ IfFailThrow(hr);
+ }
+
+ return fIsInternalFrameFirst;
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::ConvertInternalFrameToDynamicMethod
+//
+// Description:
+// In V2, PrestubMethodFrames (PMFs) are exposed as one of two things: a chain of type
+// CHAIN_CLASS_INIT in most cases, or an internal frame of type STUBFRAME_LIGHTWEIGHT_FUNCTION if
+// the method being jitted is a dynamic method. On the other hand, in Arrowhead, we consistently expose
+// PMFs as STUBFRAME_JIT_COMPILATION. This function determines if a STUBFRAME_JIT_COMPILATION should
+// be exposed, and, if so, how to expose it. In the case where conversion is necessary, this function
+// also updates the stackwalk information with the converted frame.
+//
+// Here are the rules for conversion:
+// 1) If the method being jitted is an IL stub, we set the converted frame to NULL, and we return TRUE.
+// 2) If the method being jitted is an LCG method, we set the converted frame to a
+// STUBFRAME_LIGHTWEIGHT_FUNCTION, and we return NULL.
+// 3) Otherwise, we return FALSE.
+//
+// Arguments:
+// * pStackWalkInfo - information about the current stackwalk
+//
+// Return Value:
+// Return TRUE if a conversion has taken place.
+//
+
+BOOL ShimStackWalk::ConvertInternalFrameToDynamicMethod(StackWalkInfo * pStackWalkInfo)
+{
+ HRESULT hr = E_FAIL;
+
+ // QI for ICDFrame
+ RSExtSmartPtr<ICorDebugFrame> pOriginalFrame;
+ hr = pStackWalkInfo->GetCurrentInternalFrame()->QueryInterface(
+ IID_ICorDebugFrame,
+ reinterpret_cast<void **>(&pOriginalFrame));
+ IfFailThrow(hr);
+
+ // Ask the RS to do the real work.
+ CordbThread * pThread = static_cast<CordbThread *>(m_pThread.GetValue());
+ pStackWalkInfo->m_fHasConvertedFrame = (TRUE == pThread->ConvertFrameForILMethodWithoutMetadata(
+ pOriginalFrame,
+ &(pStackWalkInfo->m_pConvertedInternalFrame2)));
+
+ if (pStackWalkInfo->HasConvertedFrame())
+ {
+ // We have a conversion.
+ if (pStackWalkInfo->GetCurrentInternalFrame() != NULL)
+ {
+ // We have a converted internal frame, so let's update the internal frame type.
+ RSExtSmartPtr<ICorDebugInternalFrame> pInternalFrame;
+ hr = pStackWalkInfo->GetCurrentInternalFrame()->QueryInterface(
+ IID_ICorDebugInternalFrame,
+ reinterpret_cast<void **>(&pInternalFrame));
+ IfFailThrow(hr);
+
+ hr = pInternalFrame->GetFrameType(&(pStackWalkInfo->m_internalFrameType));
+ IfFailThrow(hr);
+ }
+ else
+ {
+ // The method being jitted is an IL stub, so let's not expose it.
+ pStackWalkInfo->m_internalFrameType = STUBFRAME_NONE;
+ }
+ }
+
+ return pStackWalkInfo->HasConvertedFrame();
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::ConvertInternalFrameToDynamicMethod
+//
+// Description:
+// In V2, LCG methods are exposed as internal frames of type STUBFRAME_LIGHTWEIGHT_FUNCTION. However,
+// in Arrowhead, LCG methods are exposed as first-class stack frames, not internal frames. Thus,
+// the shim needs to convert an ICDNativeFrame for a dynamic method in Arrowhead to an
+// ICDInternalFrame of type STUBFRAME_LIGHTWEIGHT_FUNCTION in V2. Furthermore, IL stubs are not exposed
+// in V2 at all.
+//
+// Here are the rules for conversion:
+// 1) If the stack frame is for an IL stub, we set the converted frame to NULL, and we return TRUE.
+// 2) If the stack frame is for an LCG method, we set the converted frame to a
+// STUBFRAME_LIGHTWEIGHT_FUNCTION, and we return NULL.
+// 3) Otherwise, we return FALSE.
+//
+// Arguments:
+// * pFrame - the frame to be checked and converted if necessary
+// * pStackWalkInfo - information about the current stackwalk
+//
+// Return Value:
+// Return TRUE if a conversion has taken place.
+//
+
+BOOL ShimStackWalk::ConvertStackFrameToDynamicMethod(ICorDebugFrame * pFrame, StackWalkInfo * pStackWalkInfo)
+{
+ // If this is not a dynamic method (i.e. LCG method or IL stub), then we don't need to do a conversion.
+ if (!IsILFrameWithoutMetadata(pFrame))
+ {
+ return FALSE;
+ }
+
+ // Ask the RS to do the real work.
+ CordbThread * pThread = static_cast<CordbThread *>(m_pThread.GetValue());
+ pStackWalkInfo->m_fHasConvertedFrame = (TRUE == pThread->ConvertFrameForILMethodWithoutMetadata(
+ pFrame,
+ &(pStackWalkInfo->m_pConvertedInternalFrame2)));
+
+ return pStackWalkInfo->HasConvertedFrame();
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::TrackUMChain
+//
+// Description:
+// Keep track of enter-unmanaged chains. Extend or cancel the chain as necesasry.
+//
+// Arguments:
+// * pChainInfo - information on the current chain we are tracking
+// * pStackWalkInfo - information regarding the current stackwalk
+//
+// Notes:
+// * This logic is based on code:TrackUMChain on the LS.
+//
+
+void ShimStackWalk::TrackUMChain(ChainInfo * pChainInfo, StackWalkInfo * pStackWalkInfo)
+{
+ if (!pChainInfo->IsTrackingUMChain())
+ {
+ if (pStackWalkInfo->m_fProcessingInternalFrame)
+ {
+ if (pStackWalkInfo->m_internalFrameType == STUBFRAME_M2U)
+ {
+ // If we hit an M2U frame out in the wild, convert it to an enter-unmanaged chain.
+
+ // We can't hit an M2U frame without hitting a native stack frame
+ // first (we filter those). We should have already saved the CONTEXT.
+ // So just update the chain reason.
+ pChainInfo->m_reason = CHAIN_ENTER_UNMANAGED;
+ }
+ }
+ }
+
+ BOOL fCreateUMChain = FALSE;
+ if (pChainInfo->IsTrackingUMChain())
+ {
+ if (pStackWalkInfo->m_fProcessingInternalFrame)
+ {
+ // Extend the root end of the unmanaged chain.
+ pChainInfo->m_rootFP = GetFramePointerForChain(pStackWalkInfo->GetCurrentInternalFrame());
+
+ // Sometimes we may not want to show an UM chain b/c we know it's just
+ // code inside of mscorwks. (Eg: Funcevals & AD transitions both fall into this category).
+ // These are perfectly valid UM chains and we could give them if we wanted to.
+ if ((pStackWalkInfo->m_internalFrameType == STUBFRAME_APPDOMAIN_TRANSITION) ||
+ (pStackWalkInfo->m_internalFrameType == STUBFRAME_FUNC_EVAL))
+ {
+ pChainInfo->CancelUMChain();
+ }
+ else if (pStackWalkInfo->m_internalFrameType == STUBFRAME_M2U)
+ {
+ // If we hit an M2U frame, then go ahead and dispatch the UM chain now.
+ // This will likely also be an exit frame.
+ fCreateUMChain = TRUE;
+ }
+ else if ((pStackWalkInfo->m_internalFrameType == STUBFRAME_CLASS_INIT) ||
+ (pStackWalkInfo->m_internalFrameType == STUBFRAME_EXCEPTION) ||
+ (pStackWalkInfo->m_internalFrameType == STUBFRAME_SECURITY) ||
+ (pStackWalkInfo->m_internalFrameType == STUBFRAME_JIT_COMPILATION))
+ {
+ fCreateUMChain = TRUE;
+ }
+ }
+ else
+ {
+ // If we hit a managed stack frame when we are processing an unmanaged chain, then
+ // the chain is done.
+ fCreateUMChain = TRUE;
+ }
+ }
+
+ if (fCreateUMChain)
+ {
+ // check whether we get any stack range
+ _ASSERTE(pChainInfo->m_fLeafNativeContextIsValid);
+ FramePointer fpLeaf = GetFramePointerForChain(&(pChainInfo->m_leafNativeContext));
+
+ // Don't bother creating an unmanaged chain if the stack range is empty.
+ if (fpLeaf != pChainInfo->m_rootFP)
+ {
+ AppendChain(pChainInfo, pStackWalkInfo);
+ }
+ pChainInfo->CancelUMChain();
+ }
+}
+
+BOOL ShimStackWalk::IsV3FrameType(CorDebugInternalFrameType type)
+{
+ // These frame types are either new in Arrowhead or not used in V2.
+ if ((type == STUBFRAME_INTERNALCALL) ||
+ (type == STUBFRAME_CLASS_INIT) ||
+ (type == STUBFRAME_EXCEPTION) ||
+ (type == STUBFRAME_SECURITY) ||
+ (type == STUBFRAME_JIT_COMPILATION))
+ {
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+// Check whether a stack frame is for a dynamic method. The way to tell is if the stack frame has
+// an ICDNativeFrame but no ICDILFrame.
+BOOL ShimStackWalk::IsILFrameWithoutMetadata(ICorDebugFrame * pFrame)
+{
+ HRESULT hr = E_FAIL;
+
+ RSExtSmartPtr<ICorDebugNativeFrame> pNativeFrame;
+ hr = pFrame->QueryInterface(IID_ICorDebugNativeFrame, reinterpret_cast<void **>(&pNativeFrame));
+ IfFailThrow(hr);
+
+ if (pNativeFrame != NULL)
+ {
+ RSExtSmartPtr<ICorDebugILFrame> pILFrame;
+ hr = pFrame->QueryInterface(IID_ICorDebugILFrame, reinterpret_cast<void **>(&pILFrame));
+
+ if (FAILED(hr) || (pILFrame == NULL))
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+ShimStackWalk::StackWalkInfo::StackWalkInfo()
+ : m_cChain(0),
+ m_cFrame(0),
+ m_firstFrameInChain(0),
+ m_cInternalFrames(0),
+ m_curInternalFrame(0),
+ m_internalFrameType(STUBFRAME_NONE),
+ m_fExhaustedAllStackFrames(false),
+ m_fProcessingInternalFrame(false),
+ m_fSkipChain(false),
+ m_fLeafFrame(true),
+ m_fHasConvertedFrame(false)
+{
+ m_pChildFrame.Assign(NULL);
+ m_pConvertedInternalFrame2.Assign(NULL);
+}
+
+ShimStackWalk::StackWalkInfo::~StackWalkInfo()
+{
+ if (m_pChildFrame != NULL)
+ {
+ m_pChildFrame.Clear();
+ }
+
+ if (m_pConvertedInternalFrame2 != NULL)
+ {
+ m_pConvertedInternalFrame2.Clear();
+ }
+
+ if (!m_ppInternalFrame2.IsEmpty())
+ {
+ m_ppInternalFrame2.Clear();
+ }
+}
+
+void ShimStackWalk::StackWalkInfo::ResetForNextFrame()
+{
+ m_pConvertedInternalFrame2.Clear();
+ m_internalFrameType = STUBFRAME_NONE;
+ m_fProcessingInternalFrame = false;
+ m_fSkipChain = false;
+ m_fHasConvertedFrame = false;
+}
+
+// Check whether we have exhausted both internal frames and stack frames.
+bool ShimStackWalk::StackWalkInfo::ExhaustedAllFrames()
+{
+ return (ExhaustedAllStackFrames() && ExhaustedAllInternalFrames());
+}
+
+bool ShimStackWalk::StackWalkInfo::ExhaustedAllStackFrames()
+{
+ return m_fExhaustedAllStackFrames;
+}
+
+bool ShimStackWalk::StackWalkInfo::ExhaustedAllInternalFrames()
+{
+ return (m_curInternalFrame == m_cInternalFrames);
+}
+
+ICorDebugInternalFrame2 * ShimStackWalk::StackWalkInfo::GetCurrentInternalFrame()
+{
+ _ASSERTE(!ExhaustedAllInternalFrames() || HasConvertedFrame());
+
+ if (HasConvertedFrame())
+ {
+ return m_pConvertedInternalFrame2;
+ }
+ else
+ {
+ return m_ppInternalFrame2[m_curInternalFrame];
+ }
+}
+
+BOOL ShimStackWalk::StackWalkInfo::IsLeafFrame()
+{
+ return m_fLeafFrame;
+}
+
+BOOL ShimStackWalk::StackWalkInfo::IsSkippingFrame()
+{
+ return (m_pChildFrame != NULL);
+}
+
+BOOL ShimStackWalk::StackWalkInfo::HasConvertedFrame()
+{
+ return m_fHasConvertedFrame;
+}
+
+
+ShimChain::ShimChain(ShimStackWalk * pSW,
+ DT_CONTEXT * pContext,
+ FramePointer fpRoot,
+ UINT32 chainIndex,
+ UINT32 frameStartIndex,
+ UINT32 frameEndIndex,
+ CorDebugChainReason chainReason,
+ BOOL fIsManaged,
+ RSLock * pShimLock)
+ : m_context(*pContext),
+ m_fpRoot(fpRoot),
+ m_pStackWalk(pSW),
+ m_refCount(0),
+ m_chainIndex(chainIndex),
+ m_frameStartIndex(frameStartIndex),
+ m_frameEndIndex(frameEndIndex),
+ m_chainReason(chainReason),
+ m_fIsManaged(fIsManaged),
+ m_fIsNeutered(FALSE),
+ m_pShimLock(pShimLock)
+{
+}
+
+ShimChain::~ShimChain()
+{
+ _ASSERTE(IsNeutered());
+}
+
+void ShimChain::Neuter()
+{
+ m_fIsNeutered = TRUE;
+}
+
+BOOL ShimChain::IsNeutered()
+{
+ return m_fIsNeutered;
+}
+
+ULONG STDMETHODCALLTYPE ShimChain::AddRef()
+{
+ return InterlockedIncrement((LONG *)&m_refCount);
+}
+
+ULONG STDMETHODCALLTYPE ShimChain::Release()
+{
+ LONG newRefCount = InterlockedDecrement((LONG *)&m_refCount);
+ _ASSERTE(newRefCount >= 0);
+
+ if (newRefCount == 0)
+ {
+ delete this;
+ }
+ return newRefCount;
+}
+
+HRESULT ShimChain::QueryInterface(REFIID id, void ** pInterface)
+{
+ if (id == IID_ICorDebugChain)
+ {
+ *pInterface = static_cast<ICorDebugChain *>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugChain *>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+// Returns the thread to which this chain belongs.
+HRESULT ShimChain::GetThread(ICorDebugThread ** ppThread)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppThread, ICorDebugThread **);
+
+ *ppThread = m_pStackWalk->GetThread();
+ (*ppThread)->AddRef();
+
+ return S_OK;
+}
+
+// Get the range on the stack that this chain matches against.
+// pStart is the leafmost; pEnd is the rootmost.
+// This is particularly used in interop-debugging to get native stack traces
+// for the UM portions of the stack
+HRESULT ShimChain::GetStackRange(CORDB_ADDRESS * pStart, CORDB_ADDRESS * pEnd)
+{
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ THROW_IF_NEUTERED(this);
+
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pStart, CORDB_ADDRESS *);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pEnd, CORDB_ADDRESS *);
+
+ // Return the leafmost end of the stack range.
+ // The leafmost end is represented by the register set.
+ if (pStart)
+ {
+ *pStart = PTR_TO_CORDB_ADDRESS(CORDbgGetSP(&m_context));
+ }
+
+ // Return the rootmost end of the stack range. It is represented by the frame pointer of the chain.
+ if (pEnd)
+ {
+ *pEnd = PTR_TO_CORDB_ADDRESS(m_fpRoot.GetSPValue());
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT ShimChain::GetContext(ICorDebugContext ** ppContext)
+{
+ return E_NOTIMPL;
+}
+
+// Return the next chain which is closer to the root.
+// Currently this is just a wrapper over GetNext().
+HRESULT ShimChain::GetCaller(ICorDebugChain ** ppChain)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppChain, ICorDebugChain **);
+
+ return GetNext(ppChain);
+}
+
+// Return the previous chain which is closer to the leaf.
+// Currently this is just a wrapper over GetPrevious().
+HRESULT ShimChain::GetCallee(ICorDebugChain ** ppChain)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppChain, ICorDebugChain **);
+
+ return GetPrevious(ppChain);
+}
+
+// Return the previous chain which is closer to the leaf.
+HRESULT ShimChain::GetPrevious(ICorDebugChain ** ppChain)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppChain, ICorDebugChain **);
+
+ *ppChain = NULL;
+ if (m_chainIndex != 0)
+ {
+ *ppChain = m_pStackWalk->GetChain(m_chainIndex - 1);
+ }
+
+ if (*ppChain != NULL)
+ {
+ (*ppChain)->AddRef();
+ }
+
+ return S_OK;
+}
+
+// Return the next chain which is closer to the root.
+HRESULT ShimChain::GetNext(ICorDebugChain ** ppChain)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppChain, ICorDebugChain **);
+
+ *ppChain = m_pStackWalk->GetChain(m_chainIndex + 1);
+ if (*ppChain != NULL)
+ {
+ (*ppChain)->AddRef();
+ }
+
+ return S_OK;
+}
+
+// Return whether the chain contains frames running managed code.
+HRESULT ShimChain::IsManaged(BOOL * pManaged)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pManaged, BOOL *);
+
+ *pManaged = m_fIsManaged;
+
+ return S_OK;
+}
+
+// Return an enumerator to iterate through the frames contained in this chain.
+HRESULT ShimChain::EnumerateFrames(ICorDebugFrameEnum ** ppFrames)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppFrames, ICorDebugFrameEnum **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ ShimStackWalk * pSW = GetShimStackWalk();
+ NewHolder<ShimFrameEnum> pFrameEnum(new ShimFrameEnum(pSW, this, m_frameStartIndex, m_frameEndIndex, m_pShimLock));
+
+ *ppFrames = pFrameEnum;
+ (*ppFrames)->AddRef();
+
+ // link the new ShimFramEnum into the list on the ShimStackWalk
+ pSW->AddFrameEnum(pFrameEnum);
+
+ pFrameEnum.SuppressRelease();
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+// Return an enumerator to iterate through the frames contained in this chain.
+// Note that this function will only succeed if the cached stack trace is valid.
+HRESULT ShimChain::GetActiveFrame(ICorDebugFrame ** ppFrame)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppFrame, ICorDebugFrame **);
+ (*ppFrame) = NULL;
+
+ HRESULT hr = S_OK;
+
+ // Chains may be empty, so they have no active frame.
+ if (m_frameStartIndex == m_frameEndIndex)
+ {
+ *ppFrame = NULL;
+ }
+ else
+ {
+ *ppFrame = m_pStackWalk->GetFrame(m_frameStartIndex);
+ (*ppFrame)->AddRef();
+ }
+
+ return hr;
+}
+
+// Return the register set of the leaf end of the chain
+HRESULT ShimChain::GetRegisterSet(ICorDebugRegisterSet ** ppRegisters)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppRegisters, ICorDebugRegisterSet **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ CordbThread * pThread = static_cast<CordbThread *>(m_pStackWalk->GetThread());
+
+ // This is a private hook for calling back into the RS. Alternatively, we could have created a
+ // ShimRegisterSet, but that's too much work for now.
+ pThread->CreateCordbRegisterSet(&m_context,
+ (m_chainIndex == 0),
+ m_chainReason,
+ ppRegisters);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+// Return the chain reason
+HRESULT ShimChain::GetReason(CorDebugChainReason * pReason)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pReason, CorDebugChainReason *);
+
+ *pReason = m_chainReason;
+
+ return S_OK;
+}
+
+ShimStackWalk * ShimChain::GetShimStackWalk()
+{
+ return m_pStackWalk;
+}
+
+UINT32 ShimChain::GetFirstFrameIndex()
+{
+ return this->m_frameStartIndex;
+}
+
+UINT32 ShimChain::GetLastFrameIndex()
+{
+ return this->m_frameEndIndex;
+}
+
+
+ShimChainEnum::ShimChainEnum(ShimStackWalk * pSW, RSLock * pShimLock)
+ : m_pStackWalk(pSW),
+ m_pNext(NULL),
+ m_currentChainIndex(0),
+ m_refCount(0),
+ m_fIsNeutered(FALSE),
+ m_pShimLock(pShimLock)
+{
+}
+
+ShimChainEnum::~ShimChainEnum()
+{
+ _ASSERTE(IsNeutered());
+}
+
+void ShimChainEnum::Neuter()
+{
+ if (IsNeutered())
+ {
+ return;
+ }
+
+ m_fIsNeutered = TRUE;
+}
+
+BOOL ShimChainEnum::IsNeutered()
+{
+ return m_fIsNeutered;
+}
+
+
+ULONG STDMETHODCALLTYPE ShimChainEnum::AddRef()
+{
+ return InterlockedIncrement((LONG *)&m_refCount);
+}
+
+ULONG STDMETHODCALLTYPE ShimChainEnum::Release()
+{
+ LONG newRefCount = InterlockedDecrement((LONG *)&m_refCount);
+ _ASSERTE(newRefCount >= 0);
+
+ if (newRefCount == 0)
+ {
+ delete this;
+ }
+ return newRefCount;
+}
+
+HRESULT ShimChainEnum::QueryInterface(REFIID id, void ** ppInterface)
+{
+ if (id == IID_ICorDebugChainEnum)
+ {
+ *ppInterface = static_cast<ICorDebugChainEnum *>(this);
+ }
+ else if (id == IID_ICorDebugEnum)
+ {
+ *ppInterface = static_cast<ICorDebugEnum *>(static_cast<ICorDebugChainEnum *>(this));
+ }
+ else if (id == IID_IUnknown)
+ {
+ *ppInterface = static_cast<IUnknown *>(static_cast<ICorDebugChainEnum *>(this));
+ }
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+// Skip the specified number of chains.
+HRESULT ShimChainEnum::Skip(ULONG celt)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+
+ // increment the index by the specified amount
+ m_currentChainIndex += celt;
+ return S_OK;
+}
+
+HRESULT ShimChainEnum::Reset()
+{
+ m_currentChainIndex = 0;
+ return S_OK;
+}
+
+// Clone the chain enumerator and set the new one to the same current chain
+HRESULT ShimChainEnum::Clone(ICorDebugEnum ** ppEnum)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorDebugEnum **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ NewHolder<ShimChainEnum> pChainEnum(new ShimChainEnum(m_pStackWalk, m_pShimLock));
+
+ // set the index in the new enumerator
+ pChainEnum->m_currentChainIndex = this->m_currentChainIndex;
+
+ *ppEnum = pChainEnum;
+ (*ppEnum)->AddRef();
+ m_pStackWalk->AddChainEnum(pChainEnum);
+
+ pChainEnum.SuppressRelease();
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+// Return the number of chains on the thread
+HRESULT ShimChainEnum::GetCount(ULONG * pcChains)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pcChains, ULONG *);
+
+ *pcChains = m_pStackWalk->GetChainCount();
+ return S_OK;
+}
+
+// Retrieve the next x number of chains on the thread into "chains", where x is specified by "celt".
+// "pcChainsFetched" is set to be the actual number of chains retrieved.
+// Return S_FALSE if the number of chains actually retrieved is less than the number of chains requested.
+HRESULT ShimChainEnum::Next(ULONG cChains, ICorDebugChain * rgpChains[], ULONG * pcChainsFetched)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(rgpChains, ICorDebugChain *, cChains, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcChainsFetched, ULONG *);
+
+ // if the out parameter is NULL, then we can only return one chain at a time
+ if ((pcChainsFetched == NULL) && (cChains != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ // Check for the trivial case where no chain is actually requested.
+ // This is probably a user error.
+ if (cChains == 0)
+ {
+ if (pcChainsFetched != NULL)
+ {
+ *pcChainsFetched = 0;
+ }
+ return S_OK;
+ }
+
+ ICorDebugChain ** ppCurrentChain = rgpChains;
+
+ while ((m_currentChainIndex < m_pStackWalk->GetChainCount()) &&
+ (cChains > 0))
+ {
+ *ppCurrentChain = m_pStackWalk->GetChain(m_currentChainIndex);
+ (*ppCurrentChain)->AddRef();
+
+ ppCurrentChain++; // increment the pointer into the buffer
+ m_currentChainIndex++; // increment the index
+ cChains--;
+ }
+
+ // set the number of chains actually returned
+ if (pcChainsFetched != NULL)
+ {
+ *pcChainsFetched = (ULONG)(ppCurrentChain - rgpChains);
+ }
+
+ //
+ // If we reached the end of the enumeration, but not the end
+ // of the number of requested items, we return S_FALSE.
+ //
+ if (cChains > 0)
+ {
+ return S_FALSE;
+ }
+
+ return S_OK;
+}
+
+ShimChainEnum * ShimChainEnum::GetNext()
+{
+ return m_pNext;
+}
+
+void ShimChainEnum::SetNext(ShimChainEnum * pNext)
+{
+ if (m_pNext != NULL)
+ {
+ m_pNext->Release();
+ }
+
+ m_pNext = pNext;
+
+ if (m_pNext != NULL)
+ {
+ m_pNext->AddRef();
+ }
+}
+
+
+ShimFrameEnum::ShimFrameEnum(ShimStackWalk * pSW,
+ ShimChain * pChain,
+ UINT32 frameStartIndex,
+ UINT32 frameEndIndex,
+ RSLock * pShimLock)
+ : m_pStackWalk(pSW),
+ m_pChain(pChain),
+ m_pShimLock(pShimLock),
+ m_pNext(NULL),
+ m_currentFrameIndex(frameStartIndex),
+ m_endFrameIndex(frameEndIndex),
+ m_refCount(0),
+ m_fIsNeutered(FALSE)
+{
+}
+
+ShimFrameEnum::~ShimFrameEnum()
+{
+ _ASSERTE(IsNeutered());
+}
+
+void ShimFrameEnum::Neuter()
+{
+ if (IsNeutered())
+ {
+ return;
+ }
+
+ m_fIsNeutered = TRUE;
+}
+
+BOOL ShimFrameEnum::IsNeutered()
+{
+ return m_fIsNeutered;
+}
+
+
+ULONG STDMETHODCALLTYPE ShimFrameEnum::AddRef()
+{
+ return InterlockedIncrement((LONG *)&m_refCount);
+}
+
+ULONG STDMETHODCALLTYPE ShimFrameEnum::Release()
+{
+ LONG newRefCount = InterlockedDecrement((LONG *)&m_refCount);
+ _ASSERTE(newRefCount >= 0);
+
+ if (newRefCount == 0)
+ {
+ delete this;
+ }
+ return newRefCount;
+}
+
+HRESULT ShimFrameEnum::QueryInterface(REFIID id, void ** ppInterface)
+{
+ if (id == IID_ICorDebugFrameEnum)
+ {
+ *ppInterface = static_cast<ICorDebugFrameEnum *>(this);
+ }
+ else if (id == IID_ICorDebugEnum)
+ {
+ *ppInterface = static_cast<ICorDebugEnum *>(static_cast<ICorDebugFrameEnum *>(this));
+ }
+ else if (id == IID_IUnknown)
+ {
+ *ppInterface = static_cast<IUnknown *>(static_cast<ICorDebugFrameEnum *>(this));
+ }
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+// Skip the specified number of chains.
+HRESULT ShimFrameEnum::Skip(ULONG celt)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+
+ // increment the index by the specified amount
+ m_currentFrameIndex += celt;
+ return S_OK;
+}
+
+HRESULT ShimFrameEnum::Reset()
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+
+ m_currentFrameIndex = m_pChain->GetFirstFrameIndex();
+ return S_OK;
+}
+
+// Clone the chain enumerator and set the new one to the same current chain
+HRESULT ShimFrameEnum::Clone(ICorDebugEnum ** ppEnum)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorDebugEnum **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ NewHolder<ShimFrameEnum> pFrameEnum(new ShimFrameEnum(m_pStackWalk,
+ m_pChain,
+ m_currentFrameIndex,
+ m_endFrameIndex,
+ m_pShimLock));
+
+ *ppEnum = pFrameEnum;
+ (*ppEnum)->AddRef();
+ m_pStackWalk->AddFrameEnum(pFrameEnum);
+
+ pFrameEnum.SuppressRelease();
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+// Return the number of chains on the thread
+HRESULT ShimFrameEnum::GetCount(ULONG * pcFrames)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pcFrames, ULONG *);
+
+ *pcFrames = m_pChain->GetLastFrameIndex() - m_pChain->GetFirstFrameIndex();
+ return S_OK;
+}
+
+// Retrieve the next x number of chains on the thread into "chains", where x is specified by "celt".
+// "pcChainsFetched" is set to be the actual number of chains retrieved.
+// Return S_FALSE if the number of chains actually retrieved is less than the number of chains requested.
+HRESULT ShimFrameEnum::Next(ULONG cFrames, ICorDebugFrame * rgpFrames[], ULONG * pcFramesFetched)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(rgpFrames, ICorDebugFrame *, cFrames, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcFramesFetched, ULONG *);
+
+ // if the out parameter is NULL, then we can only return one chain at a time
+ if ((pcFramesFetched == NULL) && (cFrames != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ // Check for the trivial case where no chain is actually requested.
+ // This is probably a user error.
+ if (cFrames == 0)
+ {
+ if (pcFramesFetched != NULL)
+ {
+ *pcFramesFetched = 0;
+ }
+ return S_OK;
+ }
+
+ ICorDebugFrame ** ppCurrentFrame = rgpFrames;
+
+ while ((m_currentFrameIndex < m_endFrameIndex) &&
+ (cFrames > 0))
+ {
+ *ppCurrentFrame = m_pStackWalk->GetFrame(m_currentFrameIndex);
+ (*ppCurrentFrame)->AddRef();
+
+ ppCurrentFrame++; // increment the pointer into the buffer
+ m_currentFrameIndex++; // increment the index
+ cFrames--;
+ }
+
+ // set the number of chains actually returned
+ if (pcFramesFetched != NULL)
+ {
+ *pcFramesFetched = (ULONG)(ppCurrentFrame - rgpFrames);
+ }
+
+ //
+ // If we reached the end of the enumeration, but not the end
+ // of the number of requested items, we return S_FALSE.
+ //
+ if (cFrames > 0)
+ {
+ return S_FALSE;
+ }
+
+ return S_OK;
+}
+
+ShimFrameEnum * ShimFrameEnum::GetNext()
+{
+ return m_pNext;
+}
+
+void ShimFrameEnum::SetNext(ShimFrameEnum * pNext)
+{
+ if (m_pNext != NULL)
+ {
+ m_pNext->Release();
+ }
+
+ m_pNext = pNext;
+
+ if (m_pNext != NULL)
+ {
+ m_pNext->AddRef();
+ }
+}
diff --git a/src/debug/di/stdafx.cpp b/src/debug/di/stdafx.cpp
new file mode 100644
index 0000000000..91eba13666
--- /dev/null
+++ b/src/debug/di/stdafx.cpp
@@ -0,0 +1,12 @@
+// 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.
+//*****************************************************************************
+// stdafx.cpp
+//
+
+//
+// Host for precompiled header.
+//
+//*****************************************************************************
+#include "stdafx.h" // Precompiled header key.
diff --git a/src/debug/di/stdafx.h b/src/debug/di/stdafx.h
new file mode 100644
index 0000000000..fde3e77211
--- /dev/null
+++ b/src/debug/di/stdafx.h
@@ -0,0 +1,63 @@
+// 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.
+//*****************************************************************************
+// stdafx.h
+//
+
+//
+// Common include file for utility code.
+//*****************************************************************************
+#include <stdio.h>
+#include <windows.h>
+#include <winnt.h>
+
+#include <dbgtargetcontext.h>
+
+#define RIGHT_SIDE_COMPILE
+
+//-----------------------------------------------------------------------------
+// Contracts for RS threading.
+// We only do this for debug builds and not for inproc
+//-----------------------------------------------------------------------------
+#if defined(_DEBUG)
+ #define RSCONTRACTS
+#endif
+
+
+// In case of FEATURE_DBGIPC_TRANSPORT_DI we use pipe for debugger debugee communication
+// and event redirection is not needed. (won't work anyway)
+#ifndef FEATURE_DBGIPC_TRANSPORT_DI
+// Currently, we only can redirect exception events. Since real interop-debugging
+// neeeds all events, redirection can't work in real-interop.
+// However, whether we're interop-debugging is determined at runtime, so we always
+// enable at compile time and then we need a runtime check later.
+#define ENABLE_EVENT_REDIRECTION_PIPELINE
+#endif
+
+#include "ex.h"
+
+#include "sigparser.h"
+#include "corpub.h"
+#include "rspriv.h"
+
+// This is included to deal with GCC limitations around templates.
+// For GCC, if a compilation unit refers to a templated class (like Ptr<T>), GCC requires the compilation
+// unit to have T's definitions for anything that Ptr may call.
+// RsPriv.h has a RSExtSmartPtr<ShimProcess>, which will call ShimProcess::AddRef, which means the same compilation unit
+// must have the definition of ShimProcess::AddRef, and therefore the whole ShimProcess class.
+// CL.exe does not have this problem.
+// Practically, this means that anybody that includes rspriv.h must include shimpriv.h.
+#include "shimpriv.h"
+
+#ifdef _DEBUG
+#include "utilcode.h"
+#endif
+
+#ifndef _TARGET_ARM_
+#define DbiGetThreadContext(hThread, lpContext) ::GetThreadContext(hThread, (CONTEXT*)(lpContext))
+#define DbiSetThreadContext(hThread, lpContext) ::SetThreadContext(hThread, (CONTEXT*)(lpContext))
+#else
+BOOL DbiGetThreadContext(HANDLE hThread, DT_CONTEXT *lpContext);
+BOOL DbiSetThreadContext(HANDLE hThread, const DT_CONTEXT *lpContext);
+#endif
diff --git a/src/debug/di/symbolinfo.cpp b/src/debug/di/symbolinfo.cpp
new file mode 100644
index 0000000000..f16a47b974
--- /dev/null
+++ b/src/debug/di/symbolinfo.cpp
@@ -0,0 +1,1501 @@
+// 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.
+
+
+// callbacks for diasymreader when using SymConverter
+
+
+#include "stdafx.h"
+#include "symbolinfo.h"
+#include "ex.h"
+
+
+SymbolInfo::SymbolInfo()
+{
+ m_cRef=1;
+}
+
+SymbolInfo::~SymbolInfo()
+{
+ for (COUNT_T i = 0;i < m_Documents.GetCount();i++)
+ {
+ if (m_Documents.Get(i) != NULL)
+ ((ISymUnmanagedDocumentWriter*)m_Documents.Get(i))->Release();
+ }
+}
+
+HRESULT SymbolInfo::AddDocument(DWORD id, ISymUnmanagedDocumentWriter* pDocument)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr=S_OK;
+ EX_TRY
+ {
+ while(m_Documents.GetCount()<=id)
+ m_Documents.Append(NULL);
+ _ASSERTE(m_Documents.Get(id) == NULL);
+ m_Documents.Set(id,pDocument);
+ pDocument->AddRef();
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT SymbolInfo::MapDocument(DWORD id, ISymUnmanagedDocumentWriter** pDocument)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr=E_FAIL;
+ if(m_Documents.GetCount()>id)
+ {
+ *pDocument=(ISymUnmanagedDocumentWriter*)m_Documents.Get(id);
+ if (*pDocument == NULL)
+ return E_FAIL;
+ (*pDocument)->AddRef();
+ hr=S_OK;
+ }
+ return hr;
+}
+
+HRESULT SymbolInfo::SetClassProps(mdToken cls, DWORD flags, LPCWSTR wszName, mdToken parent)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr=S_OK;
+ EX_TRY
+ {
+ if(m_Classes.Lookup(cls) == NULL)
+ {
+ NewHolder<ClassProps> classProps (new ClassProps(cls,flags,wszName,parent));
+ m_Classes.Add(classProps);
+ classProps.SuppressRelease();
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT SymbolInfo::AddSignature(SBuffer& sig, mdSignature token)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr=S_OK;
+ EX_TRY
+ {
+ if ( m_Signatures.Lookup(sig) == NULL)
+ {
+ NewHolder<SignatureProps> sigProps (new SignatureProps(sig,token));
+ m_Signatures.Add(sigProps);
+ sigProps.SuppressRelease();
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+
+SymbolInfo::ClassProps* SymbolInfo::FindClass(mdToken cls)
+{
+ WRAPPER_NO_CONTRACT;
+ return m_Classes.Lookup(cls);
+}
+
+SymbolInfo::SignatureProps* SymbolInfo::FindSignature(SBuffer& sig)
+{
+ WRAPPER_NO_CONTRACT;
+ return m_Signatures.Lookup(sig);
+}
+
+HRESULT SymbolInfo::AddScope(ULONG32 left, ULONG32 right)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr=S_OK;
+ EX_TRY
+ {
+ if (m_Scopes.Lookup(left) == NULL)
+ {
+ NewHolder<ScopeMap> map (new ScopeMap(left,right));
+ m_Scopes.Add(map);
+ map.SuppressRelease();
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+
+}
+
+HRESULT SymbolInfo::MapScope(ULONG32 left, ULONG32* pRight)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ ScopeMap* props = m_Scopes.Lookup(left);
+ if(props == NULL)
+ {
+ _ASSERTE(FALSE);
+ return E_FAIL;
+ }
+ *pRight=props->right;
+ return S_OK;
+}
+
+
+
+HRESULT SymbolInfo::SetMethodProps(mdToken method, mdToken cls, LPCWSTR wszName)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr=S_OK;
+ EX_TRY
+ {
+ m_LastMethod.method=method;
+ m_LastMethod.cls=cls;
+ m_LastMethod.wszName=wszName;
+ m_LastMethod.wszName.Normalize();
+ }
+ EX_CATCH_HRESULT(hr)
+ return hr;
+}
+
+
+// IUnknown methods
+STDMETHODIMP SymbolInfo::QueryInterface (REFIID riid, LPVOID * ppvObj)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ if(ppvObj==NULL)
+ return E_POINTER;
+
+ if (riid == IID_IMetaDataEmit)
+ *ppvObj=static_cast<IMetaDataEmit*>(this);
+ else
+ if (riid == IID_IMetaDataImport)
+ *ppvObj=static_cast<IMetaDataImport*>(this);
+ else
+ if (riid == IID_IUnknown)
+ *ppvObj=static_cast<IMetaDataImport*>(this);
+ else
+ return E_NOTIMPL;
+
+ AddRef();
+ return S_OK;
+}
+
+STDMETHODIMP_(ULONG) SymbolInfo::AddRef ()
+{
+ LIMITED_METHOD_CONTRACT;
+ return InterlockedIncrement(&m_cRef);
+}
+
+STDMETHODIMP_(ULONG) SymbolInfo::Release ()
+{
+ LIMITED_METHOD_CONTRACT;
+ ULONG retval=InterlockedDecrement(&m_cRef);
+ if(retval==0)
+ delete this;
+
+ return retval;
+}
+
+STDMETHODIMP SymbolInfo::GetTypeDefProps ( // S_OK or error.
+ mdTypeDef td, // [IN] TypeDef token for inquiry.
+ __out_ecount_part_opt(cchTypeDef, pchTypeDef)
+ LPWSTR szTypeDef, // [OUT] Put name here.
+ ULONG cchTypeDef, // [IN] size of name buffer in wide chars.
+ ULONG *pchTypeDef, // [OUT] put size of name (wide chars) here.
+ DWORD *pdwTypeDefFlags, // [OUT] Put flags here.
+ mdToken *ptkExtends) // [OUT] Put base class TypeDef/TypeRef here.
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ PRECONDITION(ptkExtends==NULL);
+ PRECONDITION(CheckPointer(szTypeDef));
+ }
+ CONTRACTL_END;
+
+ if (szTypeDef == NULL)
+ return E_POINTER;
+
+ ClassProps* classInfo=FindClass(td);
+ _ASSERTE(classInfo);
+ if(classInfo == NULL)
+ return E_UNEXPECTED;
+
+ if(pdwTypeDefFlags)
+ *pdwTypeDefFlags=classInfo->flags;
+
+
+ SIZE_T cch=wcslen(classInfo->wszName)+1;
+ if (cch > ULONG_MAX)
+ return E_UNEXPECTED;
+ *pchTypeDef=(ULONG)cch;
+
+ if (cchTypeDef < cch)
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+
+ wcscpy_s(szTypeDef,cchTypeDef,classInfo->wszName);
+
+ if(pdwTypeDefFlags)
+ *pdwTypeDefFlags=classInfo->flags;
+
+
+ return S_OK;
+}
+
+STDMETHODIMP SymbolInfo::GetMethodProps (
+ mdMethodDef mb, // The method for which to get props.
+ mdTypeDef *pClass, // Put method's class here.
+ __out_ecount_part_opt(cchMethod, *pchMethod)
+ LPWSTR szMethod, // Put method's name here.
+ ULONG cchMethod, // Size of szMethod buffer in wide chars.
+ ULONG *pchMethod, // Put actual size here
+ DWORD *pdwAttr, // Put flags here.
+ PCCOR_SIGNATURE *ppvSigBlob, // [OUT] point to the blob value of meta data
+ ULONG *pcbSigBlob, // [OUT] actual size of signature blob
+ ULONG *pulCodeRVA, // [OUT] codeRVA
+ DWORD *pdwImplFlags) // [OUT] Impl. Flags
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ PRECONDITION(m_LastMethod.method==mb);
+ PRECONDITION(pClass!=NULL);
+ PRECONDITION(pchMethod!=NULL);
+
+ PRECONDITION(pdwAttr == NULL);
+ PRECONDITION(ppvSigBlob == NULL);
+ PRECONDITION(pcbSigBlob == NULL);
+ PRECONDITION(pulCodeRVA == NULL);
+ PRECONDITION(pdwImplFlags == NULL);
+ PRECONDITION(CheckPointer(szMethod));
+ }
+ CONTRACTL_END;
+
+ if (szMethod == NULL)
+ return E_POINTER;
+
+
+ *pClass=m_LastMethod.cls;
+ SIZE_T cch=wcslen(m_LastMethod.wszName)+1;
+ if(cch > ULONG_MAX)
+ return E_UNEXPECTED;
+ *pchMethod=(ULONG)cch;
+
+ if (cchMethod < cch)
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+
+ wcscpy_s(szMethod,cchMethod,m_LastMethod.wszName);
+
+ return S_OK;
+}
+
+
+STDMETHODIMP SymbolInfo::GetNestedClassProps ( // S_OK or error.
+ mdTypeDef tdNestedClass, // [IN] NestedClass token.
+ mdTypeDef *ptdEnclosingClass) // [OUT] EnclosingClass token.
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ PRECONDITION(CheckPointer(ptdEnclosingClass));
+ }
+ CONTRACTL_END;
+
+ if(ptdEnclosingClass == NULL)
+ return E_POINTER;
+
+ ClassProps* classInfo=FindClass(tdNestedClass);
+ _ASSERTE(classInfo);
+ if(classInfo == NULL)
+ return E_UNEXPECTED;
+
+ *ptdEnclosingClass=classInfo->tkEnclosing;
+
+
+ return S_OK;
+
+}
+
+
+STDMETHODIMP SymbolInfo::GetTokenFromSig ( // S_OK or error.
+ PCCOR_SIGNATURE pvSig, // [IN] Signature to define.
+ ULONG cbSig, // [IN] Size of signature data.
+ mdSignature *pmsig) // [OUT] returned signature token.
+{
+ SBuffer sig;
+ sig.SetImmutable(pvSig,cbSig);
+ SignatureProps* sigProps=FindSignature(sig);
+ _ASSERTE(sigProps);
+ if(sigProps == NULL)
+ return E_UNEXPECTED;
+
+ *pmsig=sigProps->tkSig;
+ return S_OK;
+}
+
+
+//////////////////////////////////////////////
+// All the functions below are just stubs
+
+STDMETHODIMP_(void) SymbolInfo::CloseEnum (HCORENUM hEnum)
+{
+ _ASSERTE(!"NYI");
+}
+
+STDMETHODIMP SymbolInfo::CountEnum (HCORENUM hEnum, ULONG *pulCount)
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::ResetEnum (HCORENUM hEnum, ULONG ulPos)
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumTypeDefs (HCORENUM *phEnum, mdTypeDef rTypeDefs[],
+ ULONG cMax, ULONG *pcTypeDefs)
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumInterfaceImpls (HCORENUM *phEnum, mdTypeDef td,
+ mdInterfaceImpl rImpls[], ULONG cMax,
+ ULONG* pcImpls)
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumTypeRefs (HCORENUM *phEnum, mdTypeRef rTypeRefs[],
+ ULONG cMax, ULONG* pcTypeRefs)
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::FindTypeDefByName ( // S_OK or error.
+ LPCWSTR szTypeDef, // [IN] Name of the Type.
+ mdToken tkEnclosingClass, // [IN] TypeDef/TypeRef for Enclosing class.
+ mdTypeDef *ptd) // [OUT] Put the TypeDef token here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetScopeProps ( // S_OK or error.
+ __out_ecount_part_opt(cchName, *pchName)
+ LPWSTR szName, // [OUT] Put the name here.
+ ULONG cchName, // [IN] Size of name buffer in wide chars.
+ ULONG *pchName, // [OUT] Put size of name (wide chars) here.
+ GUID *pmvid) // [OUT, OPTIONAL] Put MVID here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetModuleFromScope ( // S_OK.
+ mdModule *pmd) // [OUT] Put mdModule token here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+
+STDMETHODIMP SymbolInfo::GetInterfaceImplProps ( // S_OK or error.
+ mdInterfaceImpl iiImpl, // [IN] InterfaceImpl token.
+ mdTypeDef *pClass, // [OUT] Put implementing class token here.
+ mdToken *ptkIface) // [OUT] Put implemented interface token here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetTypeRefProps ( // S_OK or error.
+ mdTypeRef tr, // [IN] TypeRef token.
+ mdToken *ptkResolutionScope, // [OUT] Resolution scope, ModuleRef or AssemblyRef.
+ __out_ecount_part_opt(cchName, *pchName)
+ LPWSTR szName, // [OUT] Name of the TypeRef.
+ ULONG cchName, // [IN] Size of buffer.
+ ULONG *pchName) // [OUT] Size of Name.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::ResolveTypeRef (mdTypeRef tr, REFIID riid, IUnknown **ppIScope, mdTypeDef *ptd)
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+
+STDMETHODIMP SymbolInfo::EnumMembers ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ mdToken rMembers[], // [OUT] Put MemberDefs here.
+ ULONG cMax, // [IN] Max MemberDefs to put.
+ ULONG *pcTokens) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumMembersWithName ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ LPCWSTR szName, // [IN] Limit results to those with this name.
+ mdToken rMembers[], // [OUT] Put MemberDefs here.
+ ULONG cMax, // [IN] Max MemberDefs to put.
+ ULONG *pcTokens) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumMethods ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ mdMethodDef rMethods[], // [OUT] Put MethodDefs here.
+ ULONG cMax, // [IN] Max MethodDefs to put.
+ ULONG *pcTokens) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumMethodsWithName ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ LPCWSTR szName, // [IN] Limit results to those with this name.
+ mdMethodDef rMethods[], // [OU] Put MethodDefs here.
+ ULONG cMax, // [IN] Max MethodDefs to put.
+ ULONG *pcTokens) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumFields ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ mdFieldDef rFields[], // [OUT] Put FieldDefs here.
+ ULONG cMax, // [IN] Max FieldDefs to put.
+ ULONG *pcTokens) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumFieldsWithName ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ LPCWSTR szName, // [IN] Limit results to those with this name.
+ mdFieldDef rFields[], // [OUT] Put MemberDefs here.
+ ULONG cMax, // [IN] Max MemberDefs to put.
+ ULONG *pcTokens) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+
+STDMETHODIMP SymbolInfo::EnumParams ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdMethodDef mb, // [IN] MethodDef to scope the enumeration.
+ mdParamDef rParams[], // [OUT] Put ParamDefs here.
+ ULONG cMax, // [IN] Max ParamDefs to put.
+ ULONG *pcTokens) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumMemberRefs ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdToken tkParent, // [IN] Parent token to scope the enumeration.
+ mdMemberRef rMemberRefs[], // [OUT] Put MemberRefs here.
+ ULONG cMax, // [IN] Max MemberRefs to put.
+ ULONG *pcTokens) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumMethodImpls ( // S_OK, S_FALSE, or error
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef td, // [IN] TypeDef to scope the enumeration.
+ mdToken rMethodBody[], // [OUT] Put Method Body tokens here.
+ mdToken rMethodDecl[], // [OUT] Put Method Declaration tokens here.
+ ULONG cMax, // [IN] Max tokens to put.
+ ULONG *pcTokens) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumPermissionSets ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdToken tk, // [IN] if !NIL, token to scope the enumeration.
+ DWORD dwActions, // [IN] if !0, return only these actions.
+ mdPermission rPermission[], // [OUT] Put Permissions here.
+ ULONG cMax, // [IN] Max Permissions to put.
+ ULONG *pcTokens) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::FindMember (
+ mdTypeDef td, // [IN] given typedef
+ LPCWSTR szName, // [IN] member name
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ mdToken *pmb) // [OUT] matching memberdef
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::FindMethod (
+ mdTypeDef td, // [IN] given typedef
+ LPCWSTR szName, // [IN] member name
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ mdMethodDef *pmb) // [OUT] matching memberdef
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::FindField (
+ mdTypeDef td, // [IN] given typedef
+ LPCWSTR szName, // [IN] member name
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ mdFieldDef *pmb) // [OUT] matching memberdef
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::FindMemberRef (
+ mdTypeRef td, // [IN] given typeRef
+ LPCWSTR szName, // [IN] member name
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ mdMemberRef *pmr) // [OUT] matching memberref
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+
+STDMETHODIMP SymbolInfo::GetMemberRefProps ( // S_OK or error.
+ mdMemberRef mr, // [IN] given memberref
+ mdToken *ptk, // [OUT] Put classref or classdef here.
+ __out_ecount_part_opt(cchMember, *pchMember)
+ LPWSTR szMember, // [OUT] buffer to fill for member's name
+ ULONG cchMember, // [IN] the count of char of szMember
+ ULONG *pchMember, // [OUT] actual count of char in member name
+ PCCOR_SIGNATURE *ppvSigBlob, // [OUT] point to meta data blob value
+ ULONG *pbSig) // [OUT] actual size of signature blob
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumProperties ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef td, // [IN] TypeDef to scope the enumeration.
+ mdProperty rProperties[], // [OUT] Put Properties here.
+ ULONG cMax, // [IN] Max properties to put.
+ ULONG *pcProperties) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumEvents ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef td, // [IN] TypeDef to scope the enumeration.
+ mdEvent rEvents[], // [OUT] Put events here.
+ ULONG cMax, // [IN] Max events to put.
+ ULONG *pcEvents) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetEventProps ( // S_OK, S_FALSE, or error.
+ mdEvent ev, // [IN] event token
+ mdTypeDef *pClass, // [OUT] typedef containing the event declarion.
+ LPCWSTR szEvent, // [OUT] Event name
+ ULONG cchEvent, // [IN] the count of wchar of szEvent
+ ULONG *pchEvent, // [OUT] actual count of wchar for event's name
+ DWORD *pdwEventFlags, // [OUT] Event flags.
+ mdToken *ptkEventType, // [OUT] EventType class
+ mdMethodDef *pmdAddOn, // [OUT] AddOn method of the event
+ mdMethodDef *pmdRemoveOn, // [OUT] RemoveOn method of the event
+ mdMethodDef *pmdFire, // [OUT] Fire method of the event
+ mdMethodDef rmdOtherMethod[], // [OUT] other method of the event
+ ULONG cMax, // [IN] size of rmdOtherMethod
+ ULONG *pcOtherMethod) // [OUT] total number of other method of this event
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumMethodSemantics ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdMethodDef mb, // [IN] MethodDef to scope the enumeration.
+ mdToken rEventProp[], // [OUT] Put Event/Property here.
+ ULONG cMax, // [IN] Max properties to put.
+ ULONG *pcEventProp) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetMethodSemantics ( // S_OK, S_FALSE, or error.
+ mdMethodDef mb, // [IN] method token
+ mdToken tkEventProp, // [IN] event/property token.
+ DWORD *pdwSemanticsFlags) // [OUT] the role flags for the method/propevent pair
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetClassLayout (
+ mdTypeDef td, // [IN] give typedef
+ DWORD *pdwPackSize, // [OUT] 1, 2, 4, 8, or 16
+ COR_FIELD_OFFSET rFieldOffset[], // [OUT] field offset array
+ ULONG cMax, // [IN] size of the array
+ ULONG *pcFieldOffset, // [OUT] needed array size
+ ULONG *pulClassSize) // [OUT] the size of the class
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetFieldMarshal (
+ mdToken tk, // [IN] given a field's memberdef
+ PCCOR_SIGNATURE *ppvNativeType, // [OUT] native type of this field
+ ULONG *pcbNativeType) // [OUT] the count of bytes of *ppvNativeType
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetRVA ( // S_OK or error.
+ mdToken tk, // Member for which to set offset
+ ULONG *pulCodeRVA, // The offset
+ DWORD *pdwImplFlags) // the implementation flags
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetPermissionSetProps (
+ mdPermission pm, // [IN] the permission token.
+ DWORD *pdwAction, // [OUT] CorDeclSecurity.
+ void const **ppvPermission, // [OUT] permission blob.
+ ULONG *pcbPermission) // [OUT] count of bytes of pvPermission.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetSigFromToken ( // S_OK or error.
+ mdSignature mdSig, // [IN] Signature token.
+ PCCOR_SIGNATURE *ppvSig, // [OUT] return pointer to token.
+ ULONG *pcbSig) // [OUT] return size of signature.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetModuleRefProps ( // S_OK or error.
+ mdModuleRef mur, // [IN] moduleref token.
+ __out_ecount_part_opt(cchName, *pchName)
+ LPWSTR szName, // [OUT] buffer to fill with the moduleref name.
+ ULONG cchName, // [IN] size of szName in wide characters.
+ ULONG *pchName) // [OUT] actual count of characters in the name.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumModuleRefs ( // S_OK or error.
+ HCORENUM *phEnum, // [IN|OUT] pointer to the enum.
+ mdModuleRef rModuleRefs[], // [OUT] put modulerefs here.
+ ULONG cmax, // [IN] max memberrefs to put.
+ ULONG *pcModuleRefs) // [OUT] put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetTypeSpecFromToken ( // S_OK or error.
+ mdTypeSpec typespec, // [IN] TypeSpec token.
+ PCCOR_SIGNATURE *ppvSig, // [OUT] return pointer to TypeSpec signature
+ ULONG *pcbSig) // [OUT] return size of signature.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetNameFromToken ( // Not Recommended! May be removed!
+ mdToken tk, // [IN] Token to get name from. Must have a name.
+ MDUTF8CSTR *pszUtf8NamePtr) // [OUT] Return pointer to UTF8 name in heap.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumUnresolvedMethods ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdToken rMethods[], // [OUT] Put MemberDefs here.
+ ULONG cMax, // [IN] Max MemberDefs to put.
+ ULONG *pcTokens) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetUserString ( // S_OK or error.
+ mdString stk, // [IN] String token.
+ __out_ecount_part_opt(cchString, *pchString)
+ LPWSTR szString, // [OUT] Copy of string.
+ ULONG cchString, // [IN] Max chars of room in szString.
+ ULONG *pchString) // [OUT] How many chars in actual string.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetPinvokeMap ( // S_OK or error.
+ mdToken tk, // [IN] FieldDef or MethodDef.
+ DWORD *pdwMappingFlags, // [OUT] Flags used for mapping.
+ __out_ecount_part_opt(cchImportName, *pchImportName)
+ LPWSTR szImportName, // [OUT] Import name.
+ ULONG cchImportName, // [IN] Size of the name buffer.
+ ULONG *pchImportName, // [OUT] Actual number of characters stored.
+ mdModuleRef *pmrImportDLL) // [OUT] ModuleRef token for the target DLL.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumSignatures ( // S_OK or error.
+ HCORENUM *phEnum, // [IN|OUT] pointer to the enum.
+ mdSignature rSignatures[], // [OUT] put signatures here.
+ ULONG cmax, // [IN] max signatures to put.
+ ULONG *pcSignatures) // [OUT] put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumTypeSpecs ( // S_OK or error.
+ HCORENUM *phEnum, // [IN|OUT] pointer to the enum.
+ mdTypeSpec rTypeSpecs[], // [OUT] put TypeSpecs here.
+ ULONG cmax, // [IN] max TypeSpecs to put.
+ ULONG *pcTypeSpecs) // [OUT] put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumUserStrings ( // S_OK or error.
+ HCORENUM *phEnum, // [IN/OUT] pointer to the enum.
+ mdString rStrings[], // [OUT] put Strings here.
+ ULONG cmax, // [IN] max Strings to put.
+ ULONG *pcStrings) // [OUT] put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetParamForMethodIndex ( // S_OK or error.
+ mdMethodDef md, // [IN] Method token.
+ ULONG ulParamSeq, // [IN] Parameter sequence.
+ mdParamDef *ppd) // [IN] Put Param token here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumCustomAttributes ( // S_OK or error.
+ HCORENUM *phEnum, // [IN, OUT] COR enumerator.
+ mdToken tk, // [IN] Token to scope the enumeration, 0 for all.
+ mdToken tkType, // [IN] Type of interest, 0 for all.
+ mdCustomAttribute rCustomAttributes[], // [OUT] Put custom attribute tokens here.
+ ULONG cMax, // [IN] Size of rCustomAttributes.
+ ULONG *pcCustomAttributes) // [OUT, OPTIONAL] Put count of token values here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetCustomAttributeProps ( // S_OK or error.
+ mdCustomAttribute cv, // [IN] CustomAttribute token.
+ mdToken *ptkObj, // [OUT, OPTIONAL] Put object token here.
+ mdToken *ptkType, // [OUT, OPTIONAL] Put AttrType token here.
+ void const **ppBlob, // [OUT, OPTIONAL] Put pointer to data here.
+ ULONG *pcbSize) // [OUT, OPTIONAL] Put size of date here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::FindTypeRef (
+ mdToken tkResolutionScope, // [IN] ModuleRef, AssemblyRef or TypeRef.
+ LPCWSTR szName, // [IN] TypeRef Name.
+ mdTypeRef *ptr) // [OUT] matching TypeRef.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetMemberProps (
+ mdToken mb, // The member for which to get props.
+ mdTypeDef *pClass, // Put member's class here.
+ __out_ecount_part_opt(cchMember, *pchMember)
+ LPWSTR szMember, // Put member's name here.
+ ULONG cchMember, // Size of szMember buffer in wide chars.
+ ULONG *pchMember, // Put actual size here
+ DWORD *pdwAttr, // Put flags here.
+ PCCOR_SIGNATURE *ppvSigBlob, // [OUT] point to the blob value of meta data
+ ULONG *pcbSigBlob, // [OUT] actual size of signature blob
+ ULONG *pulCodeRVA, // [OUT] codeRVA
+ DWORD *pdwImplFlags, // [OUT] Impl. Flags
+ DWORD *pdwCPlusTypeFlag, // [OUT] flag for value type. selected ELEMENT_TYPE_*
+ UVCP_CONSTANT *ppValue, // [OUT] constant value
+ ULONG *pcchValue) // [OUT] size of constant string in chars, 0 for non-strings.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetFieldProps (
+ mdFieldDef mb, // The field for which to get props.
+ mdTypeDef *pClass, // Put field's class here.
+ __out_ecount_part_opt(cchField, *pchField)
+ LPWSTR szField, // Put field's name here.
+ ULONG cchField, // Size of szField buffer in wide chars.
+ ULONG *pchField, // Put actual size here
+ DWORD *pdwAttr, // Put flags here.
+ PCCOR_SIGNATURE *ppvSigBlob, // [OUT] point to the blob value of meta data
+ ULONG *pcbSigBlob, // [OUT] actual size of signature blob
+ DWORD *pdwCPlusTypeFlag, // [OUT] flag for value type. selected ELEMENT_TYPE_*
+ UVCP_CONSTANT *ppValue, // [OUT] constant value
+ ULONG *pcchValue) // [OUT] size of constant string in chars, 0 for non-strings.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetPropertyProps ( // S_OK, S_FALSE, or error.
+ mdProperty prop, // [IN] property token
+ mdTypeDef *pClass, // [OUT] typedef containing the property declarion.
+ LPCWSTR szProperty, // [OUT] Property name
+ ULONG cchProperty, // [IN] the count of wchar of szProperty
+ ULONG *pchProperty, // [OUT] actual count of wchar for property name
+ DWORD *pdwPropFlags, // [OUT] property flags.
+ PCCOR_SIGNATURE *ppvSig, // [OUT] property type. pointing to meta data internal blob
+ ULONG *pbSig, // [OUT] count of bytes in *ppvSig
+ DWORD *pdwCPlusTypeFlag, // [OUT] flag for value type. selected ELEMENT_TYPE_*
+ UVCP_CONSTANT *ppDefaultValue, // [OUT] constant value
+ ULONG *pcchDefaultValue, // [OUT] size of constant string in chars, 0 for non-strings.
+ mdMethodDef *pmdSetter, // [OUT] setter method of the property
+ mdMethodDef *pmdGetter, // [OUT] getter method of the property
+ mdMethodDef rmdOtherMethod[], // [OUT] other method of the property
+ ULONG cMax, // [IN] size of rmdOtherMethod
+ ULONG *pcOtherMethod) // [OUT] total number of other method of this property
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetParamProps ( // S_OK or error.
+ mdParamDef tk, // [IN]The Parameter.
+ mdMethodDef *pmd, // [OUT] Parent Method token.
+ ULONG *pulSequence, // [OUT] Parameter sequence.
+ __out_ecount_part_opt(cchName, *pchName)
+ LPWSTR szName, // [OUT] Put name here.
+ ULONG cchName, // [OUT] Size of name buffer.
+ ULONG *pchName, // [OUT] Put actual size of name here.
+ DWORD *pdwAttr, // [OUT] Put flags here.
+ DWORD *pdwCPlusTypeFlag, // [OUT] Flag for value type. selected ELEMENT_TYPE_*.
+ UVCP_CONSTANT *ppValue, // [OUT] Constant value.
+ ULONG *pcchValue) // [OUT] size of constant string in chars, 0 for non-strings.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetCustomAttributeByName ( // S_OK or error.
+ mdToken tkObj, // [IN] Object with Custom Attribute.
+ LPCWSTR szName, // [IN] Name of desired Custom Attribute.
+ const void **ppData, // [OUT] Put pointer to data here.
+ ULONG *pcbData) // [OUT] Put size of data here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP_(BOOL) SymbolInfo::IsValidToken ( // True or False.
+ mdToken tk) // [IN] Given token.
+{
+ _ASSERTE(!"NYI");
+ return FALSE;
+}
+
+
+STDMETHODIMP SymbolInfo::GetNativeCallConvFromSig ( // S_OK or error.
+ void const *pvSig, // [IN] Pointer to signature.
+ ULONG cbSig, // [IN] Count of signature bytes.
+ ULONG *pCallConv) // [OUT] Put calling conv here (see CorPinvokemap).
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::IsGlobal ( // S_OK or error.
+ mdToken pd, // [IN] Type, Field, or Method token.
+ int *pbGlobal) // [OUT] Put 1 if global, 0 otherwise.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+
+// IMetaDataEmit functions
+
+STDMETHODIMP SymbolInfo::SetModuleProps ( // S_OK or error.
+ LPCWSTR szName) // [IN] If not NULL, the name of the module to set.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::Save ( // S_OK or error.
+ LPCWSTR szFile, // [IN] The filename to save to.
+ DWORD dwSaveFlags) // [IN] Flags for the save.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SaveToStream ( // S_OK or error.
+ IStream *pIStream, // [IN] A writable stream to save to.
+ DWORD dwSaveFlags) // [IN] Flags for the save.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetSaveSize ( // S_OK or error.
+ CorSaveSize fSave, // [IN] cssAccurate or cssQuick.
+ DWORD *pdwSaveSize) // [OUT] Put the size here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineTypeDef ( // S_OK or error.
+ LPCWSTR szTypeDef, // [IN] Name of TypeDef
+ DWORD dwTypeDefFlags, // [IN] CustomAttribute flags
+ mdToken tkExtends, // [IN] extends this TypeDef or typeref
+ mdToken rtkImplements[], // [IN] Implements interfaces
+ mdTypeDef *ptd) // [OUT] Put TypeDef token here
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineNestedType ( // S_OK or error.
+ LPCWSTR szTypeDef, // [IN] Name of TypeDef
+ DWORD dwTypeDefFlags, // [IN] CustomAttribute flags
+ mdToken tkExtends, // [IN] extends this TypeDef or typeref
+ mdToken rtkImplements[], // [IN] Implements interfaces
+ mdTypeDef tdEncloser, // [IN] TypeDef token of the enclosing type.
+ mdTypeDef *ptd) // [OUT] Put TypeDef token here
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetHandler ( // S_OK.
+ IUnknown *pUnk) // [IN] The new error handler.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineMethod ( // S_OK or error.
+ mdTypeDef td, // Parent TypeDef
+ LPCWSTR szName, // Name of member
+ DWORD dwMethodFlags, // Member attributes
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ ULONG ulCodeRVA,
+ DWORD dwImplFlags,
+ mdMethodDef *pmd) // Put member token here
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineMethodImpl ( // S_OK or error.
+ mdTypeDef td, // [IN] The class implementing the method
+ mdToken tkBody, // [IN] Method body - MethodDef or MethodRef
+ mdToken tkDecl) // [IN] Method declaration - MethodDef or MethodRef
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineTypeRefByName ( // S_OK or error.
+ mdToken tkResolutionScope, // [IN] ModuleRef, AssemblyRef or TypeRef.
+ LPCWSTR szName, // [IN] Name of the TypeRef.
+ mdTypeRef *ptr) // [OUT] Put TypeRef token here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineImportType ( // S_OK or error.
+ IMetaDataAssemblyImport *pAssemImport, // [IN] Assembly containing the TypeDef.
+ const void *pbHashValue, // [IN] Hash Blob for Assembly.
+ ULONG cbHashValue, // [IN] Count of bytes.
+ IMetaDataImport *pImport, // [IN] Scope containing the TypeDef.
+ mdTypeDef tdImport, // [IN] The imported TypeDef.
+ IMetaDataAssemblyEmit *pAssemEmit, // [IN] Assembly into which the TypeDef is imported.
+ mdTypeRef *ptr) // [OUT] Put TypeRef token here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineMemberRef ( // S_OK or error
+ mdToken tkImport, // [IN] ClassRef or ClassDef importing a member.
+ LPCWSTR szName, // [IN] member's name
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ mdMemberRef *pmr) // [OUT] memberref token
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineImportMember ( // S_OK or error.
+ IMetaDataAssemblyImport *pAssemImport, // [IN] Assembly containing the Member.
+ const void *pbHashValue, // [IN] Hash Blob for Assembly.
+ ULONG cbHashValue, // [IN] Count of bytes.
+ IMetaDataImport *pImport, // [IN] Import scope, with member.
+ mdToken mbMember, // [IN] Member in import scope.
+ IMetaDataAssemblyEmit *pAssemEmit, // [IN] Assembly into which the Member is imported.
+ mdToken tkParent, // [IN] Classref or classdef in emit scope.
+ mdMemberRef *pmr) // [OUT] Put member ref here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineEvent(
+ mdTypeDef td, // [IN] the class/interface on which the event is being defined
+ LPCWSTR szEvent, // [IN] Name of the event
+ DWORD dwEventFlags, // [IN] CorEventAttr
+ mdToken tkEventType, // [IN] a reference (mdTypeRef or mdTypeRef) to the Event class
+ mdMethodDef mdAddOn, // [IN] required add method
+ mdMethodDef mdRemoveOn, // [IN] required remove method
+ mdMethodDef mdFire, // [IN] optional fire method
+ mdMethodDef rmdOtherMethods[], // [IN] optional array of other methods associate with the event
+ mdEvent *pmdEvent) // [OUT] output event token
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetClassLayout(
+ mdTypeDef td, // [IN] typedef
+ DWORD dwPackSize, // [IN] packing size specified as 1, 2, 4, 8, or 16
+ COR_FIELD_OFFSET rFieldOffsets[], // [IN] array of layout specification
+ ULONG ulClassSize) // [IN] size of the class
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DeleteClassLayout(
+ mdTypeDef td) // [IN] typedef whose layout is to be deleted.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetFieldMarshal(
+ mdToken tk, // [IN] given a fieldDef or paramDef token
+ PCCOR_SIGNATURE pvNativeType, // [IN] native type specification
+ ULONG cbNativeType) // [IN] count of bytes of pvNativeType
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DeleteFieldMarshal(
+ mdToken tk) // [IN] given a fieldDef or paramDef token
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefinePermissionSet(
+ mdToken tk, // [IN] the object to be decorated.
+ DWORD dwAction, // [IN] CorDeclSecurity.
+ void const *pvPermission, // [IN] permission blob.
+ ULONG cbPermission, // [IN] count of bytes of pvPermission.
+ mdPermission *ppm) // [OUT] returned permission token.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetRVA ( // S_OK or error.
+ mdMethodDef md, // [IN] Method for which to set offset
+ ULONG ulRVA) // [IN] The offset
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineModuleRef ( // S_OK or error.
+ LPCWSTR szName, // [IN] DLL name
+ mdModuleRef *pmur) // [OUT] returned
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetParent ( // S_OK or error.
+ mdMemberRef mr, // [IN] Token for the ref to be fixed up.
+ mdToken tk) // [IN] The ref parent.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetTokenFromTypeSpec ( // S_OK or error.
+ PCCOR_SIGNATURE pvSig, // [IN] TypeSpec Signature to define.
+ ULONG cbSig, // [IN] Size of signature data.
+ mdTypeSpec *ptypespec) // [OUT] returned TypeSpec token.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SaveToMemory ( // S_OK or error.
+ void *pbData, // [OUT] Location to write data.
+ ULONG cbData) // [IN] Max size of data buffer.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineUserString ( // Return code.
+ LPCWSTR szString, // [IN] User literal string.
+ ULONG cchString, // [IN] Length of string.
+ mdString *pstk) // [OUT] String token.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DeleteToken ( // Return code.
+ mdToken tkObj) // [IN] The token to be deleted
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetMethodProps ( // S_OK or error.
+ mdMethodDef md, // [IN] The MethodDef.
+ DWORD dwMethodFlags, // [IN] Method attributes.
+ ULONG ulCodeRVA, // [IN] Code RVA.
+ DWORD dwImplFlags) // [IN] Impl flags.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetTypeDefProps ( // S_OK or error.
+ mdTypeDef td, // [IN] The TypeDef.
+ DWORD dwTypeDefFlags, // [IN] TypeDef flags.
+ mdToken tkExtends, // [IN] Base TypeDef or TypeRef.
+ mdToken rtkImplements[]) // [IN] Implemented interfaces.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetEventProps ( // S_OK or error.
+ mdEvent ev, // [IN] The event token.
+ DWORD dwEventFlags, // [IN] CorEventAttr.
+ mdToken tkEventType, // [IN] A reference (mdTypeRef or mdTypeRef) to the Event class.
+ mdMethodDef mdAddOn, // [IN] Add method.
+ mdMethodDef mdRemoveOn, // [IN] Remove method.
+ mdMethodDef mdFire, // [IN] Fire method.
+ mdMethodDef rmdOtherMethods[])// [IN] Array of other methods associate with the event.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetPermissionSetProps ( // S_OK or error.
+ mdToken tk, // [IN] The object to be decorated.
+ DWORD dwAction, // [IN] CorDeclSecurity.
+ void const *pvPermission, // [IN] Permission blob.
+ ULONG cbPermission, // [IN] Count of bytes of pvPermission.
+ mdPermission *ppm) // [OUT] Permission token.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefinePinvokeMap ( // Return code.
+ mdToken tk, // [IN] FieldDef or MethodDef.
+ DWORD dwMappingFlags, // [IN] Flags used for mapping.
+ LPCWSTR szImportName, // [IN] Import name.
+ mdModuleRef mrImportDLL) // [IN] ModuleRef token for the target DLL.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetPinvokeMap ( // Return code.
+ mdToken tk, // [IN] FieldDef or MethodDef.
+ DWORD dwMappingFlags, // [IN] Flags used for mapping.
+ LPCWSTR szImportName, // [IN] Import name.
+ mdModuleRef mrImportDLL) // [IN] ModuleRef token for the target DLL.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DeletePinvokeMap ( // Return code.
+ mdToken tk) // [IN] FieldDef or MethodDef.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+// New CustomAttribute functions.
+STDMETHODIMP SymbolInfo::DefineCustomAttribute ( // Return code.
+ mdToken tkObj, // [IN] The object to put the value on.
+ mdToken tkType, // [IN] Type of the CustomAttribute (TypeRef/TypeDef).
+ void const *pCustomAttribute, // [IN] The custom value data.
+ ULONG cbCustomAttribute, // [IN] The custom value data length.
+ mdCustomAttribute *pcv) // [OUT] The custom value token value on return.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetCustomAttributeValue ( // Return code.
+ mdCustomAttribute pcv, // [IN] The custom value token whose value to replace.
+ void const *pCustomAttribute, // [IN] The custom value data.
+ ULONG cbCustomAttribute)// [IN] The custom value data length.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineField ( // S_OK or error.
+ mdTypeDef td, // Parent TypeDef
+ LPCWSTR szName, // Name of member
+ DWORD dwFieldFlags, // Member attributes
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ DWORD dwCPlusTypeFlag, // [IN] flag for value type. selected ELEMENT_TYPE_*
+ void const *pValue, // [IN] constant value
+ ULONG cchValue, // [IN] size of constant value (string, in wide chars).
+ mdFieldDef *pmd) // [OUT] Put member token here
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineProperty (
+ mdTypeDef td, // [IN] the class/interface on which the property is being defined
+ LPCWSTR szProperty, // [IN] Name of the property
+ DWORD dwPropFlags, // [IN] CorPropertyAttr
+ PCCOR_SIGNATURE pvSig, // [IN] the required type signature
+ ULONG cbSig, // [IN] the size of the type signature blob
+ DWORD dwCPlusTypeFlag, // [IN] flag for value type. selected ELEMENT_TYPE_*
+ void const *pValue, // [IN] constant value
+ ULONG cchValue, // [IN] size of constant value (string, in wide chars).
+ mdMethodDef mdSetter, // [IN] optional setter of the property
+ mdMethodDef mdGetter, // [IN] optional getter of the property
+ mdMethodDef rmdOtherMethods[], // [IN] an optional array of other methods
+ mdProperty *pmdProp) // [OUT] output property token
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineParam (
+ mdMethodDef md, // [IN] Owning method
+ ULONG ulParamSeq, // [IN] Which param
+ LPCWSTR szName, // [IN] Optional param name
+ DWORD dwParamFlags, // [IN] Optional param flags
+ DWORD dwCPlusTypeFlag, // [IN] flag for value type. selected ELEMENT_TYPE_*
+ void const *pValue, // [IN] constant value
+ ULONG cchValue, // [IN] size of constant value (string, in wide chars).
+ mdParamDef *ppd) // [OUT] Put param token here
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetFieldProps ( // S_OK or error.
+ mdFieldDef fd, // [IN] The FieldDef.
+ DWORD dwFieldFlags, // [IN] Field attributes.
+ DWORD dwCPlusTypeFlag, // [IN] Flag for the value type, selected ELEMENT_TYPE_*
+ void const *pValue, // [IN] Constant value.
+ ULONG cchValue) // [IN] size of constant value (string, in wide chars).
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetPropertyProps ( // S_OK or error.
+ mdProperty pr, // [IN] Property token.
+ DWORD dwPropFlags, // [IN] CorPropertyAttr.
+ DWORD dwCPlusTypeFlag, // [IN] Flag for value type, selected ELEMENT_TYPE_*
+ void const *pValue, // [IN] Constant value.
+ ULONG cchValue, // [IN] size of constant value (string, in wide chars).
+ mdMethodDef mdSetter, // [IN] Setter of the property.
+ mdMethodDef mdGetter, // [IN] Getter of the property.
+ mdMethodDef rmdOtherMethods[])// [IN] Array of other methods.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetParamProps ( // Return code.
+ mdParamDef pd, // [IN] Param token.
+ LPCWSTR szName, // [IN] Param name.
+ DWORD dwParamFlags, // [IN] Param flags.
+ DWORD dwCPlusTypeFlag, // [IN] Flag for value type. selected ELEMENT_TYPE_*.
+ void const *pValue, // [OUT] Constant value.
+ ULONG cchValue) // [IN] size of constant value (string, in wide chars).
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+// Specialized Custom Attributes for security.
+STDMETHODIMP SymbolInfo::DefineSecurityAttributeSet ( // Return code.
+ mdToken tkObj, // [IN] Class or method requiring security attributes.
+ COR_SECATTR rSecAttrs[], // [IN] Array of security attribute descriptions.
+ ULONG cSecAttrs, // [IN] Count of elements in above array.
+ ULONG *pulErrorAttr) // [OUT] On error, index of attribute causing problem.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::ApplyEditAndContinue ( // S_OK or error.
+ IUnknown *pImport) // [IN] Metadata from the delta PE.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::TranslateSigWithScope (
+ IMetaDataAssemblyImport *pAssemImport, // [IN] importing assembly interface
+ const void *pbHashValue, // [IN] Hash Blob for Assembly.
+ ULONG cbHashValue, // [IN] Count of bytes.
+ IMetaDataImport *import, // [IN] importing interface
+ PCCOR_SIGNATURE pbSigBlob, // [IN] signature in the importing scope
+ ULONG cbSigBlob, // [IN] count of bytes of signature
+ IMetaDataAssemblyEmit *pAssemEmit, // [IN] emit assembly interface
+ IMetaDataEmit *emit, // [IN] emit interface
+ PCOR_SIGNATURE pvTranslatedSig, // [OUT] buffer to hold translated signature
+ ULONG cbTranslatedSigMax,
+ ULONG *pcbTranslatedSig)// [OUT] count of bytes in the translated signature
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetMethodImplFlags ( // [IN] S_OK or error.
+ mdMethodDef md, // [IN] Method for which to set ImplFlags
+ DWORD dwImplFlags)
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetFieldRVA ( // [IN] S_OK or error.
+ mdFieldDef fd, // [IN] Field for which to set offset
+ ULONG ulRVA) // [IN] The offset
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::Merge ( // S_OK or error.
+ IMetaDataImport *pImport, // [IN] The scope to be merged.
+ IMapToken *pHostMapToken, // [IN] Host IMapToken interface to receive token remap notification
+ IUnknown *pHandler) // [IN] An object to receive to receive error notification.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::MergeEnd () // S_OK or error.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+
diff --git a/src/debug/di/symbolinfo.h b/src/debug/di/symbolinfo.h
new file mode 100644
index 0000000000..a4402cb7e6
--- /dev/null
+++ b/src/debug/di/symbolinfo.h
@@ -0,0 +1,816 @@
+// 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.
+
+
+// callbacks for diasymreader when using SymConverter
+
+
+#ifndef SYMBOLINFO_H
+#define SYMBOLINFO_H
+#include "arraylist.h"
+#include "shash.h"
+#include "corsym.h"
+#include "sstring.h"
+
+class SymbolInfo: IMetaDataEmit, IMetaDataImport
+{
+
+ struct ScopeMap
+ {
+ ULONG32 left;
+ ULONG32 right;
+ ScopeMap(ULONG32 l, ULONG32 r):left(l), right(r){};
+ ULONG32 GetKey() {return left;};
+ static COUNT_T Hash(ULONG32 key) {return key;};
+ };
+
+ struct MethodProps
+ {
+ mdMethodDef method;
+ mdTypeDef cls;
+ SString wszName;
+ MethodProps() : method(mdTokenNil) {};
+ };
+
+ struct ClassProps
+ {
+ mdTypeDef cls;
+ DWORD flags;
+ SString wszName;
+ mdTypeDef tkEnclosing;
+ ClassProps(mdToken c, DWORD f, LPCWSTR name, mdToken tkE):
+ cls(c),flags(f),wszName(name),tkEnclosing(tkE) {wszName.Normalize();};
+
+ mdTypeDef GetKey() {return cls;};
+ static COUNT_T Hash(mdTypeDef key) {return key;};
+ };
+
+ struct SignatureProps
+ {
+ SBuffer sig;
+ DWORD tkSig;
+ SignatureProps(SBuffer& signature, DWORD token):
+ tkSig(token) {sig.Set(signature);};
+ SBuffer& GetKey() {return sig;};
+ static COUNT_T Hash(SBuffer& key) {return HashBytes(key,key.GetSize());};
+ };
+
+ Volatile<LONG> m_cRef;
+ ArrayList m_Documents;
+ PtrSHashWithCleanup<ScopeMap,ULONG32> m_Scopes;
+ PtrSHashWithCleanup<SignatureProps,SBuffer&> m_Signatures;
+ PtrSHashWithCleanup<ClassProps,mdTypeDef> m_Classes;
+
+ MethodProps m_LastMethod; // diasymreader supports only one open method at a time
+
+ ClassProps* FindClass(mdToken cls);
+ SignatureProps* FindSignature(SBuffer& sig);
+
+ virtual ~SymbolInfo(); // protected, b/c the lifetime is controlled by refcount
+
+public:
+ SymbolInfo();
+
+ HRESULT AddDocument(DWORD id, ISymUnmanagedDocumentWriter* Document);
+ HRESULT MapDocument(DWORD id, ISymUnmanagedDocumentWriter** Document);
+ HRESULT AddScope(ULONG32 left, ULONG32 right);
+ HRESULT MapScope(ULONG32 left, ULONG32* pRight);
+ HRESULT SetMethodProps(mdToken method, mdTypeDef cls, LPCWSTR wszName);
+ HRESULT SetClassProps(mdTypeDef cls, DWORD flags, LPCWSTR wszName, mdTypeDef enclosingCls);
+ HRESULT AddSignature(SBuffer& sig, mdSignature token);
+
+ // IUnknown methods
+ STDMETHOD(QueryInterface) (REFIID riid, LPVOID * ppvObj);
+ STDMETHOD_(ULONG,AddRef) ();
+ STDMETHOD_(ULONG,Release) ();
+
+
+ // IMetaDataImport functions
+
+ STDMETHOD_(void, CloseEnum)(HCORENUM hEnum);
+ STDMETHOD(CountEnum)(HCORENUM hEnum, ULONG *pulCount);
+ STDMETHOD(ResetEnum)(HCORENUM hEnum, ULONG ulPos);
+ STDMETHOD(EnumTypeDefs)(HCORENUM *phEnum, mdTypeDef rTypeDefs[],
+ ULONG cMax, ULONG *pcTypeDefs);
+ STDMETHOD(EnumInterfaceImpls)(HCORENUM *phEnum, mdTypeDef td,
+ mdInterfaceImpl rImpls[], ULONG cMax,
+ ULONG* pcImpls);
+ STDMETHOD(EnumTypeRefs)(HCORENUM *phEnum, mdTypeRef rTypeRefs[],
+ ULONG cMax, ULONG* pcTypeRefs);
+
+ STDMETHOD(FindTypeDefByName)( // S_OK or error.
+ LPCWSTR szTypeDef, // [IN] Name of the Type.
+ mdToken tkEnclosingClass, // [IN] TypeDef/TypeRef for Enclosing class.
+ mdTypeDef *ptd); // [OUT] Put the TypeDef token here.
+
+ STDMETHOD(GetScopeProps)( // S_OK or error.
+ __out_ecount_part_opt(cchName, *pchName)
+ LPWSTR szName, // [OUT] Put the name here.
+ ULONG cchName, // [IN] Size of name buffer in wide chars.
+ ULONG *pchName, // [OUT] Put size of name (wide chars) here.
+ GUID *pmvid); // [OUT, OPTIONAL] Put MVID here.
+
+ STDMETHOD(GetModuleFromScope)( // S_OK.
+ mdModule *pmd); // [OUT] Put mdModule token here.
+
+ STDMETHOD(GetTypeDefProps)( // S_OK or error.
+ mdTypeDef td, // [IN] TypeDef token for inquiry.
+ __out_ecount_part_opt(cchTypeDef, *pchTypeDef)
+ LPWSTR szTypeDef, // [OUT] Put name here.
+ ULONG cchTypeDef, // [IN] size of name buffer in wide chars.
+ ULONG *pchTypeDef, // [OUT] put size of name (wide chars) here.
+ DWORD *pdwTypeDefFlags, // [OUT] Put flags here.
+ mdToken *ptkExtends); // [OUT] Put base class TypeDef/TypeRef here.
+
+ STDMETHOD(GetInterfaceImplProps)( // S_OK or error.
+ mdInterfaceImpl iiImpl, // [IN] InterfaceImpl token.
+ mdTypeDef *pClass, // [OUT] Put implementing class token here.
+ mdToken *ptkIface); // [OUT] Put implemented interface token here.
+
+ STDMETHOD(GetTypeRefProps)( // S_OK or error.
+ mdTypeRef tr, // [IN] TypeRef token.
+ mdToken *ptkResolutionScope, // [OUT] Resolution scope, ModuleRef or AssemblyRef.
+ __out_ecount_part_opt(cchName, *pchName)
+ LPWSTR szName, // [OUT] Name of the TypeRef.
+ ULONG cchName, // [IN] Size of buffer.
+ ULONG *pchName); // [OUT] Size of Name.
+
+ STDMETHOD(ResolveTypeRef)(mdTypeRef tr, REFIID riid, IUnknown **ppIScope, mdTypeDef *ptd);
+
+ STDMETHOD(EnumMembers)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ mdToken rMembers[], // [OUT] Put MemberDefs here.
+ ULONG cMax, // [IN] Max MemberDefs to put.
+ ULONG *pcTokens); // [OUT] Put # put here.
+
+ STDMETHOD(EnumMembersWithName)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ LPCWSTR szName, // [IN] Limit results to those with this name.
+ mdToken rMembers[], // [OUT] Put MemberDefs here.
+ ULONG cMax, // [IN] Max MemberDefs to put.
+ ULONG *pcTokens); // [OUT] Put # put here.
+
+ STDMETHOD(EnumMethods)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ mdMethodDef rMethods[], // [OUT] Put MethodDefs here.
+ ULONG cMax, // [IN] Max MethodDefs to put.
+ ULONG *pcTokens); // [OUT] Put # put here.
+
+ STDMETHOD(EnumMethodsWithName)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ LPCWSTR szName, // [IN] Limit results to those with this name.
+ mdMethodDef rMethods[], // [OU] Put MethodDefs here.
+ ULONG cMax, // [IN] Max MethodDefs to put.
+ ULONG *pcTokens); // [OUT] Put # put here.
+
+ STDMETHOD(EnumFields)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ mdFieldDef rFields[], // [OUT] Put FieldDefs here.
+ ULONG cMax, // [IN] Max FieldDefs to put.
+ ULONG *pcTokens); // [OUT] Put # put here.
+
+ STDMETHOD(EnumFieldsWithName)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ LPCWSTR szName, // [IN] Limit results to those with this name.
+ mdFieldDef rFields[], // [OUT] Put MemberDefs here.
+ ULONG cMax, // [IN] Max MemberDefs to put.
+ ULONG *pcTokens); // [OUT] Put # put here.
+
+
+ STDMETHOD(EnumParams)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdMethodDef mb, // [IN] MethodDef to scope the enumeration.
+ mdParamDef rParams[], // [OUT] Put ParamDefs here.
+ ULONG cMax, // [IN] Max ParamDefs to put.
+ ULONG *pcTokens); // [OUT] Put # put here.
+
+ STDMETHOD(EnumMemberRefs)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdToken tkParent, // [IN] Parent token to scope the enumeration.
+ mdMemberRef rMemberRefs[], // [OUT] Put MemberRefs here.
+ ULONG cMax, // [IN] Max MemberRefs to put.
+ ULONG *pcTokens); // [OUT] Put # put here.
+
+ STDMETHOD(EnumMethodImpls)( // S_OK, S_FALSE, or error
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef td, // [IN] TypeDef to scope the enumeration.
+ mdToken rMethodBody[], // [OUT] Put Method Body tokens here.
+ mdToken rMethodDecl[], // [OUT] Put Method Declaration tokens here.
+ ULONG cMax, // [IN] Max tokens to put.
+ ULONG *pcTokens); // [OUT] Put # put here.
+
+ STDMETHOD(EnumPermissionSets)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdToken tk, // [IN] if !NIL, token to scope the enumeration.
+ DWORD dwActions, // [IN] if !0, return only these actions.
+ mdPermission rPermission[], // [OUT] Put Permissions here.
+ ULONG cMax, // [IN] Max Permissions to put.
+ ULONG *pcTokens); // [OUT] Put # put here.
+
+ STDMETHOD(FindMember)(
+ mdTypeDef td, // [IN] given typedef
+ LPCWSTR szName, // [IN] member name
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ mdToken *pmb); // [OUT] matching memberdef
+
+ STDMETHOD(FindMethod)(
+ mdTypeDef td, // [IN] given typedef
+ LPCWSTR szName, // [IN] member name
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ mdMethodDef *pmb); // [OUT] matching memberdef
+
+ STDMETHOD(FindField)(
+ mdTypeDef td, // [IN] given typedef
+ LPCWSTR szName, // [IN] member name
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ mdFieldDef *pmb); // [OUT] matching memberdef
+
+ STDMETHOD(FindMemberRef)(
+ mdTypeRef td, // [IN] given typeRef
+ LPCWSTR szName, // [IN] member name
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ mdMemberRef *pmr); // [OUT] matching memberref
+
+ STDMETHOD (GetMethodProps)(
+ mdMethodDef mb, // The method for which to get props.
+ mdTypeDef *pClass, // Put method's class here.
+ __out_ecount_part_opt(cchMethod, *pchMethod)
+ LPWSTR szMethod, // Put method's name here.
+ ULONG cchMethod, // Size of szMethod buffer in wide chars.
+ ULONG *pchMethod, // Put actual size here
+ DWORD *pdwAttr, // Put flags here.
+ PCCOR_SIGNATURE *ppvSigBlob, // [OUT] point to the blob value of meta data
+ ULONG *pcbSigBlob, // [OUT] actual size of signature blob
+ ULONG *pulCodeRVA, // [OUT] codeRVA
+ DWORD *pdwImplFlags); // [OUT] Impl. Flags
+
+ STDMETHOD(GetMemberRefProps)( // S_OK or error.
+ mdMemberRef mr, // [IN] given memberref
+ mdToken *ptk, // [OUT] Put classref or classdef here.
+ __out_ecount_part_opt(cchMember, *pchMember)
+ LPWSTR szMember, // [OUT] buffer to fill for member's name
+ ULONG cchMember, // [IN] the count of char of szMember
+ ULONG *pchMember, // [OUT] actual count of char in member name
+ PCCOR_SIGNATURE *ppvSigBlob, // [OUT] point to meta data blob value
+ ULONG *pbSig); // [OUT] actual size of signature blob
+
+ STDMETHOD(EnumProperties)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef td, // [IN] TypeDef to scope the enumeration.
+ mdProperty rProperties[], // [OUT] Put Properties here.
+ ULONG cMax, // [IN] Max properties to put.
+ ULONG *pcProperties); // [OUT] Put # put here.
+
+ STDMETHOD(EnumEvents)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef td, // [IN] TypeDef to scope the enumeration.
+ mdEvent rEvents[], // [OUT] Put events here.
+ ULONG cMax, // [IN] Max events to put.
+ ULONG *pcEvents); // [OUT] Put # put here.
+
+ STDMETHOD(GetEventProps)( // S_OK, S_FALSE, or error.
+ mdEvent ev, // [IN] event token
+ mdTypeDef *pClass, // [OUT] typedef containing the event declarion.
+ LPCWSTR szEvent, // [OUT] Event name
+ ULONG cchEvent, // [IN] the count of wchar of szEvent
+ ULONG *pchEvent, // [OUT] actual count of wchar for event's name
+ DWORD *pdwEventFlags, // [OUT] Event flags.
+ mdToken *ptkEventType, // [OUT] EventType class
+ mdMethodDef *pmdAddOn, // [OUT] AddOn method of the event
+ mdMethodDef *pmdRemoveOn, // [OUT] RemoveOn method of the event
+ mdMethodDef *pmdFire, // [OUT] Fire method of the event
+ mdMethodDef rmdOtherMethod[], // [OUT] other method of the event
+ ULONG cMax, // [IN] size of rmdOtherMethod
+ ULONG *pcOtherMethod); // [OUT] total number of other method of this event
+
+ STDMETHOD(EnumMethodSemantics)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdMethodDef mb, // [IN] MethodDef to scope the enumeration.
+ mdToken rEventProp[], // [OUT] Put Event/Property here.
+ ULONG cMax, // [IN] Max properties to put.
+ ULONG *pcEventProp); // [OUT] Put # put here.
+
+ STDMETHOD(GetMethodSemantics)( // S_OK, S_FALSE, or error.
+ mdMethodDef mb, // [IN] method token
+ mdToken tkEventProp, // [IN] event/property token.
+ DWORD *pdwSemanticsFlags); // [OUT] the role flags for the method/propevent pair
+
+ STDMETHOD(GetClassLayout) (
+ mdTypeDef td, // [IN] give typedef
+ DWORD *pdwPackSize, // [OUT] 1, 2, 4, 8, or 16
+ COR_FIELD_OFFSET rFieldOffset[], // [OUT] field offset array
+ ULONG cMax, // [IN] size of the array
+ ULONG *pcFieldOffset, // [OUT] needed array size
+ ULONG *pulClassSize); // [OUT] the size of the class
+
+ STDMETHOD(GetFieldMarshal) (
+ mdToken tk, // [IN] given a field's memberdef
+ PCCOR_SIGNATURE *ppvNativeType, // [OUT] native type of this field
+ ULONG *pcbNativeType); // [OUT] the count of bytes of *ppvNativeType
+
+ STDMETHOD(GetRVA)( // S_OK or error.
+ mdToken tk, // Member for which to set offset
+ ULONG *pulCodeRVA, // The offset
+ DWORD *pdwImplFlags); // the implementation flags
+
+ STDMETHOD(GetPermissionSetProps) (
+ mdPermission pm, // [IN] the permission token.
+ DWORD *pdwAction, // [OUT] CorDeclSecurity.
+ void const **ppvPermission, // [OUT] permission blob.
+ ULONG *pcbPermission); // [OUT] count of bytes of pvPermission.
+
+ STDMETHOD(GetSigFromToken)( // S_OK or error.
+ mdSignature mdSig, // [IN] Signature token.
+ PCCOR_SIGNATURE *ppvSig, // [OUT] return pointer to token.
+ ULONG *pcbSig); // [OUT] return size of signature.
+
+ STDMETHOD(GetModuleRefProps)( // S_OK or error.
+ mdModuleRef mur, // [IN] moduleref token.
+ __out_ecount_part_opt(cchName, *pchName)
+ LPWSTR szName, // [OUT] buffer to fill with the moduleref name.
+ ULONG cchName, // [IN] size of szName in wide characters.
+ ULONG *pchName); // [OUT] actual count of characters in the name.
+
+ STDMETHOD(EnumModuleRefs)( // S_OK or error.
+ HCORENUM *phEnum, // [IN|OUT] pointer to the enum.
+ mdModuleRef rModuleRefs[], // [OUT] put modulerefs here.
+ ULONG cmax, // [IN] max memberrefs to put.
+ ULONG *pcModuleRefs); // [OUT] put # put here.
+
+ STDMETHOD(GetTypeSpecFromToken)( // S_OK or error.
+ mdTypeSpec typespec, // [IN] TypeSpec token.
+ PCCOR_SIGNATURE *ppvSig, // [OUT] return pointer to TypeSpec signature
+ ULONG *pcbSig); // [OUT] return size of signature.
+
+ STDMETHOD(GetNameFromToken)( // Not Recommended! May be removed!
+ mdToken tk, // [IN] Token to get name from. Must have a name.
+ MDUTF8CSTR *pszUtf8NamePtr); // [OUT] Return pointer to UTF8 name in heap.
+
+ STDMETHOD(EnumUnresolvedMethods)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdToken rMethods[], // [OUT] Put MemberDefs here.
+ ULONG cMax, // [IN] Max MemberDefs to put.
+ ULONG *pcTokens); // [OUT] Put # put here.
+
+ STDMETHOD(GetUserString)( // S_OK or error.
+ mdString stk, // [IN] String token.
+ __out_ecount_part_opt(cchString, *pchString)
+ LPWSTR szString, // [OUT] Copy of string.
+ ULONG cchString, // [IN] Max chars of room in szString.
+ ULONG *pchString); // [OUT] How many chars in actual string.
+
+ STDMETHOD(GetPinvokeMap)( // S_OK or error.
+ mdToken tk, // [IN] FieldDef or MethodDef.
+ DWORD *pdwMappingFlags, // [OUT] Flags used for mapping.
+ __out_ecount_part_opt(cchImportName, *pchImportName)
+ LPWSTR szImportName, // [OUT] Import name.
+ ULONG cchImportName, // [IN] Size of the name buffer.
+ ULONG *pchImportName, // [OUT] Actual number of characters stored.
+ mdModuleRef *pmrImportDLL); // [OUT] ModuleRef token for the target DLL.
+
+ STDMETHOD(EnumSignatures)( // S_OK or error.
+ HCORENUM *phEnum, // [IN|OUT] pointer to the enum.
+ mdSignature rSignatures[], // [OUT] put signatures here.
+ ULONG cmax, // [IN] max signatures to put.
+ ULONG *pcSignatures); // [OUT] put # put here.
+
+ STDMETHOD(EnumTypeSpecs)( // S_OK or error.
+ HCORENUM *phEnum, // [IN|OUT] pointer to the enum.
+ mdTypeSpec rTypeSpecs[], // [OUT] put TypeSpecs here.
+ ULONG cmax, // [IN] max TypeSpecs to put.
+ ULONG *pcTypeSpecs); // [OUT] put # put here.
+
+ STDMETHOD(EnumUserStrings)( // S_OK or error.
+ HCORENUM *phEnum, // [IN/OUT] pointer to the enum.
+ mdString rStrings[], // [OUT] put Strings here.
+ ULONG cmax, // [IN] max Strings to put.
+ ULONG *pcStrings); // [OUT] put # put here.
+
+ STDMETHOD(GetParamForMethodIndex)( // S_OK or error.
+ mdMethodDef md, // [IN] Method token.
+ ULONG ulParamSeq, // [IN] Parameter sequence.
+ mdParamDef *ppd); // [IN] Put Param token here.
+
+ STDMETHOD(EnumCustomAttributes)( // S_OK or error.
+ HCORENUM *phEnum, // [IN, OUT] COR enumerator.
+ mdToken tk, // [IN] Token to scope the enumeration, 0 for all.
+ mdToken tkType, // [IN] Type of interest, 0 for all.
+ mdCustomAttribute rCustomAttributes[], // [OUT] Put custom attribute tokens here.
+ ULONG cMax, // [IN] Size of rCustomAttributes.
+ ULONG *pcCustomAttributes); // [OUT, OPTIONAL] Put count of token values here.
+
+ STDMETHOD(GetCustomAttributeProps)( // S_OK or error.
+ mdCustomAttribute cv, // [IN] CustomAttribute token.
+ mdToken *ptkObj, // [OUT, OPTIONAL] Put object token here.
+ mdToken *ptkType, // [OUT, OPTIONAL] Put AttrType token here.
+ void const **ppBlob, // [OUT, OPTIONAL] Put pointer to data here.
+ ULONG *pcbSize); // [OUT, OPTIONAL] Put size of date here.
+
+ STDMETHOD(FindTypeRef)(
+ mdToken tkResolutionScope, // [IN] ModuleRef, AssemblyRef or TypeRef.
+ LPCWSTR szName, // [IN] TypeRef Name.
+ mdTypeRef *ptr); // [OUT] matching TypeRef.
+
+ STDMETHOD(GetMemberProps)(
+ mdToken mb, // The member for which to get props.
+ mdTypeDef *pClass, // Put member's class here.
+ __out_ecount_part_opt(cchMember, *pchMember)
+ LPWSTR szMember, // Put member's name here.
+ ULONG cchMember, // Size of szMember buffer in wide chars.
+ ULONG *pchMember, // Put actual size here
+ DWORD *pdwAttr, // Put flags here.
+ PCCOR_SIGNATURE *ppvSigBlob, // [OUT] point to the blob value of meta data
+ ULONG *pcbSigBlob, // [OUT] actual size of signature blob
+ ULONG *pulCodeRVA, // [OUT] codeRVA
+ DWORD *pdwImplFlags, // [OUT] Impl. Flags
+ DWORD *pdwCPlusTypeFlag, // [OUT] flag for value type. selected ELEMENT_TYPE_*
+ UVCP_CONSTANT *ppValue, // [OUT] constant value
+ ULONG *pcchValue); // [OUT] size of constant string in chars, 0 for non-strings.
+
+ STDMETHOD(GetFieldProps)(
+ mdFieldDef mb, // The field for which to get props.
+ mdTypeDef *pClass, // Put field's class here.
+ __out_ecount_part_opt(cchField, *pchField)
+ LPWSTR szField, // Put field's name here.
+ ULONG cchField, // Size of szField buffer in wide chars.
+ ULONG *pchField, // Put actual size here
+ DWORD *pdwAttr, // Put flags here.
+ PCCOR_SIGNATURE *ppvSigBlob, // [OUT] point to the blob value of meta data
+ ULONG *pcbSigBlob, // [OUT] actual size of signature blob
+ DWORD *pdwCPlusTypeFlag, // [OUT] flag for value type. selected ELEMENT_TYPE_*
+ UVCP_CONSTANT *ppValue, // [OUT] constant value
+ ULONG *pcchValue); // [OUT] size of constant string in chars, 0 for non-strings.
+
+ STDMETHOD(GetPropertyProps)( // S_OK, S_FALSE, or error.
+ mdProperty prop, // [IN] property token
+ mdTypeDef *pClass, // [OUT] typedef containing the property declarion.
+ LPCWSTR szProperty, // [OUT] Property name
+ ULONG cchProperty, // [IN] the count of wchar of szProperty
+ ULONG *pchProperty, // [OUT] actual count of wchar for property name
+ DWORD *pdwPropFlags, // [OUT] property flags.
+ PCCOR_SIGNATURE *ppvSig, // [OUT] property type. pointing to meta data internal blob
+ ULONG *pbSig, // [OUT] count of bytes in *ppvSig
+ DWORD *pdwCPlusTypeFlag, // [OUT] flag for value type. selected ELEMENT_TYPE_*
+ UVCP_CONSTANT *ppDefaultValue, // [OUT] constant value
+ ULONG *pcchDefaultValue, // [OUT] size of constant string in chars, 0 for non-strings.
+ mdMethodDef *pmdSetter, // [OUT] setter method of the property
+ mdMethodDef *pmdGetter, // [OUT] getter method of the property
+ mdMethodDef rmdOtherMethod[], // [OUT] other method of the property
+ ULONG cMax, // [IN] size of rmdOtherMethod
+ ULONG *pcOtherMethod); // [OUT] total number of other method of this property
+
+ STDMETHOD(GetParamProps)( // S_OK or error.
+ mdParamDef tk, // [IN]The Parameter.
+ mdMethodDef *pmd, // [OUT] Parent Method token.
+ ULONG *pulSequence, // [OUT] Parameter sequence.
+ __out_ecount_part_opt(cchName, *pchName)
+ LPWSTR szName, // [OUT] Put name here.
+ ULONG cchName, // [OUT] Size of name buffer.
+ ULONG *pchName, // [OUT] Put actual size of name here.
+ DWORD *pdwAttr, // [OUT] Put flags here.
+ DWORD *pdwCPlusTypeFlag, // [OUT] Flag for value type. selected ELEMENT_TYPE_*.
+ UVCP_CONSTANT *ppValue, // [OUT] Constant value.
+ ULONG *pcchValue); // [OUT] size of constant string in chars, 0 for non-strings.
+
+ STDMETHOD(GetCustomAttributeByName)( // S_OK or error.
+ mdToken tkObj, // [IN] Object with Custom Attribute.
+ LPCWSTR szName, // [IN] Name of desired Custom Attribute.
+ const void **ppData, // [OUT] Put pointer to data here.
+ ULONG *pcbData); // [OUT] Put size of data here.
+
+ STDMETHOD_(BOOL, IsValidToken)( // True or False.
+ mdToken tk); // [IN] Given token.
+
+ STDMETHOD(GetNestedClassProps)( // S_OK or error.
+ mdTypeDef tdNestedClass, // [IN] NestedClass token.
+ mdTypeDef *ptdEnclosingClass); // [OUT] EnclosingClass token.
+
+ STDMETHOD(GetNativeCallConvFromSig)( // S_OK or error.
+ void const *pvSig, // [IN] Pointer to signature.
+ ULONG cbSig, // [IN] Count of signature bytes.
+ ULONG *pCallConv); // [OUT] Put calling conv here (see CorPinvokemap).
+
+ STDMETHOD(IsGlobal)( // S_OK or error.
+ mdToken pd, // [IN] Type, Field, or Method token.
+ int *pbGlobal); // [OUT] Put 1 if global, 0 otherwise.
+
+
+ // IMetaDataEmit functions
+
+ STDMETHOD(SetModuleProps)( // S_OK or error.
+ LPCWSTR szName); // [IN] If not NULL, the name of the module to set.
+
+ STDMETHOD(Save)( // S_OK or error.
+ LPCWSTR szFile, // [IN] The filename to save to.
+ DWORD dwSaveFlags); // [IN] Flags for the save.
+
+ STDMETHOD(SaveToStream)( // S_OK or error.
+ IStream *pIStream, // [IN] A writable stream to save to.
+ DWORD dwSaveFlags); // [IN] Flags for the save.
+
+ STDMETHOD(GetSaveSize)( // S_OK or error.
+ CorSaveSize fSave, // [IN] cssAccurate or cssQuick.
+ DWORD *pdwSaveSize); // [OUT] Put the size here.
+
+ STDMETHOD(DefineTypeDef)( // S_OK or error.
+ LPCWSTR szTypeDef, // [IN] Name of TypeDef
+ DWORD dwTypeDefFlags, // [IN] CustomAttribute flags
+ mdToken tkExtends, // [IN] extends this TypeDef or typeref
+ mdToken rtkImplements[], // [IN] Implements interfaces
+ mdTypeDef *ptd); // [OUT] Put TypeDef token here
+
+ STDMETHOD(DefineNestedType)( // S_OK or error.
+ LPCWSTR szTypeDef, // [IN] Name of TypeDef
+ DWORD dwTypeDefFlags, // [IN] CustomAttribute flags
+ mdToken tkExtends, // [IN] extends this TypeDef or typeref
+ mdToken rtkImplements[], // [IN] Implements interfaces
+ mdTypeDef tdEncloser, // [IN] TypeDef token of the enclosing type.
+ mdTypeDef *ptd); // [OUT] Put TypeDef token here
+
+ STDMETHOD(SetHandler)( // S_OK.
+ IUnknown *pUnk); // [IN] The new error handler.
+
+ STDMETHOD(DefineMethod)( // S_OK or error.
+ mdTypeDef td, // Parent TypeDef
+ LPCWSTR szName, // Name of member
+ DWORD dwMethodFlags, // Member attributes
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ ULONG ulCodeRVA,
+ DWORD dwImplFlags,
+ mdMethodDef *pmd); // Put member token here
+
+ STDMETHOD(DefineMethodImpl)( // S_OK or error.
+ mdTypeDef td, // [IN] The class implementing the method
+ mdToken tkBody, // [IN] Method body - MethodDef or MethodRef
+ mdToken tkDecl); // [IN] Method declaration - MethodDef or MethodRef
+
+ STDMETHOD(DefineTypeRefByName)( // S_OK or error.
+ mdToken tkResolutionScope, // [IN] ModuleRef, AssemblyRef or TypeRef.
+ LPCWSTR szName, // [IN] Name of the TypeRef.
+ mdTypeRef *ptr); // [OUT] Put TypeRef token here.
+
+ STDMETHOD(DefineImportType)( // S_OK or error.
+ IMetaDataAssemblyImport *pAssemImport, // [IN] Assembly containing the TypeDef.
+ const void *pbHashValue, // [IN] Hash Blob for Assembly.
+ ULONG cbHashValue, // [IN] Count of bytes.
+ IMetaDataImport *pImport, // [IN] Scope containing the TypeDef.
+ mdTypeDef tdImport, // [IN] The imported TypeDef.
+ IMetaDataAssemblyEmit *pAssemEmit, // [IN] Assembly into which the TypeDef is imported.
+ mdTypeRef *ptr); // [OUT] Put TypeRef token here.
+
+ STDMETHOD(DefineMemberRef)( // S_OK or error
+ mdToken tkImport, // [IN] ClassRef or ClassDef importing a member.
+ LPCWSTR szName, // [IN] member's name
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ mdMemberRef *pmr); // [OUT] memberref token
+
+ STDMETHOD(DefineImportMember)( // S_OK or error.
+ IMetaDataAssemblyImport *pAssemImport, // [IN] Assembly containing the Member.
+ const void *pbHashValue, // [IN] Hash Blob for Assembly.
+ ULONG cbHashValue, // [IN] Count of bytes.
+ IMetaDataImport *pImport, // [IN] Import scope, with member.
+ mdToken mbMember, // [IN] Member in import scope.
+ IMetaDataAssemblyEmit *pAssemEmit, // [IN] Assembly into which the Member is imported.
+ mdToken tkParent, // [IN] Classref or classdef in emit scope.
+ mdMemberRef *pmr); // [OUT] Put member ref here.
+
+ STDMETHOD(DefineEvent) (
+ mdTypeDef td, // [IN] the class/interface on which the event is being defined
+ LPCWSTR szEvent, // [IN] Name of the event
+ DWORD dwEventFlags, // [IN] CorEventAttr
+ mdToken tkEventType, // [IN] a reference (mdTypeRef or mdTypeRef) to the Event class
+ mdMethodDef mdAddOn, // [IN] required add method
+ mdMethodDef mdRemoveOn, // [IN] required remove method
+ mdMethodDef mdFire, // [IN] optional fire method
+ mdMethodDef rmdOtherMethods[], // [IN] optional array of other methods associate with the event
+ mdEvent *pmdEvent); // [OUT] output event token
+
+ STDMETHOD(SetClassLayout) (
+ mdTypeDef td, // [IN] typedef
+ DWORD dwPackSize, // [IN] packing size specified as 1, 2, 4, 8, or 16
+ COR_FIELD_OFFSET rFieldOffsets[], // [IN] array of layout specification
+ ULONG ulClassSize); // [IN] size of the class
+
+ STDMETHOD(DeleteClassLayout) (
+ mdTypeDef td); // [IN] typedef whose layout is to be deleted.
+
+ STDMETHOD(SetFieldMarshal) (
+ mdToken tk, // [IN] given a fieldDef or paramDef token
+ PCCOR_SIGNATURE pvNativeType, // [IN] native type specification
+ ULONG cbNativeType); // [IN] count of bytes of pvNativeType
+
+ STDMETHOD(DeleteFieldMarshal) (
+ mdToken tk); // [IN] given a fieldDef or paramDef token
+
+ STDMETHOD(DefinePermissionSet) (
+ mdToken tk, // [IN] the object to be decorated.
+ DWORD dwAction, // [IN] CorDeclSecurity.
+ void const *pvPermission, // [IN] permission blob.
+ ULONG cbPermission, // [IN] count of bytes of pvPermission.
+ mdPermission *ppm); // [OUT] returned permission token.
+
+ STDMETHOD(SetRVA)( // S_OK or error.
+ mdMethodDef md, // [IN] Method for which to set offset
+ ULONG ulRVA); // [IN] The offset
+
+ STDMETHOD(GetTokenFromSig)( // S_OK or error.
+ PCCOR_SIGNATURE pvSig, // [IN] Signature to define.
+ ULONG cbSig, // [IN] Size of signature data.
+ mdSignature *pmsig); // [OUT] returned signature token.
+
+ STDMETHOD(DefineModuleRef)( // S_OK or error.
+ LPCWSTR szName, // [IN] DLL name
+ mdModuleRef *pmur); // [OUT] returned
+
+ // <TODO>This should go away once everyone starts using SetMemberRefProps.</TODO>
+ STDMETHOD(SetParent)( // S_OK or error.
+ mdMemberRef mr, // [IN] Token for the ref to be fixed up.
+ mdToken tk); // [IN] The ref parent.
+
+ STDMETHOD(GetTokenFromTypeSpec)( // S_OK or error.
+ PCCOR_SIGNATURE pvSig, // [IN] TypeSpec Signature to define.
+ ULONG cbSig, // [IN] Size of signature data.
+ mdTypeSpec *ptypespec); // [OUT] returned TypeSpec token.
+
+ STDMETHOD(SaveToMemory)( // S_OK or error.
+ void *pbData, // [OUT] Location to write data.
+ ULONG cbData); // [IN] Max size of data buffer.
+
+ STDMETHOD(DefineUserString)( // Return code.
+ LPCWSTR szString, // [IN] User literal string.
+ ULONG cchString, // [IN] Length of string.
+ mdString *pstk); // [OUT] String token.
+
+ STDMETHOD(DeleteToken)( // Return code.
+ mdToken tkObj); // [IN] The token to be deleted
+
+ STDMETHOD(SetMethodProps)( // S_OK or error.
+ mdMethodDef md, // [IN] The MethodDef.
+ DWORD dwMethodFlags, // [IN] Method attributes.
+ ULONG ulCodeRVA, // [IN] Code RVA.
+ DWORD dwImplFlags); // [IN] Impl flags.
+
+ STDMETHOD(SetTypeDefProps)( // S_OK or error.
+ mdTypeDef td, // [IN] The TypeDef.
+ DWORD dwTypeDefFlags, // [IN] TypeDef flags.
+ mdToken tkExtends, // [IN] Base TypeDef or TypeRef.
+ mdToken rtkImplements[]); // [IN] Implemented interfaces.
+
+ STDMETHOD(SetEventProps)( // S_OK or error.
+ mdEvent ev, // [IN] The event token.
+ DWORD dwEventFlags, // [IN] CorEventAttr.
+ mdToken tkEventType, // [IN] A reference (mdTypeRef or mdTypeRef) to the Event class.
+ mdMethodDef mdAddOn, // [IN] Add method.
+ mdMethodDef mdRemoveOn, // [IN] Remove method.
+ mdMethodDef mdFire, // [IN] Fire method.
+ mdMethodDef rmdOtherMethods[]);// [IN] Array of other methods associate with the event.
+
+ STDMETHOD(SetPermissionSetProps)( // S_OK or error.
+ mdToken tk, // [IN] The object to be decorated.
+ DWORD dwAction, // [IN] CorDeclSecurity.
+ void const *pvPermission, // [IN] Permission blob.
+ ULONG cbPermission, // [IN] Count of bytes of pvPermission.
+ mdPermission *ppm); // [OUT] Permission token.
+
+ STDMETHOD(DefinePinvokeMap)( // Return code.
+ mdToken tk, // [IN] FieldDef or MethodDef.
+ DWORD dwMappingFlags, // [IN] Flags used for mapping.
+ LPCWSTR szImportName, // [IN] Import name.
+ mdModuleRef mrImportDLL); // [IN] ModuleRef token for the target DLL.
+
+ STDMETHOD(SetPinvokeMap)( // Return code.
+ mdToken tk, // [IN] FieldDef or MethodDef.
+ DWORD dwMappingFlags, // [IN] Flags used for mapping.
+ LPCWSTR szImportName, // [IN] Import name.
+ mdModuleRef mrImportDLL); // [IN] ModuleRef token for the target DLL.
+
+ STDMETHOD(DeletePinvokeMap)( // Return code.
+ mdToken tk); // [IN] FieldDef or MethodDef.
+
+ // New CustomAttribute functions.
+ STDMETHOD(DefineCustomAttribute)( // Return code.
+ mdToken tkObj, // [IN] The object to put the value on.
+ mdToken tkType, // [IN] Type of the CustomAttribute (TypeRef/TypeDef).
+ void const *pCustomAttribute, // [IN] The custom value data.
+ ULONG cbCustomAttribute, // [IN] The custom value data length.
+ mdCustomAttribute *pcv); // [OUT] The custom value token value on return.
+
+ STDMETHOD(SetCustomAttributeValue)( // Return code.
+ mdCustomAttribute pcv, // [IN] The custom value token whose value to replace.
+ void const *pCustomAttribute, // [IN] The custom value data.
+ ULONG cbCustomAttribute);// [IN] The custom value data length.
+
+ STDMETHOD(DefineField)( // S_OK or error.
+ mdTypeDef td, // Parent TypeDef
+ LPCWSTR szName, // Name of member
+ DWORD dwFieldFlags, // Member attributes
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ DWORD dwCPlusTypeFlag, // [IN] flag for value type. selected ELEMENT_TYPE_*
+ void const *pValue, // [IN] constant value
+ ULONG cchValue, // [IN] size of constant value (string, in wide chars).
+ mdFieldDef *pmd); // [OUT] Put member token here
+
+ STDMETHOD(DefineProperty)(
+ mdTypeDef td, // [IN] the class/interface on which the property is being defined
+ LPCWSTR szProperty, // [IN] Name of the property
+ DWORD dwPropFlags, // [IN] CorPropertyAttr
+ PCCOR_SIGNATURE pvSig, // [IN] the required type signature
+ ULONG cbSig, // [IN] the size of the type signature blob
+ DWORD dwCPlusTypeFlag, // [IN] flag for value type. selected ELEMENT_TYPE_*
+ void const *pValue, // [IN] constant value
+ ULONG cchValue, // [IN] size of constant value (string, in wide chars).
+ mdMethodDef mdSetter, // [IN] optional setter of the property
+ mdMethodDef mdGetter, // [IN] optional getter of the property
+ mdMethodDef rmdOtherMethods[], // [IN] an optional array of other methods
+ mdProperty *pmdProp); // [OUT] output property token
+
+ STDMETHOD(DefineParam)(
+ mdMethodDef md, // [IN] Owning method
+ ULONG ulParamSeq, // [IN] Which param
+ LPCWSTR szName, // [IN] Optional param name
+ DWORD dwParamFlags, // [IN] Optional param flags
+ DWORD dwCPlusTypeFlag, // [IN] flag for value type. selected ELEMENT_TYPE_*
+ void const *pValue, // [IN] constant value
+ ULONG cchValue, // [IN] size of constant value (string, in wide chars).
+ mdParamDef *ppd); // [OUT] Put param token here
+
+ STDMETHOD(SetFieldProps)( // S_OK or error.
+ mdFieldDef fd, // [IN] The FieldDef.
+ DWORD dwFieldFlags, // [IN] Field attributes.
+ DWORD dwCPlusTypeFlag, // [IN] Flag for the value type, selected ELEMENT_TYPE_*
+ void const *pValue, // [IN] Constant value.
+ ULONG cchValue); // [IN] size of constant value (string, in wide chars).
+
+ STDMETHOD(SetPropertyProps)( // S_OK or error.
+ mdProperty pr, // [IN] Property token.
+ DWORD dwPropFlags, // [IN] CorPropertyAttr.
+ DWORD dwCPlusTypeFlag, // [IN] Flag for value type, selected ELEMENT_TYPE_*
+ void const *pValue, // [IN] Constant value.
+ ULONG cchValue, // [IN] size of constant value (string, in wide chars).
+ mdMethodDef mdSetter, // [IN] Setter of the property.
+ mdMethodDef mdGetter, // [IN] Getter of the property.
+ mdMethodDef rmdOtherMethods[]);// [IN] Array of other methods.
+
+ STDMETHOD(SetParamProps)( // Return code.
+ mdParamDef pd, // [IN] Param token.
+ LPCWSTR szName, // [IN] Param name.
+ DWORD dwParamFlags, // [IN] Param flags.
+ DWORD dwCPlusTypeFlag, // [IN] Flag for value type. selected ELEMENT_TYPE_*.
+ void const *pValue, // [OUT] Constant value.
+ ULONG cchValue); // [IN] size of constant value (string, in wide chars).
+
+ // Specialized Custom Attributes for security.
+ STDMETHOD(DefineSecurityAttributeSet)( // Return code.
+ mdToken tkObj, // [IN] Class or method requiring security attributes.
+ COR_SECATTR rSecAttrs[], // [IN] Array of security attribute descriptions.
+ ULONG cSecAttrs, // [IN] Count of elements in above array.
+ ULONG *pulErrorAttr); // [OUT] On error, index of attribute causing problem.
+
+ STDMETHOD(ApplyEditAndContinue)( // S_OK or error.
+ IUnknown *pImport); // [IN] Metadata from the delta PE.
+
+ STDMETHOD(TranslateSigWithScope)(
+ IMetaDataAssemblyImport *pAssemImport, // [IN] importing assembly interface
+ const void *pbHashValue, // [IN] Hash Blob for Assembly.
+ ULONG cbHashValue, // [IN] Count of bytes.
+ IMetaDataImport *import, // [IN] importing interface
+ PCCOR_SIGNATURE pbSigBlob, // [IN] signature in the importing scope
+ ULONG cbSigBlob, // [IN] count of bytes of signature
+ IMetaDataAssemblyEmit *pAssemEmit, // [IN] emit assembly interface
+ IMetaDataEmit *emit, // [IN] emit interface
+ PCOR_SIGNATURE pvTranslatedSig, // [OUT] buffer to hold translated signature
+ ULONG cbTranslatedSigMax,
+ ULONG *pcbTranslatedSig);// [OUT] count of bytes in the translated signature
+
+ STDMETHOD(SetMethodImplFlags)( // [IN] S_OK or error.
+ mdMethodDef md, // [IN] Method for which to set ImplFlags
+ DWORD dwImplFlags);
+
+ STDMETHOD(SetFieldRVA)( // [IN] S_OK or error.
+ mdFieldDef fd, // [IN] Field for which to set offset
+ ULONG ulRVA); // [IN] The offset
+
+ STDMETHOD(Merge)( // S_OK or error.
+ IMetaDataImport *pImport, // [IN] The scope to be merged.
+ IMapToken *pHostMapToken, // [IN] Host IMapToken interface to receive token remap notification
+ IUnknown *pHandler); // [IN] An object to receive to receive error notification.
+
+ STDMETHOD(MergeEnd)(); // S_OK or error.
+
+};
+
+#endif // SYMBOLINFO_H
diff --git a/src/debug/di/valuehome.cpp b/src/debug/di/valuehome.cpp
new file mode 100644
index 0000000000..837afd5f8b
--- /dev/null
+++ b/src/debug/di/valuehome.cpp
@@ -0,0 +1,1062 @@
+// 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.
+//*****************************************************************************
+// File: ValueHome.cpp
+//
+
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "primitives.h"
+
+// constructor to initialize an instance of EnregisteredValueHome
+// Arguments:
+// input: pFrame - frame to which the value belongs
+// output: no out parameters, but the instance has been initialized
+EnregisteredValueHome::EnregisteredValueHome(const CordbNativeFrame * pFrame):
+ m_pFrame(pFrame)
+{
+ _ASSERTE(pFrame != NULL);
+}
+
+// ----------------------------------------------------------------------------
+// RegValueHome member function implementations
+// ----------------------------------------------------------------------------
+
+// initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+// instance of a derived class of EnregisteredValueHome (see EnregisteredValueHome::CopyToIPCEType for full
+// header comment)
+void RegValueHome::CopyToIPCEType(RemoteAddress * pRegAddr)
+{
+ pRegAddr->kind = RAK_REG;
+ pRegAddr->reg1 = m_reg1Info.m_kRegNumber;
+ pRegAddr->reg1Addr = CORDB_ADDRESS_TO_PTR(m_reg1Info.m_regAddr);
+ pRegAddr->reg1Value = m_reg1Info.m_regValue;
+} // RegValueHome::CopyToIPCEType
+
+// RegValueHome::SetContextRegister
+// This will update a register in a given context, and in the regdisplay of a given frame.
+// Arguments:
+// input: pContext - context from which the register comes
+// regnum - enumeration constant indicating which register is to be updated
+// newVal - the new value for the register contents
+// output: no out parameters, but the new value will be written to the context and the frame
+// Notes: We don't take a data target here because we are directly writing process memory and passing
+// in a context, which has the location to update.
+// Throws
+void RegValueHome::SetContextRegister(DT_CONTEXT * pContext,
+ CorDebugRegister regNum,
+ SIZE_T newVal)
+{
+ LPVOID rdRegAddr;
+
+#define _UpdateFrame() \
+ if (m_pFrame != NULL) \
+ { \
+ rdRegAddr = m_pFrame->GetAddressOfRegister(regNum); \
+ *(SIZE_T *)rdRegAddr = newVal; \
+ }
+
+ switch(regNum)
+ {
+ case REGISTER_INSTRUCTION_POINTER: CORDbgSetIP(pContext, (LPVOID)newVal); break;
+ case REGISTER_STACK_POINTER: CORDbgSetSP(pContext, (LPVOID)newVal); break;
+
+#if defined(DBG_TARGET_X86)
+ case REGISTER_FRAME_POINTER: CORDbgSetFP(pContext, (LPVOID)newVal);
+ _UpdateFrame(); break;
+
+ case REGISTER_X86_EAX: pContext->Eax = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_X86_ECX: pContext->Ecx = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_X86_EDX: pContext->Edx = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_X86_EBX: pContext->Ebx = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_X86_ESI: pContext->Esi = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_X86_EDI: pContext->Edi = newVal;
+ _UpdateFrame(); break;
+
+#elif defined(DBG_TARGET_AMD64)
+ case REGISTER_AMD64_RBP: pContext->Rbp = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_RAX: pContext->Rax = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_RCX: pContext->Rcx = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_RDX: pContext->Rdx = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_RBX: pContext->Rbx = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_RSI: pContext->Rsi = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_RDI: pContext->Rdi = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_R8: pContext->R8 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_R9: pContext->R9 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_R10: pContext->R10 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_R11: pContext->R11 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_R12: pContext->R12 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_R13: pContext->R13 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_R14: pContext->R14 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_R15: pContext->R15 = newVal;
+ _UpdateFrame(); break;
+#elif defined(DBG_TARGET_ARM)
+ case REGISTER_ARM_R0: pContext->R0 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R1: pContext->R1 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R2: pContext->R2 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R3: pContext->R3 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R4: pContext->R4 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R5: pContext->R5 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R6: pContext->R6 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R7: pContext->R7 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R8: pContext->R8 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R9: pContext->R9 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R10: pContext->R10 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R11: pContext->R11 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R12: pContext->R12 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_LR: pContext->Lr = newVal;
+ _UpdateFrame(); break;
+#elif defined(DBG_TARGET_ARM64)
+ case REGISTER_ARM64_X0:
+ case REGISTER_ARM64_X1:
+ case REGISTER_ARM64_X2:
+ case REGISTER_ARM64_X3:
+ case REGISTER_ARM64_X4:
+ case REGISTER_ARM64_X5:
+ case REGISTER_ARM64_X6:
+ case REGISTER_ARM64_X7:
+ case REGISTER_ARM64_X8:
+ case REGISTER_ARM64_X9:
+ case REGISTER_ARM64_X10:
+ case REGISTER_ARM64_X11:
+ case REGISTER_ARM64_X12:
+ case REGISTER_ARM64_X13:
+ case REGISTER_ARM64_X14:
+ case REGISTER_ARM64_X15:
+ case REGISTER_ARM64_X16:
+ case REGISTER_ARM64_X17:
+ case REGISTER_ARM64_X18:
+ case REGISTER_ARM64_X19:
+ case REGISTER_ARM64_X20:
+ case REGISTER_ARM64_X21:
+ case REGISTER_ARM64_X22:
+ case REGISTER_ARM64_X23:
+ case REGISTER_ARM64_X24:
+ case REGISTER_ARM64_X25:
+ case REGISTER_ARM64_X26:
+ case REGISTER_ARM64_X27:
+ case REGISTER_ARM64_X28: pContext->X[regNum - REGISTER_ARM64_X0] = newVal;
+ _UpdateFrame(); break;
+
+ case REGISTER_ARM64_LR: pContext->Lr = newVal;
+ _UpdateFrame(); break;
+#endif
+ default:
+ _ASSERTE(!"Invalid register number!");
+ ThrowHR(E_FAIL);
+ }
+} // RegValueHome::SetContextRegister
+
+// RegValueHome::SetEnregisteredValue
+// set a remote enregistered location to a new value (see code:EnregisteredValueHome::SetEnregisteredValue
+// for full header comment)
+void RegValueHome::SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pContext, bool fIsSigned)
+{
+ SIZE_T extendedVal = 0;
+
+ // If the value is in a reg, then it's going to be a register's width (regardless of
+ // the actual width of the data).
+ // For signed types, like i2, i1, make sure we sign extend.
+
+ if (fIsSigned)
+ {
+ // Sign extend. SSIZE_T is a register size signed value.
+ // Casting
+ switch(newValue.Size())
+ {
+ case 1: _ASSERTE(sizeof( BYTE) == 1);
+ extendedVal = (SSIZE_T) *(char*)newValue.StartAddress(); break;
+ case 2: _ASSERTE(sizeof( WORD) == 2);
+ extendedVal = (SSIZE_T) *(short*)newValue.StartAddress(); break;
+ case 4: _ASSERTE(sizeof(DWORD) == 4);
+ extendedVal = (SSIZE_T) *(int*)newValue.StartAddress(); break;
+#if defined(DBG_TARGET_WIN64)
+ case 8: _ASSERTE(sizeof(ULONGLONG) == 8);
+ extendedVal = (SSIZE_T) *(ULONGLONG*)newValue.StartAddress(); break;
+#endif // DBG_TARGET_WIN64
+ default: _ASSERTE(!"bad size");
+ }
+ }
+ else
+ {
+ // Zero extend.
+ switch(newValue.Size())
+ {
+ case 1: _ASSERTE(sizeof( BYTE) == 1);
+ extendedVal = *( BYTE*)newValue.StartAddress(); break;
+ case 2: _ASSERTE(sizeof( WORD) == 2);
+ extendedVal = *( WORD*)newValue.StartAddress(); break;
+ case 4: _ASSERTE(sizeof(DWORD) == 4);
+ extendedVal = *(DWORD*)newValue.StartAddress(); break;
+#if defined(DBG_TARGET_WIN64)
+ case 8: _ASSERTE(sizeof(ULONGLONG) == 8);
+ extendedVal = *(ULONGLONG*)newValue.StartAddress(); break;
+#endif // DBG_TARGET_WIN64
+ default: _ASSERTE(!"bad size");
+ }
+ }
+
+ SetContextRegister(pContext, m_reg1Info.m_kRegNumber, extendedVal); // throws
+} // RegValueHome::SetEnregisteredValue
+
+// RegValueHome::GetEnregisteredValue
+// Gets an enregistered value and returns it to the caller (see EnregisteredValueHome::GetEnregisteredValue
+// for full header comment)
+void RegValueHome::GetEnregisteredValue(MemoryRange valueOutBuffer)
+{
+ UINT_PTR* reg = m_pFrame->GetAddressOfRegister(m_reg1Info.m_kRegNumber);
+ PREFIX_ASSUME(reg != NULL);
+ _ASSERTE(sizeof(*reg) == valueOutBuffer.Size());
+
+ memcpy(valueOutBuffer.StartAddress(), reg, sizeof(*reg));
+} // RegValueHome::GetEnregisteredValue
+
+
+// ----------------------------------------------------------------------------
+// RegRegValueHome member function implementations
+// ----------------------------------------------------------------------------
+
+// initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+// instance of a derived class of EnregisteredValueHome (see EnregisteredValueHome::CopyToIPCEType for full
+// header comment)
+void RegRegValueHome::CopyToIPCEType(RemoteAddress * pRegAddr)
+{
+ pRegAddr->kind = RAK_REGREG;
+ pRegAddr->reg1 = m_reg1Info.m_kRegNumber;
+ pRegAddr->reg1Addr = CORDB_ADDRESS_TO_PTR(m_reg1Info.m_regAddr);
+ pRegAddr->reg1Value = m_reg1Info.m_regValue;
+ pRegAddr->u.reg2 = m_reg2Info.m_kRegNumber;
+ pRegAddr->u.reg2Addr = CORDB_ADDRESS_TO_PTR(m_reg2Info.m_regAddr);
+ pRegAddr->u.reg2Value = m_reg2Info.m_regValue;
+} // RegRegValueHome::CopyToIPCEType
+
+// RegRegValueHome::SetEnregisteredValue
+// set a remote enregistered location to a new value (see EnregisteredValueHome::SetEnregisteredValue
+// for full header comment)
+void RegRegValueHome::SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pContext, bool fIsSigned)
+{
+ _ASSERTE(newValue.Size() == 8);
+ _ASSERTE(REG_SIZE == sizeof(void*));
+
+ // Split the new value into high and low parts.
+ SIZE_T highPart;
+ SIZE_T lowPart;
+
+ memcpy(&lowPart, newValue.StartAddress(), REG_SIZE);
+ memcpy(&highPart, (BYTE *)newValue.StartAddress() + REG_SIZE, REG_SIZE);
+
+ // Update the proper registers.
+ SetContextRegister(pContext, m_reg1Info.m_kRegNumber, highPart); // throws
+ SetContextRegister(pContext, m_reg2Info.m_kRegNumber, lowPart); // throws
+
+ // update the frame's register display
+ void * valueAddress = (void *)(m_pFrame->GetAddressOfRegister(m_reg1Info.m_kRegNumber));
+ memcpy(valueAddress, newValue.StartAddress(), newValue.Size());
+} // RegRegValueHome::SetEnregisteredValue
+
+// RegRegValueHome::GetEnregisteredValue
+// Gets an enregistered value and returns it to the caller (see EnregisteredValueHome::GetEnregisteredValue
+// for full header comment)
+void RegRegValueHome::GetEnregisteredValue(MemoryRange valueOutBuffer)
+{
+ UINT_PTR* highWordAddr = m_pFrame->GetAddressOfRegister(m_reg1Info.m_kRegNumber);
+ PREFIX_ASSUME(highWordAddr != NULL);
+
+ UINT_PTR* lowWordAddr = m_pFrame->GetAddressOfRegister(m_reg2Info.m_kRegNumber);
+ PREFIX_ASSUME(lowWordAddr != NULL);
+
+ _ASSERTE(sizeof(*highWordAddr) + sizeof(*lowWordAddr) == valueOutBuffer.Size());
+
+ memcpy(valueOutBuffer.StartAddress(), lowWordAddr, sizeof(*lowWordAddr));
+ memcpy((BYTE *)valueOutBuffer.StartAddress() + sizeof(*lowWordAddr), highWordAddr, sizeof(*highWordAddr));
+
+} // RegRegValueHome::GetEnregisteredValue
+
+
+// ----------------------------------------------------------------------------
+// RegMemValueHome member function implementations
+// ----------------------------------------------------------------------------
+
+// initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+// instance of a derived class of EnregisteredValueHome (see EnregisteredValueHome::CopyToIPCEType for full
+// header comment)
+void RegMemValueHome::CopyToIPCEType(RemoteAddress * pRegAddr)
+{
+ pRegAddr->kind = RAK_REGMEM;
+ pRegAddr->reg1 = m_reg1Info.m_kRegNumber;
+ pRegAddr->reg1Addr = CORDB_ADDRESS_TO_PTR(m_reg1Info.m_regAddr);
+ pRegAddr->reg1Value = m_reg1Info.m_regValue;
+ pRegAddr->addr = m_memAddr;
+} // RegMemValueHome::CopyToIPCEType
+
+// RegMemValueHome::SetEnregisteredValue
+// set a remote enregistered location to a new value (see EnregisteredValueHome::SetEnregisteredValue
+// for full header comment)
+void RegMemValueHome::SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pContext, bool fIsSigned)
+{
+ _ASSERTE(newValue.Size() == REG_SIZE >> 1); // make sure we have bytes for two registers
+ _ASSERTE(REG_SIZE == sizeof(void*));
+
+ // Split the new value into high and low parts.
+ SIZE_T highPart;
+ SIZE_T lowPart;
+
+ memcpy(&lowPart, newValue.StartAddress(), REG_SIZE);
+ memcpy(&highPart, (BYTE *)newValue.StartAddress() + REG_SIZE, REG_SIZE);
+
+ // Update the proper registers.
+ SetContextRegister(pContext, m_reg1Info.m_kRegNumber, highPart); // throws
+
+ _ASSERTE(REG_SIZE == sizeof(lowPart));
+ HRESULT hr = m_pFrame->GetProcess()->SafeReadStruct(m_memAddr, &lowPart);
+ IfFailThrow(hr);
+
+} // RegMemValueHome::SetEnregisteredValue
+
+// RegMemValueHome::GetEnregisteredValue
+// Gets an enregistered value and returns it to the caller (see EnregisteredValueHome::GetEnregisteredValue
+// for full header comment)
+void RegMemValueHome::GetEnregisteredValue(MemoryRange valueOutBuffer)
+{
+ // Read the high bits from the register...
+ UINT_PTR* highBitsAddr = m_pFrame->GetAddressOfRegister(m_reg1Info.m_kRegNumber);
+ PREFIX_ASSUME(highBitsAddr != NULL);
+
+ // ... and the low bits from the remote process
+ DWORD lowBits;
+ HRESULT hr = m_pFrame->GetProcess()->SafeReadStruct(m_memAddr, &lowBits);
+ IfFailThrow(hr);
+
+ _ASSERTE(sizeof(lowBits)+sizeof(*highBitsAddr) == valueOutBuffer.Size());
+
+ memcpy(valueOutBuffer.StartAddress(), &lowBits, sizeof(lowBits));
+ memcpy((BYTE *)valueOutBuffer.StartAddress() + sizeof(lowBits), highBitsAddr, sizeof(*highBitsAddr));
+
+} // RegMemValueHome::GetEnregisteredValue
+
+
+// ----------------------------------------------------------------------------
+// MemRegValueHome member function implementations
+// ----------------------------------------------------------------------------
+
+// initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+// instance of a derived class of EnregisteredValueHome (see EnregisteredValueHome::CopyToIPCEType for full
+// header comment)
+void MemRegValueHome::CopyToIPCEType(RemoteAddress * pRegAddr)
+{
+ pRegAddr->kind = RAK_MEMREG;
+ pRegAddr->reg1 = m_reg1Info.m_kRegNumber;
+ pRegAddr->reg1Addr = CORDB_ADDRESS_TO_PTR(m_reg1Info.m_regAddr);
+ pRegAddr->reg1Value = m_reg1Info.m_regValue;
+ pRegAddr->addr = m_memAddr;
+} // MemRegValueHome::CopyToIPCEType
+
+// MemRegValueHome::SetEnregisteredValue
+// set a remote enregistered location to a new value (see EnregisteredValueHome::SetEnregisteredValue
+// for full header comment)
+void MemRegValueHome::SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pContext, bool fIsSigned)
+{
+ _ASSERTE(newValue.Size() == REG_SIZE << 1); // make sure we have bytes for two registers
+ _ASSERTE(REG_SIZE == sizeof(void *));
+
+ // Split the new value into high and low parts.
+ SIZE_T highPart;
+ SIZE_T lowPart;
+
+ memcpy(&lowPart, newValue.StartAddress(), REG_SIZE);
+ memcpy(&highPart, (BYTE *)newValue.StartAddress() + REG_SIZE, REG_SIZE);
+
+ // Update the proper registers.
+ SetContextRegister(pContext, m_reg1Info.m_kRegNumber, lowPart); // throws
+
+ _ASSERTE(REG_SIZE == sizeof(highPart));
+ HRESULT hr = m_pFrame->GetProcess()->SafeWriteStruct(m_memAddr, &highPart);
+ IfFailThrow(hr);
+} // MemRegValueHome::SetEnregisteredValue
+
+// MemRegValueHome::GetEnregisteredValue
+// Gets an enregistered value and returns it to the caller (see EnregisteredValueHome::GetEnregisteredValue
+// for full header comment)
+void MemRegValueHome::GetEnregisteredValue(MemoryRange valueOutBuffer)
+{
+ // Read the high bits from the remote process' memory
+ DWORD highBits;
+ HRESULT hr = m_pFrame->GetProcess()->SafeReadStruct(m_memAddr, &highBits);
+ IfFailThrow(hr);
+
+
+ // and the low bits from a register
+ UINT_PTR* lowBitsAddr = m_pFrame->GetAddressOfRegister(m_reg1Info.m_kRegNumber);
+ PREFIX_ASSUME(lowBitsAddr != NULL);
+
+ _ASSERTE(sizeof(*lowBitsAddr)+sizeof(highBits) == valueOutBuffer.Size());
+
+ memcpy(valueOutBuffer.StartAddress(), lowBitsAddr, sizeof(*lowBitsAddr));
+ memcpy((BYTE *)valueOutBuffer.StartAddress() + sizeof(*lowBitsAddr), &highBits, sizeof(highBits));
+
+} // MemRegValueHome::GetEnregisteredValue
+
+#if !defined(DBG_TARGET_ARM) // @ARMTODO
+
+// ----------------------------------------------------------------------------
+// FloatRegValueHome member function implementations
+// ----------------------------------------------------------------------------
+
+// initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+// instance of a derived class of EnregisteredValueHome (see EnregisteredValueHome::CopyToIPCEType for full
+// header comment)
+void FloatRegValueHome::CopyToIPCEType(RemoteAddress * pRegAddr)
+{
+ pRegAddr->kind = RAK_FLOAT;
+ pRegAddr->reg1Addr = NULL;
+ pRegAddr->floatIndex = m_floatIndex;
+} // FloatRegValueHome::CopyToIPCEType
+
+// FloatValueHome::SetEnregisteredValue
+// set a remote enregistered location to a new value (see EnregisteredValueHome::SetEnregisteredValue
+// for full header comment)
+void FloatRegValueHome::SetEnregisteredValue(MemoryRange newValue,
+ DT_CONTEXT * pContext,
+ bool fIsSigned)
+{
+ // TODO: : implement CordbValue::SetEnregisteredValue for RAK_FLOAT
+ #if defined(DBG_TARGET_AMD64)
+ PORTABILITY_ASSERT("NYI: SetEnregisteredValue (divalue.cpp): RAK_FLOAT for AMD64");
+ #endif // DBG_TARGET_AMD64
+
+ _ASSERTE((newValue.Size() == 4) || (newValue.Size() == 8));
+
+ // Convert the input to a double.
+ double newVal = 0.0;
+
+ memcpy(&newVal, newValue.StartAddress(), newValue.Size());
+
+ #if defined(DBG_TARGET_X86)
+
+ // This is unfortunately non-portable. Luckily we can live with this for now since we only support
+ // Win/X86 debugging a Mac/X86 platform.
+
+ #if !defined(_TARGET_X86_)
+ #error Unsupported target platform
+ #endif // !_TARGET_X86_
+
+ // What a pain, on X86 take the floating
+ // point state in the context and make it our current FP
+ // state, set the value into the current FP state, then
+ // save out the FP state into the context again and
+ // restore our original state.
+ DT_FLOATING_SAVE_AREA currentFPUState;
+
+ __asm fnsave currentFPUState // save the current FPU state.
+
+ // Copy the state out of the context.
+ DT_FLOATING_SAVE_AREA floatarea = pContext->FloatSave;
+ floatarea.StatusWord &= 0xFF00; // remove any error codes.
+ floatarea.ControlWord |= 0x3F; // mask all exceptions.
+
+ __asm
+ {
+ fninit
+ frstor floatarea ;; reload the threads FPU state.
+ }
+
+ double td; // temp double
+ double popArea[DebuggerIPCE_FloatCount];
+
+ // Pop off until we reach the value we want to change.
+ DWORD i = 0;
+
+ while (i <= m_floatIndex)
+ {
+ __asm fstp td
+ popArea[i++] = td;
+ }
+
+ __asm fld newVal; // push on the new value.
+
+ // Push any values that we popled off back onto the stack,
+ // _except_ the last one, which was the one we changed.
+ i--;
+
+ while (i > 0)
+ {
+ td = popArea[--i];
+ __asm fld td
+ }
+
+ // Save out the modified float area.
+ __asm fnsave floatarea
+
+ // Put it into the context.
+ pContext->FloatSave= floatarea;
+
+ // Restore our FPU state
+ __asm
+ {
+ fninit
+ frstor currentFPUState ;; restore our saved FPU state.
+ }
+ #endif // DBG_TARGET_X86
+
+ // update the thread's floating point stack
+ void * valueAddress = (void *) &(m_pFrame->m_pThread->m_floatValues[m_floatIndex]);
+ memcpy(valueAddress, newValue.StartAddress(), newValue.Size());
+} // FloatValueHome::SetEnregisteredValue
+
+// FloatRegValueHome::GetEnregisteredValue
+// Throws E_NOTIMPL for attempts to get an enregistered value for a float register
+void FloatRegValueHome::GetEnregisteredValue(MemoryRange valueOutBuffer)
+{
+ _ASSERTE(!"invalid variable home");
+ ThrowHR(E_NOTIMPL);
+} // FloatRegValueHome::GetEnregisteredValue
+
+#endif // !DBG_TARGET_ARM @ARMTODO
+
+// ============================================================================
+// RemoteValueHome implementation
+// ============================================================================
+
+ // constructor
+ // Arguments:
+ // input: pProcess - the process to which the value belongs
+ // remoteValue - a buffer with the target address of the value and its size
+ // Note: It's possible a particular instance of CordbGenericValue may have neither a remote address nor a
+ // register address--FuncEval makes empty GenericValues for literals but for those, we will make a
+ // RegisterValueHome,so we can assert that we have a non-null remote address here
+ RemoteValueHome::RemoteValueHome(CordbProcess * pProcess, TargetBuffer remoteValue):
+ ValueHome(pProcess),
+ m_remoteValue(remoteValue)
+ {
+ _ASSERTE(remoteValue.pAddress != NULL);
+ } // RemoteValueHome::RemoteValueHome
+
+// Gets a value and returns it in dest
+// virtual
+void RemoteValueHome::GetValue(MemoryRange dest)
+{
+ _ASSERTE(dest.Size() == m_remoteValue.cbSize);
+ _ASSERTE((!m_remoteValue.IsEmpty()) && (dest.StartAddress() != NULL));
+ m_pProcess->SafeReadBuffer(m_remoteValue, (BYTE *)dest.StartAddress());
+} // RemoteValueHome::GetValue
+
+// Sets a location to the value provided in src
+// virtual
+void RemoteValueHome::SetValue(MemoryRange src, CordbType * pType)
+{
+ _ASSERTE(!m_remoteValue.IsEmpty());
+ _ASSERTE(src.Size() == m_remoteValue.cbSize);
+ _ASSERTE(src.StartAddress() != NULL);
+ m_pProcess->SafeWriteBuffer(m_remoteValue, (BYTE *)src.StartAddress());
+} // RemoteValueHome::SetValue
+
+// creates an ICDValue for a field or array element or for the value type of a boxed object
+// virtual
+void RemoteValueHome::CreateInternalValue(CordbType * pType,
+ SIZE_T offset,
+ void * localAddress,
+ ULONG32 size,
+ ICorDebugValue ** ppValue)
+{
+ // If we're creating an ICDValue for a field added with EnC, the local address will be null, since the field
+ // will not be included with the local cached copy of the ICDObjectValue to which the field belongs.
+ // This means we need to compute the size for the type of the field, and then determine whether this
+ // should also be the size for the localValue we pass to CreateValueByType. The only way we can tell if this
+ // is an EnC added field is if the local address is NULL.
+ ULONG32 localSize = localAddress != NULL ? size : 0;
+
+ CordbAppDomain * pAppdomain = pType->GetAppDomain();
+
+ CordbValue::CreateValueByType(pAppdomain,
+ pType,
+ kUnboxed,
+ TargetBuffer(m_remoteValue.pAddress + offset, size),
+ MemoryRange(localAddress, localSize),
+ NULL, // remote reg
+ ppValue); // throws
+} // RemoteValueHome::CreateInternalValue
+
+// Gets the value of a field or element of an existing ICDValue instance and returns it in dest
+void RemoteValueHome::GetInternalValue(MemoryRange dest, SIZE_T offset)
+{
+ _ASSERTE((!m_remoteValue.IsEmpty()) && (dest.StartAddress() != NULL));
+
+ m_pProcess->SafeReadBuffer(TargetBuffer(m_remoteValue.pAddress + offset, (ULONG)dest.Size()),
+ (BYTE *)dest.StartAddress());
+} // RemoteValueHome::GetInternalValue
+
+// copies the register information from this to a RemoteAddress instance
+// virtual
+void RemoteValueHome::CopyToIPCEType(RemoteAddress * pRegAddr)
+{
+ pRegAddr->kind = RAK_NONE;
+} // RegisterValueHome::CopyToIPCEType
+
+
+// ============================================================================
+// RegisterValueHome implementation
+// ============================================================================
+
+// constructor
+// Arguments:
+// input: pProcess - process for this value
+// ppRemoteRegAddr - enregistered value information
+//
+RegisterValueHome::RegisterValueHome(CordbProcess * pProcess,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr):
+ ValueHome(pProcess)
+{
+ EnregisteredValueHome * pRemoteRegAddr = ppRemoteRegAddr == NULL ? NULL : ppRemoteRegAddr->GetValue();
+ // in the general case, we should have either a remote address or a register address, but FuncEval makes
+ // empty GenericValues for literals, so it's possible that we have neither address
+
+ if (pRemoteRegAddr != NULL)
+ {
+ m_pRemoteRegAddr = pRemoteRegAddr;
+ // be sure not to delete the remote register information on exit
+ ppRemoteRegAddr->SuppressRelease();
+ }
+ else
+ {
+ m_pRemoteRegAddr = NULL;
+ }
+ } // RegisterValueHome::RegisterValueHome
+
+// clean up resources as necessary
+void RegisterValueHome::Clear()
+{
+ if (m_pRemoteRegAddr != NULL)
+ {
+ delete m_pRemoteRegAddr;
+ m_pRemoteRegAddr = NULL;
+ }
+} // RegisterValueHome::Clear
+
+// Gets a value and returns it in dest
+// virtual
+void RegisterValueHome::GetValue(MemoryRange dest)
+{
+ // FuncEval makes empty CordbGenericValue instances for literals, which will have a RegisterValueHome,
+ // but we should not be calling this in that case; we should be able to assert that the register
+ // address isn't NULL
+ _ASSERTE(m_pRemoteRegAddr != NULL);
+ m_pRemoteRegAddr->GetEnregisteredValue(dest); // throws
+} // RegisterValueHome::GetValue
+
+// Sets a location to the value provided in src
+void RegisterValueHome::SetValue(MemoryRange src, CordbType * pType)
+{
+ SetEnregisteredValue(src, IsSigned(pType->m_elementType)); // throws
+} // RegisterValueHome::SetValue
+
+// creates an ICDValue for a field or array element or for the value type of a boxed object
+// virtual
+void RegisterValueHome::CreateInternalValue(CordbType * pType,
+ SIZE_T offset,
+ void * localAddress,
+ ULONG32 size,
+ ICorDebugValue ** ppValue)
+{
+ TargetBuffer remoteValue(PTR_TO_CORDB_ADDRESS((void *)NULL),0);
+ EnregisteredValueHomeHolder pRemoteReg(NULL);
+
+ _ASSERTE(m_pRemoteRegAddr != NULL);
+ // Remote register address is the same as the parent....
+ /*
+ * <TODO>
+ * nickbe 10/17/2002 07:31:53
+ * If this object consists of two register-sized fields, e.g.
+ * struct Point
+ * {
+ * int x;
+ * int y;
+ * };
+ * then the variable home of this object is not necessarily the variable
+ * home for member data within this object. For example, if we have
+ * Point p;
+ * and p.x is in a register, while p.y is in memory, then clearly the
+ * home of p (RAK_REGMEM) is not the same as the home of p.x (RAK_MEM).
+ *
+ * Currently the JIT does not split compound objects in this way. It
+ * will only split an object that has exactly one field that is twice
+ * the size of the register
+ * </TODO>
+ */
+ _ASSERTE(offset == 0);
+ pRemoteReg.Assign(m_pRemoteRegAddr->Clone());
+
+ EnregisteredValueHomeHolder * pRegHolder = pRemoteReg.GetAddr();
+
+ // create a value for the member field.
+ CordbValue::CreateValueByType(pType->GetAppDomain(),
+ pType,
+ kUnboxed,
+ EMPTY_BUFFER, // remote address
+ MemoryRange(localAddress, size),
+ pRegHolder,
+ ppValue); // throws
+} // RegisterValueHome::CreateInternalValue
+
+// Gets the value of a field or element of an existing ICDValue instance and returns it in dest
+// virtual
+void RegisterValueHome::GetInternalValue(MemoryRange dest, SIZE_T offset)
+{
+ // currently, we can't have an enregistered value that has more than one field or element, so
+ // there's nothing to do here but ASSERT. If the JIT changes what can be enregistered, we'll have
+ // work to do here
+ _ASSERTE(!"Compound types are not enregistered--we shouldn't be here");
+ ThrowHR(E_INVALIDARG);
+} // RegisterValueHome::GetInternalValue
+
+
+// copies the register information from this to a RemoteAddress instance
+// virtual
+void RegisterValueHome::CopyToIPCEType(RemoteAddress * pRegAddr)
+{
+ if(m_pRemoteRegAddr != NULL)
+ {
+ m_pRemoteRegAddr->CopyToIPCEType(pRegAddr);
+ }
+ else
+ {
+ pRegAddr->kind = RAK_NONE;
+ }
+} // RegisterValueHome::CopyToIPCEType
+
+// sets a remote enregistered location to a new value
+// Arguments:
+// input: src - contains the new value
+// fIsSigned - indicates whether the new value is signed (needed for proper extension
+// Return value: S_OK on success or CORDBG_E_SET_VALUE_NOT_ALLOWED_ON_NONLEAF_FRAME, CORDBG_E_CONTEXT_UNVAILABLE,
+// or HRESULT values from writing process memory
+void RegisterValueHome::SetEnregisteredValue(MemoryRange src, bool fIsSigned)
+{
+ _ASSERTE(m_pRemoteRegAddr != NULL);
+ // Get the thread's context so we can update it.
+ DT_CONTEXT * cTemp;
+ const CordbNativeFrame * frame = m_pRemoteRegAddr->GetFrame();
+
+ // Can't set an enregistered value unless the frame the value was
+ // from is also the current leaf frame. This is because we don't
+ // track where we get the registers from every frame from.
+
+ if (!frame->IsLeafFrame())
+ {
+ ThrowHR(CORDBG_E_SET_VALUE_NOT_ALLOWED_ON_NONLEAF_FRAME);
+ }
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // This may throw, in which case we want to return our own HRESULT.
+ hr = frame->m_pThread->GetManagedContext(&cTemp);
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ // If we failed to get the context, then we must not be in a leaf frame.
+ ThrowHR(CORDBG_E_SET_VALUE_NOT_ALLOWED_ON_NONLEAF_FRAME);
+ }
+
+ // Its important to copy this context that we're given a ptr to.
+ DT_CONTEXT c;
+ c = *cTemp;
+
+ m_pRemoteRegAddr->SetEnregisteredValue(src, &c, fIsSigned);
+
+ // Set the thread's modified context.
+ IfFailThrow(frame->m_pThread->SetManagedContext(&c));
+} // RegisterValueHome::SetEnregisteredValue
+
+
+// Get an enregistered value from the register display of the native frame
+// Arguments:
+// output: dest - buffer will hold the register value
+// Note: Throws E_NOTIMPL for attempts to get an enregistered value for a float register
+// or for 64-bit platforms
+void RegisterValueHome::GetEnregisteredValue(MemoryRange dest)
+{
+#if !defined(DBG_TARGET_X86)
+ _ASSERTE(!"@TODO IA64/AMD64 -- Not Yet Implemented");
+ ThrowHR(E_NOTIMPL);
+#else // DBG_TARGET_X86
+ _ASSERTE(m_pRemoteRegAddr != NULL);
+
+ m_pRemoteRegAddr->GetEnregisteredValue(dest); // throws
+#endif // !DBG_TARGET_X86
+} // RegisterValueHome::GetEnregisteredValue
+
+// Is this a signed type or unsigned type?
+// Useful to known when we need to sign-extend.
+bool RegisterValueHome::IsSigned(CorElementType elementType)
+{
+ switch (elementType)
+ {
+ case ELEMENT_TYPE_I1:
+ case ELEMENT_TYPE_I2:
+ case ELEMENT_TYPE_I4:
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_I:
+ return true;
+
+ default:
+ return false;
+ }
+} // RegisterValueHome::IsSigned
+
+// ============================================================================
+// HandleValueHome implementation
+// ============================================================================
+
+//
+CORDB_ADDRESS HandleValueHome::GetAddress()
+{
+ _ASSERTE((m_pProcess != NULL) && !m_vmObjectHandle.IsNull());
+ CORDB_ADDRESS handle = PTR_TO_CORDB_ADDRESS((void *)NULL);
+ EX_TRY
+ {
+ handle = m_pProcess->GetDAC()->GetHandleAddressFromVmHandle(m_vmObjectHandle);
+ }
+ EX_CATCH
+ {
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+ return handle;
+}
+
+// Gets a value and returns it in dest
+// virtual
+void HandleValueHome::GetValue(MemoryRange dest)
+{
+ _ASSERTE((m_pProcess != NULL) && !m_vmObjectHandle.IsNull());
+ CORDB_ADDRESS objPtr = PTR_TO_CORDB_ADDRESS((void *)NULL);
+ objPtr = m_pProcess->GetDAC()->GetHandleAddressFromVmHandle(m_vmObjectHandle);
+
+ _ASSERTE(dest.Size() <= sizeof(void *));
+ _ASSERTE(dest.StartAddress() != NULL);
+ _ASSERTE(objPtr != NULL);
+ m_pProcess->SafeReadBuffer(TargetBuffer(objPtr, sizeof(void *)), (BYTE *)dest.StartAddress());
+} // HandleValueHome::GetValue
+
+// Sets a location to the value provided in src
+// virtual
+void HandleValueHome::SetValue(MemoryRange src, CordbType * pType)
+{
+ _ASSERTE(!m_vmObjectHandle.IsNull());
+
+ DebuggerIPCEvent event;
+
+ m_pProcess->InitIPCEvent(&event, DB_IPCE_SET_REFERENCE, true, VMPTR_AppDomain::NullPtr());
+
+ event.SetReference.objectRefAddress = NULL;
+ event.SetReference.vmObjectHandle = m_vmObjectHandle;
+ event.SetReference.newReference = *((void **)src.StartAddress());
+
+ // Note: two-way event here...
+ IfFailThrow(m_pProcess->SendIPCEvent(&event, sizeof(DebuggerIPCEvent)));
+
+ _ASSERTE(event.type == DB_IPCE_SET_REFERENCE_RESULT);
+
+ IfFailThrow(event.hr);
+} // HandleValueHome::SetValue
+
+// creates an ICDValue for a field or array element or for the value type of a boxed object
+// virtual
+void HandleValueHome::CreateInternalValue(CordbType * pType,
+ SIZE_T offset,
+ void * localAddress,
+ ULONG32 size,
+ ICorDebugValue ** ppValue)
+{
+ _ASSERTE(!"References don't have sub-objects--we shouldn't be here");
+ ThrowHR(E_INVALIDARG);
+
+} // HandleValueHome::CreateInternalValue
+
+// Gets the value of a field or element of an existing ICDValue instance and returns it in dest
+// virtual
+void HandleValueHome::GetInternalValue(MemoryRange dest, SIZE_T offset)
+{
+ _ASSERTE(!"References don't have sub-objects--we shouldn't be here");
+ ThrowHR(E_INVALIDARG);
+} // HandleValueHome::GetInternalValue
+
+// copies the register information from this to a RemoteAddress instance
+// virtual
+void HandleValueHome::CopyToIPCEType(RemoteAddress * pRegAddr)
+{
+ pRegAddr->kind = RAK_NONE;
+} // HandleValueHome::CopyToIPCEType
+
+// ============================================================================
+// VCRemoteValueHome implementation
+// ============================================================================
+
+// Sets a location to the value provided in src
+// Arguments:
+// input: src - buffer containing the new value to be set--memory for this buffer is owned by the caller
+// pType - type information for the value
+// output: none, but sets the value on success
+// Note: Throws CORDBG_E_CLASS_NOT_LOADED or errors from WriteProcessMemory or
+// GetRemoteBuffer on failure
+void VCRemoteValueHome::SetValue(MemoryRange src, CordbType * pType)
+{
+ _ASSERTE(!m_remoteValue.IsEmpty());
+
+ // send a Set Value Class message to the right side with the address of this value class, the address of
+ // the new data, and the class of the value class that we're setting.
+ DebuggerIPCEvent event;
+
+ // First, we have to make room on the Left Side for the new data for the value class. We allocate
+ // memory on the Left Side for this, then write the new data across. The Set Value Class message will
+ // free the buffer when its done.
+ void *buffer = NULL;
+ IfFailThrow(m_pProcess->GetAndWriteRemoteBuffer(NULL,
+ m_remoteValue.cbSize,
+ CORDB_ADDRESS_TO_PTR(src.StartAddress()),
+ &buffer));
+
+ // Finally, send over the Set Value Class message.
+ m_pProcess->InitIPCEvent(&event, DB_IPCE_SET_VALUE_CLASS, true, VMPTR_AppDomain::NullPtr());
+ event.SetValueClass.oldData = CORDB_ADDRESS_TO_PTR(m_remoteValue.pAddress);
+ event.SetValueClass.newData = buffer;
+ IfFailThrow(pType->TypeToBasicTypeData(&event.SetValueClass.type));
+
+ // Note: two-way event here...
+ IfFailThrow(m_pProcess->SendIPCEvent(&event, sizeof(DebuggerIPCEvent)));
+
+ _ASSERTE(event.type == DB_IPCE_SET_VALUE_CLASS_RESULT);
+
+ IfFailThrow(event.hr);
+} // VCRemoteValueHome::SetValue
+
+
+// ============================================================================
+// RefRemoteValueHome implementation
+// ============================================================================
+
+// constructor
+// Arguments:
+// input: pProcess - process for this value
+// remoteValue - remote location information
+// vmObjHandle - object handle
+RefRemoteValueHome ::RefRemoteValueHome (CordbProcess * pProcess,
+ TargetBuffer remoteValue):
+ RemoteValueHome(pProcess, remoteValue)
+{
+ // caller supplies remoteValue, to work w/ Func-eval.
+ _ASSERTE((!remoteValue.IsEmpty()) && (remoteValue.cbSize == sizeof (void *)));
+
+} // RefRemoteValueHome::RefRemoteValueHome
+
+// Sets a location to the value provided in src
+// Arguments:
+// input: src - buffer containing the new value to be set--memory for this buffer is owned by the caller
+// pType - type information for the value
+// output: none, but sets the value on success
+// Return Value: S_OK on success or CORDBG_E_CLASS_NOT_LOADED or errors from WriteProcessMemory or
+// GetRemoteBuffer on failure
+void RefRemoteValueHome::SetValue(MemoryRange src, CordbType * pType)
+{
+ // We had better have a remote address.
+ _ASSERTE(!m_remoteValue.IsEmpty());
+
+ // send a Set Reference message to the right side with the address of this reference and whether or not
+ // the reference points to a handle.
+
+ // If it's a reference but not a GC root then just we can just treat it like raw data (like a DWORD).
+ // This would include things like "int*", and E_T_FNPTR. If it is a GC root, then we need to go over to
+ // the LS to update the WriteBarrier.
+ if ((pType != NULL) && !pType->IsGCRoot())
+ {
+ m_pProcess->SafeWriteBuffer(m_remoteValue, (BYTE *)src.StartAddress());
+ }
+ else
+ {
+ DebuggerIPCEvent event;
+
+ m_pProcess->InitIPCEvent(&event, DB_IPCE_SET_REFERENCE, true, VMPTR_AppDomain::NullPtr());
+
+ event.SetReference.objectRefAddress = CORDB_ADDRESS_TO_PTR(m_remoteValue.pAddress);
+ event.SetReference.vmObjectHandle = VMPTR_OBJECTHANDLE::NullPtr();
+ event.SetReference.newReference = *((void **)src.StartAddress());
+
+ // Note: two-way event here...
+ IfFailThrow(m_pProcess->SendIPCEvent(&event, sizeof(DebuggerIPCEvent)));
+
+ _ASSERTE(event.type == DB_IPCE_SET_REFERENCE_RESULT);
+
+ IfFailThrow(event.hr);
+ }
+} // RefRemoteValueHome::SetValue
+
+// ============================================================================
+// RefValueHome implementation
+// ============================================================================
+
+// constructor
+// Only one of the location types should be non-NULL, but we pass all of them to the
+// constructor so we can instantiate m_pHome correctly.
+// Arguments:
+// input: pProcess - process to which the value belongs
+// remoteValue - a target location holding the object reference
+// ppRemoteRegAddr - information about the register that holds the object ref
+// vmObjHandle - an object handle that holds the object ref
+RefValueHome::RefValueHome(CordbProcess * pProcess,
+ TargetBuffer remoteValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr,
+ VMPTR_OBJECTHANDLE vmObjHandle)
+{
+ if (!remoteValue.IsEmpty())
+ {
+ NewHolder<ValueHome> pHome(new RefRemoteValueHome(pProcess, remoteValue));
+ m_fNullObjHandle = true;
+ }
+ else if (!vmObjHandle.IsNull())
+ {
+ NewHolder<ValueHome> pHome(new HandleValueHome(pProcess, vmObjHandle));
+ m_fNullObjHandle = false;
+ }
+ else
+ {
+ NewHolder<ValueHome> pHome(new RegisterValueHome(pProcess, ppRemoteRegAddr));
+ m_fNullObjHandle = true;
+ }
+
+
+} // RefValueHome::RefValueHome
+
diff --git a/src/debug/di/windowspipeline.cpp b/src/debug/di/windowspipeline.cpp
new file mode 100644
index 0000000000..c3050e3290
--- /dev/null
+++ b/src/debug/di/windowspipeline.cpp
@@ -0,0 +1,419 @@
+// 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.
+//*****************************************************************************
+// File: WindowsPipeline.cpp
+//
+
+//
+// Implements the native-pipeline on Windows OS.
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "nativepipeline.h"
+
+#include <Tlhelp32.h>
+
+#include "holder.h"
+
+
+DWORD GetProcessId(const DEBUG_EVENT * pEvent)
+{
+ return pEvent->dwProcessId;
+}
+DWORD GetThreadId(const DEBUG_EVENT * pEvent)
+{
+ return pEvent->dwThreadId;
+}
+
+// Get exception event
+BOOL IsExceptionEvent(const DEBUG_EVENT * pEvent, BOOL * pfFirstChance, const EXCEPTION_RECORD ** ppRecord)
+{
+ if (pEvent->dwDebugEventCode != EXCEPTION_DEBUG_EVENT)
+ {
+ *pfFirstChance = FALSE;
+ *ppRecord = NULL;
+ return FALSE;
+ }
+ *pfFirstChance = pEvent->u.Exception.dwFirstChance;
+ *ppRecord = &(pEvent->u.Exception.ExceptionRecord);
+ return TRUE;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Class serves as a connector to win32 native-debugging API.
+class WindowsNativePipeline :
+ public INativeEventPipeline
+{
+public:
+ WindowsNativePipeline()
+ {
+ // Default value for Win32.
+ m_fKillOnExit = true;
+ m_dwProcessId = 0;
+ }
+
+ // Call to free up the pipeline.
+ virtual void Delete();
+
+ virtual BOOL DebugSetProcessKillOnExit(bool fKillOnExit);
+
+ // Create
+ virtual HRESULT CreateProcessUnderDebugger(
+ MachineInfo machineInfo,
+ LPCWSTR lpApplicationName,
+ LPCWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation);
+
+ // Attach
+ virtual HRESULT DebugActiveProcess(MachineInfo machineInfo, DWORD processId);
+
+ // Detach
+ virtual HRESULT DebugActiveProcessStop(DWORD processId);
+
+ virtual BOOL WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess);
+
+ virtual BOOL ContinueDebugEvent(
+ DWORD dwProcessId,
+ DWORD dwThreadId,
+ DWORD dwContinueStatus
+ );
+
+ // Return a handle for the debuggee process.
+ virtual HANDLE GetProcessHandle();
+
+ // Terminate the debuggee process.
+ virtual BOOL TerminateProcess(UINT32 exitCode);
+
+ // Resume any suspended threads
+ virtual HRESULT EnsureThreadsRunning();
+
+protected:
+ void UpdateDebugSetProcessKillOnExit();
+
+ HRESULT IsRemoteDebuggerPresent(DWORD processId, BOOL* pfDebuggerPresent);
+
+ // Cached value from DebugSetProcessKillOnExit.
+ // This is thread-local, and impacts all debuggees on the thread.
+ bool m_fKillOnExit;
+
+ DWORD m_dwProcessId;
+};
+
+// Allocate and return a pipeline object for this platform
+INativeEventPipeline * NewPipelineForThisPlatform()
+{
+ return new (nothrow) WindowsNativePipeline();
+}
+
+// Call to free up the pipeline.
+void WindowsNativePipeline::Delete()
+{
+ delete this;
+}
+
+
+// set whether to kill outstanding debuggees when the debugger exits.
+BOOL WindowsNativePipeline::DebugSetProcessKillOnExit(bool fKillOnExit)
+{
+ // Can't call kernel32!DebugSetProcessKillOnExit until after the event thread
+ // has spawned a debuggee. So cache the value now and call it later.
+ // This bit is enforced in code:WindowsNativePipeline::UpdateDebugSetProcessKillOnExit
+ m_fKillOnExit = fKillOnExit;
+ return TRUE;
+}
+
+// Enforces the bit set in code:WindowsNativePipeline::DebugSetProcessKillOnExit
+void WindowsNativePipeline::UpdateDebugSetProcessKillOnExit()
+{
+#if !defined(FEATURE_CORESYSTEM)
+ // Late bind to DebugSetProcessKillOnExit - WinXP and above only
+ HModuleHolder hKernel32;
+ hKernel32 = WszLoadLibrary(W("kernel32"));
+ SIMPLIFYING_ASSUMPTION(hKernel32 != NULL);
+ if (hKernel32 == NULL)
+ return;
+
+ typedef BOOL (*DebugSetProcessKillOnExitSig) (BOOL);
+ DebugSetProcessKillOnExitSig pDebugSetProcessKillOnExit =
+ reinterpret_cast<DebugSetProcessKillOnExitSig>(GetProcAddress(hKernel32, "DebugSetProcessKillOnExit"));
+
+ // If the API doesn't exist (eg. Win2k) - there isn't anything we can do, just
+ // silently ignore the request.
+ if (pDebugSetProcessKillOnExit == NULL)
+ return;
+
+ BOOL ret = pDebugSetProcessKillOnExit(m_fKillOnExit);
+
+ // Not a good failure path here.
+ // 1) This shouldn't fail.
+ // 2) Even if it does, this is likely called after the debuggee
+ // has already been created, and if this API fails, most scenarios will
+ // be unaffected, so we don't want to fail the overall debugging session.
+ SIMPLIFYING_ASSUMPTION(ret);
+
+#else
+ // The API doesn't exit on CoreSystem, just return
+ return;
+#endif
+}
+
+// Create an process under the debugger.
+HRESULT WindowsNativePipeline::CreateProcessUnderDebugger(
+ MachineInfo machineInfo,
+ LPCWSTR lpApplicationName,
+ LPCWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation)
+{
+ // This is always doing Native-debugging at the OS-level.
+ dwCreationFlags |= (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS);
+
+ BOOL ret = ::WszCreateProcess(
+ lpApplicationName,
+ lpCommandLine,
+ lpProcessAttributes,
+ lpThreadAttributes,
+ bInheritHandles,
+ dwCreationFlags,
+ lpEnvironment,
+ lpCurrentDirectory,
+ lpStartupInfo,
+ lpProcessInformation);
+ if (!ret)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ m_dwProcessId = lpProcessInformation->dwProcessId;
+ UpdateDebugSetProcessKillOnExit();
+ return S_OK;
+}
+
+// Attach the debugger to this process.
+HRESULT WindowsNativePipeline::DebugActiveProcess(MachineInfo machineInfo, DWORD processId)
+{
+ HRESULT hr = E_FAIL;
+ BOOL ret = ::DebugActiveProcess(processId);
+
+ if (ret)
+ {
+ hr = S_OK;
+ m_dwProcessId = processId;
+ UpdateDebugSetProcessKillOnExit();
+ }
+ else
+ {
+ hr = HRESULT_FROM_GetLastError();
+
+ // There are at least two scenarios in which DebugActiveProcess() returns E_INVALIDARG:
+ // 1) if the specified process doesn't exist, or
+ // 2) if the specified process already has a debugger atttached
+ // We need to distinguish these two cases in order to return the correct HR.
+ if (hr == E_INVALIDARG)
+ {
+ // Check whether a debugger is known to be already attached.
+ // Note that this API won't work on some OSes, in which case we err on the side of returning E_INVALIDARG
+ // even though a debugger may be attached. Another approach could be to assume that if
+ // OpenProcess succeeded, then DebugActiveProcess must only have failed because a debugger is
+ // attached. But I think it's better to only return the specific error code if we know for sure
+ // the case is true.
+ BOOL fIsDebuggerPresent = FALSE;
+ if (SUCCEEDED(IsRemoteDebuggerPresent(processId, &fIsDebuggerPresent)))
+ {
+ if (fIsDebuggerPresent)
+ {
+ hr = CORDBG_E_DEBUGGER_ALREADY_ATTACHED;
+ }
+ }
+ }
+ }
+
+ return hr;
+}
+
+// Determine (if possible) whether a debugger is attached to the target process
+HRESULT WindowsNativePipeline::IsRemoteDebuggerPresent(DWORD processId, BOOL* pfDebuggerPresent)
+{
+#if !defined(FEATURE_CORESYSTEM)
+
+ // Get a process handle for the process ID.
+ HandleHolder hProc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, processId);
+ if (hProc == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ // Delay-bind to CheckRemoteDebuggerPresent - WinXP SP1 and above only
+ HModuleHolder hKernel32;
+ hKernel32 = WszLoadLibrary(W("kernel32"));
+ if (hKernel32 == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ typedef BOOL (*CheckRemoteDebuggerPresentSig) (HANDLE, PBOOL);
+ CheckRemoteDebuggerPresentSig pCheckRemoteDebuggerPresent =
+ reinterpret_cast<CheckRemoteDebuggerPresentSig>(GetProcAddress(hKernel32, "CheckRemoteDebuggerPresent"));
+ if (pCheckRemoteDebuggerPresent == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ // API exists - call it
+ if (!pCheckRemoteDebuggerPresent(hProc, pfDebuggerPresent))
+ return HRESULT_FROM_GetLastError();
+
+ return S_OK;
+#else
+
+ //CoreSystem doesn't have this API
+ return E_FAIL;
+#endif
+}
+
+// Detach
+HRESULT WindowsNativePipeline::DebugActiveProcessStop(DWORD processId)
+{
+#if !defined(FEATURE_CORESYSTEM)
+ // Late-bind to DebugActiveProcessStop since it's WinXP and above only
+ HModuleHolder hKernel32;
+ hKernel32 = WszLoadLibrary(W("kernel32"));
+ if (hKernel32 == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ typedef BOOL (*DebugActiveProcessStopSig) (DWORD);
+ DebugActiveProcessStopSig pDebugActiveProcessStop =
+ reinterpret_cast<DebugActiveProcessStopSig>(GetProcAddress(hKernel32, "DebugActiveProcessStop"));
+
+ // Win2K will fail here - can't find DebugActiveProcessStop
+ if (pDebugActiveProcessStop == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ // Ok, the API exists, call it
+ if (!pDebugActiveProcessStop(processId))
+ {
+ // Detach itself failed
+ return HRESULT_FROM_GetLastError();
+ }
+#else
+ // The API exists, call it
+ if (!::DebugActiveProcessStop(processId))
+ {
+ // Detach itself failed
+ return HRESULT_FROM_GetLastError();
+ }
+#endif
+ return S_OK;
+}
+
+BOOL WindowsNativePipeline::WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess)
+{
+ return ::WaitForDebugEvent(pEvent, dwTimeout);
+}
+
+BOOL WindowsNativePipeline::ContinueDebugEvent(
+ DWORD dwProcessId,
+ DWORD dwThreadId,
+ DWORD dwContinueStatus
+)
+{
+ return ::ContinueDebugEvent(dwProcessId, dwThreadId, dwContinueStatus);
+}
+
+// Return a handle for the debuggee process.
+HANDLE WindowsNativePipeline::GetProcessHandle()
+{
+ _ASSERTE(m_dwProcessId != 0);
+
+ return ::OpenProcess(PROCESS_DUP_HANDLE |
+ PROCESS_QUERY_INFORMATION |
+ PROCESS_TERMINATE |
+ PROCESS_VM_OPERATION |
+ PROCESS_VM_READ |
+ PROCESS_VM_WRITE |
+ SYNCHRONIZE,
+ FALSE,
+ m_dwProcessId);
+}
+
+// Terminate the debuggee process.
+BOOL WindowsNativePipeline::TerminateProcess(UINT32 exitCode)
+{
+ _ASSERTE(m_dwProcessId != 0);
+
+ // Get a process handle for the process ID.
+ HandleHolder hProc = OpenProcess(PROCESS_TERMINATE, FALSE, m_dwProcessId);
+
+ if (hProc == NULL)
+ {
+ return FALSE;
+ }
+
+ return ::TerminateProcess(hProc, exitCode);
+}
+
+// Resume any suspended threads (but just once)
+HRESULT WindowsNativePipeline::EnsureThreadsRunning()
+{
+#ifdef FEATURE_CORESYSTEM
+ _ASSERTE("NYI");
+ return E_FAIL;
+#else
+ _ASSERTE(m_dwProcessId != 0);
+
+ // Take a snapshot of all running threads (similar to ShimProcess::QueueFakeThreadAttachEventsNativeOrder)
+ // Alternately we could return thread creation/exit in WaitForDebugEvent. But we expect this to be used
+ // very rarely, so no need to complicate more common codepaths.
+ HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
+ THREADENTRY32 te32;
+
+ hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
+ if (hThreadSnap == INVALID_HANDLE_VALUE)
+ return HRESULT_FROM_GetLastError();
+
+ // HandleHolder doesn't deal with INVALID_HANDLE_VALUE, so we only assign if we have a legal value.
+ HandleHolder hSnapshotHolder(hThreadSnap);
+
+ // Fill in the size of the structure before using it.
+ te32.dwSize = sizeof(THREADENTRY32);
+
+ // Retrieve information about the first thread, and exit if unsuccessful
+ if (!Thread32First(hThreadSnap, &te32))
+ return HRESULT_FROM_GetLastError();
+
+ // Now walk the thread list of the system and attempt to resume any that are part of this process
+ // Ignore errors - this is a best effort (but ASSERT in CHK builds since we don't expect errors
+ // in practice - we expect the process to be frozen at a debug event, so no races etc.)
+
+ HRESULT hr = S_FALSE; // no thread was resumed
+ do
+ {
+ if (te32.th32OwnerProcessID == m_dwProcessId)
+ {
+ HandleHolder hThread = ::OpenThread(THREAD_SUSPEND_RESUME, FALSE, te32.th32ThreadID);
+ _ASSERTE(hThread != NULL);
+ if (hThread != NULL)
+ {
+ // Resume each thread exactly once (if they were suspended multiple times,
+ // then EnsureThreadsRunning would need to be called multiple times until it
+ // returned S_FALSE.
+ DWORD prevCount = ::ResumeThread(hThread);
+ _ASSERTE(prevCount >= 0);
+ if (prevCount >= 1)
+ hr = S_OK; // some thread was resumed
+ }
+ }
+ } while(Thread32Next(hThreadSnap, &te32));
+
+ return hr;
+#endif
+}