diff options
Diffstat (limited to 'src/debug/di')
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 Binary files differnew file mode 100644 index 0000000000..b16ba39fe4 --- /dev/null +++ b/src/debug/di/ICorDebugValueTypes.vsd 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**>(¤tStackFrame.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(®Buffer[iRegister++], + &(FPSTACK_FROM_INDEX(0)), + sizeof(CORDB_REGISTER)); + break; + case REGISTER_X86_FPSTACK_1: + memcpy( ®Buffer[iRegister++], + & (FPSTACK_FROM_INDEX( 1 ) ), + sizeof(CORDB_REGISTER) ); + break; + case REGISTER_X86_FPSTACK_2: + memcpy( ®Buffer[iRegister++], + & (FPSTACK_FROM_INDEX( 2 ) ), + sizeof(CORDB_REGISTER) ); break; + case REGISTER_X86_FPSTACK_3: + memcpy( ®Buffer[iRegister++], + & (FPSTACK_FROM_INDEX( 3 ) ), + sizeof(CORDB_REGISTER) ); break; + case REGISTER_X86_FPSTACK_4: + memcpy( ®Buffer[iRegister++], + & (FPSTACK_FROM_INDEX( 4 ) ), + sizeof(CORDB_REGISTER) ); break; + case REGISTER_X86_FPSTACK_5: + memcpy( ®Buffer[iRegister++], + & (FPSTACK_FROM_INDEX( 5 ) ), + sizeof(CORDB_REGISTER) ); break; + case REGISTER_X86_FPSTACK_6: + memcpy( ®Buffer[iRegister++], + & (FPSTACK_FROM_INDEX( 6 ) ), + sizeof(CORDB_REGISTER) ); break; + case REGISTER_X86_FPSTACK_7: + memcpy( ®Buffer[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, §ionHeader)); + 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, §ionHeader)); + 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, ¶ms); + } + + // 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 +} |