diff --git a/docs/design/datacontracts/Debugger.md b/docs/design/datacontracts/Debugger.md new file mode 100644 index 00000000000000..03ee1ebae704d2 --- /dev/null +++ b/docs/design/datacontracts/Debugger.md @@ -0,0 +1,68 @@ +# Contract Debugger + +This contract is for reading debugger state from the target process, including initialization status, configuration flags, metadata update state, and JIT attach state. + +## APIs of contract + +```csharp +record struct DebuggerData(uint DefinesBitField, uint MDStructuresVersion); +``` + +```csharp +bool TryGetDebuggerData(out DebuggerData data); +int GetAttachStateFlags(); +bool MetadataUpdatesApplied(); +``` + +## Version 1 + +The contract depends on the following globals + +| Global Name | Type | Description | +| --- | --- | --- | +| `Debugger` | TargetPointer | Address of the pointer to the Debugger instance (`&g_pDebugger`) | +| `CLRJitAttachState` | TargetPointer | Pointer to the CLR JIT attach state flags | +| `MetadataUpdatesApplied` | TargetPointer | Pointer to the g_metadataUpdatesApplied flag | + +The contract additionally depends on these data descriptors + +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| `Debugger` | `LeftSideInitialized` | Whether the left-side debugger infrastructure is initialized | +| `Debugger` | `Defines` | Bitfield of compile-time debugger feature defines | +| `Debugger` | `MDStructuresVersion` | Version of metadata data structures | + +```csharp +bool TryGetDebuggerData(out DebuggerData data) +{ + data = default; + // The Debugger global points to g_pDebugger (a pointer-to-pointer). + // First read gets the address of g_pDebugger, second dereferences it. + TargetPointer debuggerPtrPtr = target.ReadGlobalPointer("Debugger"); + if (debuggerPtrPtr == TargetPointer.Null) + return false; + TargetPointer debuggerPtr = target.ReadPointer(debuggerPtrPtr); + if (debuggerPtr == TargetPointer.Null) + return false; + int leftSideInitialized = target.Read(debuggerPtr + /* Debugger::LeftSideInitialized offset */); + if (leftSideInitialized == 0) + return false; + data = new DebuggerData( + DefinesBitField: target.Read(debuggerPtr + /* Debugger::Defines offset */), + MDStructuresVersion: target.Read(debuggerPtr + /* Debugger::MDStructuresVersion offset */)); + return true; +} + +int GetAttachStateFlags() +{ + TargetPointer addr = target.ReadGlobalPointer("CLRJitAttachState"); + return (int)target.Read(addr); +} + +bool MetadataUpdatesApplied() +{ + if (target.TryReadGlobalPointer("MetadataUpdatesApplied", out TargetPointer addr)) + return target.Read(addr) != 0; + return false; +} +``` diff --git a/src/coreclr/debug/daccess/cdac.cpp b/src/coreclr/debug/daccess/cdac.cpp index d62e134a700fe3..6c84fbe688e091 100644 --- a/src/coreclr/debug/daccess/cdac.cpp +++ b/src/coreclr/debug/daccess/cdac.cpp @@ -56,8 +56,13 @@ namespace int WriteToTargetCallback(uint64_t addr, const uint8_t* buff, uint32_t count, void* context) { - ICorDebugMutableDataTarget* target = static_cast(context); - HRESULT hr = target->WriteVirtual((CORDB_ADDRESS)addr, buff, count); + ICorDebugDataTarget* target = reinterpret_cast(context); + ICorDebugMutableDataTarget* mutableTarget = nullptr; + HRESULT hr = target->QueryInterface(__uuidof(ICorDebugMutableDataTarget), (void**)&mutableTarget); + if (FAILED(hr)) + return hr; + hr = mutableTarget->WriteVirtual((CORDB_ADDRESS)addr, buff, count); + mutableTarget->Release(); if (FAILED(hr)) return hr; @@ -75,7 +80,7 @@ namespace } } -CDAC CDAC::Create(uint64_t descriptorAddr, ICorDebugMutableDataTarget* target, IUnknown* legacyImpl) +CDAC CDAC::Create(uint64_t descriptorAddr, ICorDebugDataTarget* target, IUnknown* legacyImpl) { HMODULE cdacLib; if (!TryLoadCDACLibrary(&cdacLib)) @@ -125,3 +130,11 @@ void CDAC::CreateSosInterface(IUnknown** sos) int ret = createSosInterface(m_cdac_handle, m_legacyImpl, sos); _ASSERTE(ret == 0); } + +void CDAC::CreateDacDbiInterface(IUnknown** dbi) +{ + decltype(&cdac_reader_create_dacdbi_interface) createDacDbiInterface = reinterpret_cast(::GetProcAddress(m_module, "cdac_reader_create_dacdbi_interface")); + _ASSERTE(createDacDbiInterface != nullptr); + int ret = createDacDbiInterface(m_cdac_handle, m_legacyImpl, dbi); + _ASSERTE(ret == 0); +} diff --git a/src/coreclr/debug/daccess/cdac.h b/src/coreclr/debug/daccess/cdac.h index 559f20226df339..929ce7369e40f5 100644 --- a/src/coreclr/debug/daccess/cdac.h +++ b/src/coreclr/debug/daccess/cdac.h @@ -7,7 +7,7 @@ class CDAC final { public: // static - static CDAC Create(uint64_t descriptorAddr, ICorDebugMutableDataTarget *pDataTarget, IUnknown* legacyImpl); + static CDAC Create(uint64_t descriptorAddr, ICorDebugDataTarget *pDataTarget, IUnknown* legacyImpl); public: CDAC() = default; @@ -50,6 +50,7 @@ class CDAC final } void CreateSosInterface(IUnknown** sos); + void CreateDacDbiInterface(IUnknown** dbi); private: CDAC(HMODULE module, intptr_t handle, ICorDebugDataTarget* target, IUnknown* legacyImpl); diff --git a/src/coreclr/debug/daccess/daccess.cpp b/src/coreclr/debug/daccess/daccess.cpp index 661648e67afe50..82f5260afba6da 100644 --- a/src/coreclr/debug/daccess/daccess.cpp +++ b/src/coreclr/debug/daccess/daccess.cpp @@ -6667,7 +6667,7 @@ CLRDataCreateInstance(REFIID iid, HRESULT qiRes = pClrDataAccess->QueryInterface(IID_IUnknown, (void**)&thisImpl); _ASSERTE(SUCCEEDED(qiRes)); CDAC& cdac = pClrDataAccess->m_cdac; - cdac = CDAC::Create(contractDescriptorAddr, pClrDataAccess->m_pMutableTarget, thisImpl); + cdac = CDAC::Create(contractDescriptorAddr, pClrDataAccess->m_pTarget, thisImpl); if (cdac.IsValid()) { // Get SOS interfaces from the cDAC if available. diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index aebc7f56a78e06..276c15f9c6f535 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -31,6 +31,12 @@ #include "request_common.h" #include "conditionalweaktable.h" +#ifndef USE_DAC_TABLE_RVA +extern "C" bool TryGetSymbol(ICorDebugDataTarget* dataTarget, uint64_t baseAddress, const char* symbolName, uint64_t* symbolAddress); +#include +#define CAN_USE_CDAC +#endif + //----------------------------------------------------------------------------- // Have standard enter and leave macros at the DacDbi boundary to enforce // standard behavior. @@ -274,14 +280,60 @@ DacDbiInterfaceInstance( HRESULT hrStatus = pDac->Initialize(); - if (SUCCEEDED(hrStatus)) + if (FAILED(hrStatus)) { - *ppInterface = pDac; + delete pDac; + return hrStatus; } - else + +#ifdef CAN_USE_CDAC + CLRConfigNoCache enable = CLRConfigNoCache::Get("ENABLE_CDAC"); + if (enable.IsSet()) { - delete pDac; + DWORD val; + if (enable.TryAsInteger(10, val) && val == 1) + { + uint64_t contractDescriptorAddr = 0; + if (TryGetSymbol(pDac->m_pTarget, pDac->m_globalBase, "DotNetRuntimeContractDescriptor", &contractDescriptorAddr)) + { + IUnknown* legacyImpl; + HRESULT qiRes = pDac->QueryInterface(IID_IUnknown, (void**)&legacyImpl); + _ASSERTE(SUCCEEDED(qiRes)); + + CDAC& cdac = pDac->m_cdac; + cdac = CDAC::Create(contractDescriptorAddr, pDac->m_pTarget, legacyImpl); + if (cdac.IsValid()) + { + NonVMComHolder cdacInterface = nullptr; + cdac.CreateDacDbiInterface(&cdacInterface); + if (cdacInterface != nullptr) + { + IDacDbiInterface* pCDacDbi = nullptr; + HRESULT hr = cdacInterface->QueryInterface(__uuidof(IDacDbiInterface), (void**)&pCDacDbi); + if (SUCCEEDED(hr)) + { + // Lifetime is now managed by cDAC implementation + pDac->Release(); + // Release the AddRef from the QI for legacyImpl + pDac->Release(); + *ppInterface = pCDacDbi; + return S_OK; + } + } + } + + // Release the AddRef from the QI for legacyImpl + pDac->Release(); + } + + // If we requested to use the cDAC, but failed to create the cDAC interface, return failure + pDac->Release(); + return E_FAIL; + } } +#endif + + *ppInterface = pDac; return hrStatus; } diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index caeeded4fbb82d..c46fc57c7b9340 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -354,6 +354,15 @@ CDAC_TYPE_FIELD(SystemDomain, /*GlobalLoaderAllocator*/, GlobalLoaderAllocator, CDAC_TYPE_FIELD(SystemDomain, /*pointer*/, SystemAssembly, cdac_data::SystemAssembly) CDAC_TYPE_END(SystemDomain) +#if defined(DEBUGGING_SUPPORTED) && !defined(TARGET_WASM) +CDAC_TYPE_BEGIN(Debugger) +CDAC_TYPE_INDETERMINATE(Debugger) +CDAC_TYPE_FIELD(Debugger, /*int32*/, LeftSideInitialized, offsetof(Debugger, m_fLeftSideInitialized)) +CDAC_TYPE_FIELD(Debugger, /*uint32*/, Defines, offsetof(Debugger, m_defines)) +CDAC_TYPE_FIELD(Debugger, /*uint32*/, MDStructuresVersion, offsetof(Debugger, m_mdDataStructureVersion)) +CDAC_TYPE_END(Debugger) +#endif // DEBUGGING_SUPPORTED && !TARGET_WASM + CDAC_TYPE_BEGIN(ArrayListBase) CDAC_TYPE_INDETERMINATE(ArrayListBase) CDAC_TYPE_FIELD(ArrayListBase, /*uint32*/, Count, cdac_data::Count) @@ -1296,6 +1305,13 @@ CDAC_GLOBAL_POINTER(SystemDomain, cdac_data::SystemDomainPtr) CDAC_GLOBAL_POINTER(ThreadStore, &ThreadStore::s_pThreadStore) CDAC_GLOBAL_POINTER(FinalizerThread, &::g_pFinalizerThread) CDAC_GLOBAL_POINTER(GCThread, &::g_pSuspensionThread) +#if defined(DEBUGGING_SUPPORTED) && !defined(TARGET_WASM) +CDAC_GLOBAL_POINTER(Debugger, &::g_pDebugger) +CDAC_GLOBAL_POINTER(CLRJitAttachState, &::CLRJitAttachState) +#endif // DEBUGGING_SUPPORTED && !TARGET_WASM +#ifdef FEATURE_METADATA_UPDATER +CDAC_GLOBAL_POINTER(MetadataUpdatesApplied, &::g_metadataUpdatesApplied) +#endif // Add FrameIdentifier for all defined Frame types. Used to differentiate Frame objects. #define FRAME_TYPE_NAME(frameType) \ @@ -1444,6 +1460,9 @@ CDAC_GLOBAL_CONTRACT(ComWrappers, 1) #endif // FEATURE_COMWRAPPERS CDAC_GLOBAL_CONTRACT(ConditionalWeakTable, 1) CDAC_GLOBAL_CONTRACT(DacStreams, 1) +#if defined(DEBUGGING_SUPPORTED) && !defined(TARGET_WASM) +CDAC_GLOBAL_CONTRACT(Debugger, 1) +#endif // DEBUGGING_SUPPORTED && !TARGET_WASM CDAC_GLOBAL_CONTRACT(DebugInfo, 2) CDAC_GLOBAL_CONTRACT(EcmaMetadata, 1) CDAC_GLOBAL_CONTRACT(Exception, 1) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs index f47c90249aefa5..5f4e5a6a85ce11 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -110,6 +110,10 @@ public abstract class ContractRegistry /// Gets an instance of the AuxiliarySymbols contract for the target. /// public virtual IAuxiliarySymbols AuxiliarySymbols => GetContract(); + /// + /// Gets an instance of the Debugger contract for the target. + /// + public virtual IDebugger Debugger => GetContract(); public abstract TContract GetContract() where TContract : IContract; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugger.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugger.cs new file mode 100644 index 00000000000000..e8a8a237afc800 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugger.cs @@ -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. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +public record struct DebuggerData(uint DefinesBitField, uint MDStructuresVersion); + +public interface IDebugger : IContract +{ + static string IContract.Name { get; } = nameof(Debugger); + + bool TryGetDebuggerData(out DebuggerData data) => throw new NotImplementedException(); + int GetAttachStateFlags() => throw new NotImplementedException(); + bool MetadataUpdatesApplied() => throw new NotImplementedException(); +} + +public readonly struct Debugger : IDebugger +{ + // Everything throws NotImplementedException +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs index 5ee10f84d0d517..c5ab2f8e9d30b0 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs @@ -5,6 +5,7 @@ namespace Microsoft.Diagnostics.DataContractReader; public static class CorDbgHResults { + public const int CORDBG_E_NOTREADY = unchecked((int)0x80131c10); public const int CORDBG_E_READVIRTUAL_FAILURE = unchecked((int)0x80131c49); public const int ERROR_BUFFER_OVERFLOW = unchecked((int)0x8007006F); // HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW) } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index 45d2f13eb87934..8078c24f3eee6a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -42,6 +42,7 @@ public enum DataType Module, ModuleLookupMap, AppDomain, + Debugger, SystemDomain, Assembly, LoaderAllocator, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index 945bb0d07cc6ac..c2cc5f234141a1 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -13,6 +13,9 @@ public static class Globals public const string ThreadStore = nameof(ThreadStore); public const string FinalizerThread = nameof(FinalizerThread); public const string GCThread = nameof(GCThread); + public const string Debugger = nameof(Debugger); + public const string CLRJitAttachState = nameof(CLRJitAttachState); + public const string MetadataUpdatesApplied = nameof(MetadataUpdatesApplied); public const string FeatureCOMInterop = nameof(FeatureCOMInterop); public const string FeatureComWrappers = nameof(FeatureComWrappers); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebuggerFactory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebuggerFactory.cs new file mode 100644 index 00000000000000..58bd60ac94c5c7 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebuggerFactory.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +public sealed class DebuggerFactory : IContractFactory +{ + IDebugger IContractFactory.CreateContract(Target target, int version) + { + return version switch + { + 1 => new Debugger_1(target), + _ => default(Debugger), + }; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Debugger_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Debugger_1.cs new file mode 100644 index 00000000000000..fab0ca1c91557b --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Debugger_1.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal readonly struct Debugger_1 : IDebugger +{ + private readonly Target _target; + + internal Debugger_1(Target target) + { + _target = target; + } + + bool IDebugger.TryGetDebuggerData(out DebuggerData data) + { + data = default; + TargetPointer debuggerPtrPtr = _target.ReadGlobalPointer(Constants.Globals.Debugger); + if (debuggerPtrPtr == TargetPointer.Null) + return false; + + TargetPointer debuggerPtr = _target.ReadPointer(debuggerPtrPtr); + if (debuggerPtr == TargetPointer.Null) + return false; + + Data.Debugger debugger = _target.ProcessedData.GetOrAdd(debuggerPtr); + if (debugger.LeftSideInitialized == 0) + return false; + + data = new DebuggerData(debugger.Defines, debugger.MDStructuresVersion); + return true; + } + + int IDebugger.GetAttachStateFlags() + { + TargetPointer addr = _target.ReadGlobalPointer(Constants.Globals.CLRJitAttachState); + return (int)_target.Read(addr.Value); + } + + bool IDebugger.MetadataUpdatesApplied() + { + if (_target.TryReadGlobalPointer(Constants.Globals.MetadataUpdatesApplied, out TargetPointer? addr)) + { + return _target.Read(addr.Value.Value) != 0; + } + return false; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Debugger.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Debugger.cs new file mode 100644 index 00000000000000..3165d480333a8e --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Debugger.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class Debugger : IData +{ + static Debugger IData.Create(Target target, TargetPointer address) + => new Debugger(target, address); + + public Debugger(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.Debugger); + + LeftSideInitialized = target.Read(address + (ulong)type.Fields[nameof(LeftSideInitialized)].Offset); + Defines = target.Read(address + (ulong)type.Fields[nameof(Defines)].Offset); + MDStructuresVersion = target.Read(address + (ulong)type.Fields[nameof(MDStructuresVersion)].Offset); + } + + public int LeftSideInitialized { get; init; } + public uint Defines { get; init; } + public uint MDStructuresVersion { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs new file mode 100644 index 00000000000000..ce9e28ca54aa36 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -0,0 +1,1274 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.Legacy; + +[GeneratedComClass] +public sealed unsafe partial class DacDbiImpl : IDacDbiInterface +{ + private readonly Target _target; + private readonly IDacDbiInterface? _legacy; + + // IStringHolder is a native C++ abstract class (not COM) with a single virtual method: + // virtual HRESULT AssignCopy(const WCHAR* psz) = 0; + // The nint we receive is a pointer to the object, whose first field is the vtable pointer. + // The vtable has a single entry: a function pointer for AssignCopy. + // Use Thiscall because this is a C++ virtual method (thiscall on x86, no-op on x64/arm64). + private delegate* unmanaged[Thiscall] GetAssignCopyFnPtr(nint stringHolder) + { + // stringHolder -> vtable ptr -> first slot is AssignCopy + nint vtable = *(nint*)stringHolder; + return (delegate* unmanaged[Thiscall])(*(nint*)vtable); + } + + private int StringHolderAssignCopy(nint stringHolder, string str) + { + fixed (char* pStr = str) + { + return GetAssignCopyFnPtr(stringHolder)(stringHolder, pStr); + } + } + + public DacDbiImpl(Target target, object? legacyObj) + { + _target = target; + _legacy = legacyObj as IDacDbiInterface; + } + + public int CheckDbiVersion(DbiVersion* pVersion) + => _legacy is not null ? _legacy.CheckDbiVersion(pVersion) : HResults.E_NOTIMPL; + + public int FlushCache() + { + _target.Flush(); + return _legacy is not null ? _legacy.FlushCache() : HResults.S_OK; + } + + public int DacSetTargetConsistencyChecks(Interop.BOOL fEnableAsserts) + => _legacy is not null ? _legacy.DacSetTargetConsistencyChecks(fEnableAsserts) : HResults.E_NOTIMPL; + + public int IsLeftSideInitialized(Interop.BOOL* pResult) + { + *pResult = Interop.BOOL.FALSE; + int hr = HResults.S_OK; + try + { + *pResult = _target.Contracts.Debugger.TryGetDebuggerData(out _) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacy is not null) + { + Interop.BOOL resultLocal; + int hrLocal = _legacy.IsLeftSideInitialized(&resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == resultLocal); + } +#endif + + return hr; + } + + public int GetAppDomainFromId(uint appdomainId, ulong* pRetVal) + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + if (appdomainId != _target.ReadGlobal(Constants.Globals.DefaultADID)) + throw new ArgumentException(null, nameof(appdomainId)); + TargetPointer appDomainPtr = _target.ReadGlobalPointer(Constants.Globals.AppDomain); + *pRetVal = _target.ReadPointer(appDomainPtr); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.GetAppDomainFromId(appdomainId, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal:x}, DAC: {retValLocal:x}"); + } +#endif + return hr; + } + + public int GetAppDomainId(ulong vmAppDomain, uint* pRetVal) + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + *pRetVal = vmAppDomain == 0 ? 0u : _target.ReadGlobal(Constants.Globals.DefaultADID); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + uint retValLocal; + int hrLocal = _legacy.GetAppDomainId(vmAppDomain, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}"); + } +#endif + return hr; + } + + public int GetAppDomainObject(ulong vmAppDomain, ulong* pRetVal) + => _legacy is not null ? _legacy.GetAppDomainObject(vmAppDomain, pRetVal) : HResults.E_NOTIMPL; + + public int GetAssemblyFromDomainAssembly(ulong vmDomainAssembly, ulong* vmAssembly) + => _legacy is not null ? _legacy.GetAssemblyFromDomainAssembly(vmDomainAssembly, vmAssembly) : HResults.E_NOTIMPL; + + public int IsAssemblyFullyTrusted(ulong vmDomainAssembly, Interop.BOOL* pResult) + { + *pResult = Interop.BOOL.TRUE; + int hr = HResults.S_OK; +#if DEBUG + if (_legacy is not null) + { + Interop.BOOL resultLocal; + int hrLocal = _legacy.IsAssemblyFullyTrusted(vmDomainAssembly, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); + } +#endif + return hr; + } + + public int GetAppDomainFullName(ulong vmAppDomain, nint pStrName) + { + int hr = HResults.S_OK; + try + { + string name = _target.Contracts.Loader.GetAppDomainFriendlyName(); + hr = StringHolderAssignCopy(pStrName, name); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + int hrLocal = _legacy.GetAppDomainFullName(vmAppDomain, pStrName); + Debug.ValidateHResult(hr, hrLocal); + } +#endif + return hr; + } + + public int GetModuleSimpleName(ulong vmModule, nint pStrFilename) + => _legacy is not null ? _legacy.GetModuleSimpleName(vmModule, pStrFilename) : HResults.E_NOTIMPL; + + public int GetAssemblyPath(ulong vmAssembly, nint pStrFilename, Interop.BOOL* pResult) + { + *pResult = Interop.BOOL.FALSE; + int hr = HResults.S_OK; + try + { + Contracts.ILoader loader = _target.Contracts.Loader; + Contracts.ModuleHandle handle = loader.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmAssembly)); + string path = loader.GetPath(handle); + if (string.IsNullOrEmpty(path)) + { + *pResult = Interop.BOOL.FALSE; + } + else + { + hr = StringHolderAssignCopy(pStrFilename, path); + *pResult = Interop.BOOL.TRUE; + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + Interop.BOOL resultLocal; + int hrLocal = _legacy.GetAssemblyPath(vmAssembly, pStrFilename, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); + } +#endif + return hr; + } + + public int ResolveTypeReference(DacDbiTypeRefData* pTypeRefInfo, DacDbiTypeRefData* pTargetRefInfo) + => _legacy is not null ? _legacy.ResolveTypeReference(pTypeRefInfo, pTargetRefInfo) : HResults.E_NOTIMPL; + + public int GetModulePath(ulong vmModule, nint pStrFilename, Interop.BOOL* pResult) + { + *pResult = Interop.BOOL.FALSE; + int hr = HResults.S_OK; + try + { + Contracts.ILoader loader = _target.Contracts.Loader; + Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(new TargetPointer(vmModule)); + string path = loader.GetPath(handle); + if (string.IsNullOrEmpty(path)) + { + *pResult = Interop.BOOL.FALSE; + } + else + { + hr = StringHolderAssignCopy(pStrFilename, path); + *pResult = Interop.BOOL.TRUE; + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + Interop.BOOL resultLocal; + int hrLocal = _legacy.GetModulePath(vmModule, pStrFilename, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); + } +#endif + return hr; + } + + public int GetMetadata(ulong vmModule, DacDbiTargetBuffer* pTargetBuffer) + => _legacy is not null ? _legacy.GetMetadata(vmModule, pTargetBuffer) : HResults.E_NOTIMPL; + + public int GetSymbolsBuffer(ulong vmModule, DacDbiTargetBuffer* pTargetBuffer, int* pSymbolFormat) + => _legacy is not null ? _legacy.GetSymbolsBuffer(vmModule, pTargetBuffer, pSymbolFormat) : HResults.E_NOTIMPL; + + public int GetModuleData(ulong vmModule, DacDbiModuleInfo* pData) + => _legacy is not null ? _legacy.GetModuleData(vmModule, pData) : HResults.E_NOTIMPL; + + public int GetDomainAssemblyData(ulong vmDomainAssembly, DacDbiDomainAssemblyInfo* pData) + => _legacy is not null ? _legacy.GetDomainAssemblyData(vmDomainAssembly, pData) : HResults.E_NOTIMPL; + + public int GetModuleForDomainAssembly(ulong vmDomainAssembly, ulong* pModule) + => _legacy is not null ? _legacy.GetModuleForDomainAssembly(vmDomainAssembly, pModule) : HResults.E_NOTIMPL; + + public int GetAddressType(ulong address, int* pRetVal) + => _legacy is not null ? _legacy.GetAddressType(address, pRetVal) : HResults.E_NOTIMPL; + + public int IsTransitionStub(ulong address, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.IsTransitionStub(address, pResult) : HResults.E_NOTIMPL; + + public int GetCompilerFlags(ulong vmDomainAssembly, Interop.BOOL* pfAllowJITOpts, Interop.BOOL* pfEnableEnC) + => _legacy is not null ? _legacy.GetCompilerFlags(vmDomainAssembly, pfAllowJITOpts, pfEnableEnC) : HResults.E_NOTIMPL; + + public int SetCompilerFlags(ulong vmDomainAssembly, Interop.BOOL fAllowJitOpts, Interop.BOOL fEnableEnC) + => _legacy is not null ? _legacy.SetCompilerFlags(vmDomainAssembly, fAllowJitOpts, fEnableEnC) : HResults.E_NOTIMPL; + + public int EnumerateAppDomains(nint fpCallback, nint pUserData) + { + int hr = HResults.S_OK; + try + { + TargetPointer appDomainPtr = _target.ReadGlobalPointer(Constants.Globals.AppDomain); + ulong appDomain = _target.ReadPointer(appDomainPtr); + var callback = (delegate* unmanaged)fpCallback; + callback(appDomain, pUserData); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + return hr; + } + + public int EnumerateAssembliesInAppDomain(ulong vmAppDomain, nint fpCallback, nint pUserData) + => _legacy is not null ? _legacy.EnumerateAssembliesInAppDomain(vmAppDomain, fpCallback, pUserData) : HResults.E_NOTIMPL; + + public int EnumerateModulesInAssembly(ulong vmAssembly, nint fpCallback, nint pUserData) + => _legacy is not null ? _legacy.EnumerateModulesInAssembly(vmAssembly, fpCallback, pUserData) : HResults.E_NOTIMPL; + + public int RequestSyncAtEvent() + => _legacy is not null ? _legacy.RequestSyncAtEvent() : HResults.E_NOTIMPL; + + public int SetSendExceptionsOutsideOfJMC(Interop.BOOL sendExceptionsOutsideOfJMC) + => _legacy is not null ? _legacy.SetSendExceptionsOutsideOfJMC(sendExceptionsOutsideOfJMC) : HResults.E_NOTIMPL; + + public int MarkDebuggerAttachPending() + => _legacy is not null ? _legacy.MarkDebuggerAttachPending() : HResults.E_NOTIMPL; + + public int MarkDebuggerAttached(Interop.BOOL fAttached) + => _legacy is not null ? _legacy.MarkDebuggerAttached(fAttached) : HResults.E_NOTIMPL; + + public int Hijack(ulong vmThread, uint dwThreadId, nint pRecord, nint pOriginalContext, uint cbSizeContext, int reason, nint pUserData, ulong* pRemoteContextAddr) + => _legacy is not null ? _legacy.Hijack(vmThread, dwThreadId, pRecord, pOriginalContext, cbSizeContext, reason, pUserData, pRemoteContextAddr) : HResults.E_NOTIMPL; + + public int EnumerateThreads(nint fpCallback, nint pUserData) + { + int hr = HResults.S_OK; +#if DEBUG + List? cdacThreads = _legacy is not null ? new() : null; +#endif + try + { + Contracts.IThread threadContract = _target.Contracts.Thread; + Contracts.ThreadStoreData threadStore = threadContract.GetThreadStoreData(); + var callback = (delegate* unmanaged)fpCallback; + TargetPointer currentThread = threadStore.FirstThread; + while (currentThread != TargetPointer.Null) + { + Contracts.ThreadData threadData = threadContract.GetThreadData(currentThread); + // Match native: skip dead and unstarted threads + if ((threadData.State & (Contracts.ThreadState.Dead | Contracts.ThreadState.Unstarted)) == 0) + { + callback(currentThread.Value, pUserData); +#if DEBUG + cdacThreads?.Add(currentThread.Value); +#endif + } + currentThread = threadData.NextThread; + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + List dacThreads = new(); + GCHandle dacHandle = GCHandle.Alloc(dacThreads); + int hrLocal = _legacy.EnumerateThreads( + (nint)(delegate* unmanaged)&CollectThreadCallback, + GCHandle.ToIntPtr(dacHandle)); + dacHandle.Free(); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert( + cdacThreads!.SequenceEqual(dacThreads), + $"Thread enumeration mismatch - cDAC: [{string.Join(",", cdacThreads!.Select(t => $"0x{t:x}"))}], DAC: [{string.Join(",", dacThreads.Select(t => $"0x{t:x}"))}]"); + } + } +#endif + return hr; + } + +#if DEBUG + [UnmanagedCallersOnly] + private static void CollectThreadCallback(ulong value, nint pUserData) + { + GCHandle handle = GCHandle.FromIntPtr(pUserData); + ((List)handle.Target!).Add(value); + } +#endif + + public int IsThreadMarkedDead(ulong vmThread, Interop.BOOL* pResult) + { + *pResult = Interop.BOOL.FALSE; + int hr = HResults.S_OK; + try + { + Contracts.ThreadData threadData = _target.Contracts.Thread.GetThreadData(new TargetPointer(vmThread)); + *pResult = (threadData.State & Contracts.ThreadState.Dead) != 0 ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + Interop.BOOL resultLocal; + int hrLocal = _legacy.IsThreadMarkedDead(vmThread, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); + } +#endif + return hr; + } + + public int GetThreadHandle(ulong vmThread, nint pRetVal) + => _legacy is not null ? _legacy.GetThreadHandle(vmThread, pRetVal) : HResults.E_NOTIMPL; + + public int GetThreadObject(ulong vmThread, ulong* pRetVal) + => _legacy is not null ? _legacy.GetThreadObject(vmThread, pRetVal) : HResults.E_NOTIMPL; + + public int GetThreadAllocInfo(ulong vmThread, DacDbiThreadAllocInfo* pThreadAllocInfo) + => _legacy is not null ? _legacy.GetThreadAllocInfo(vmThread, pThreadAllocInfo) : HResults.E_NOTIMPL; + + public int SetDebugState(ulong vmThread, int debugState) + => _legacy is not null ? _legacy.SetDebugState(vmThread, debugState) : HResults.E_NOTIMPL; + + public int HasUnhandledException(ulong vmThread, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.HasUnhandledException(vmThread, pResult) : HResults.E_NOTIMPL; + + public int GetUserState(ulong vmThread, int* pRetVal) + => _legacy is not null ? _legacy.GetUserState(vmThread, pRetVal) : HResults.E_NOTIMPL; + + public int GetPartialUserState(ulong vmThread, int* pRetVal) + => _legacy is not null ? _legacy.GetPartialUserState(vmThread, pRetVal) : HResults.E_NOTIMPL; + + public int GetConnectionID(ulong vmThread, uint* pRetVal) + { + *pRetVal = 0; + int hr = HResults.S_OK; +#if DEBUG + if (_legacy is not null) + { + uint retValLocal; + int hrLocal = _legacy.GetConnectionID(vmThread, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}"); + } +#endif + return hr; + } + + public int GetTaskID(ulong vmThread, ulong* pRetVal) + { + *pRetVal = 0; + int hr = HResults.S_OK; +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.GetTaskID(vmThread, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}"); + } +#endif + return hr; + } + + public int TryGetVolatileOSThreadID(ulong vmThread, uint* pRetVal) + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + Contracts.ThreadData threadData = _target.Contracts.Thread.GetThreadData(new TargetPointer(vmThread)); + uint osId = (uint)threadData.OSId.Value; + // Match native: SWITCHED_OUT_FIBER_OSID (0xbaadf00d) means thread is switched out + const uint SWITCHED_OUT_FIBER_OSID = 0xbaadf00d; + *pRetVal = osId == SWITCHED_OUT_FIBER_OSID ? 0 : osId; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + uint retValLocal; + int hrLocal = _legacy.TryGetVolatileOSThreadID(vmThread, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}"); + } +#endif + return hr; + } + + public int GetUniqueThreadID(ulong vmThread, uint* pRetVal) + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + Contracts.ThreadData threadData = _target.Contracts.Thread.GetThreadData(new TargetPointer(vmThread)); + *pRetVal = (uint)threadData.OSId.Value; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + uint retValLocal; + int hrLocal = _legacy.GetUniqueThreadID(vmThread, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}"); + } +#endif + return hr; + } + + public int GetCurrentException(ulong vmThread, ulong* pRetVal) + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + TargetPointer throwable = _target.Contracts.Thread.GetThrowableObject(new TargetPointer(vmThread)); + *pRetVal = throwable.Value; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.GetCurrentException(vmThread, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal:x}, DAC: {retValLocal:x}"); + } +#endif + return hr; + } + + public int GetObjectForCCW(ulong ccwPtr, ulong* pRetVal) + => _legacy is not null ? _legacy.GetObjectForCCW(ccwPtr, pRetVal) : HResults.E_NOTIMPL; + + public int GetCurrentCustomDebuggerNotification(ulong vmThread, ulong* pRetVal) + => _legacy is not null ? _legacy.GetCurrentCustomDebuggerNotification(vmThread, pRetVal) : HResults.E_NOTIMPL; + + public int GetCurrentAppDomain(ulong* pRetVal) + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + TargetPointer appDomainPtr = _target.ReadGlobalPointer(Constants.Globals.AppDomain); + *pRetVal = _target.ReadPointer(appDomainPtr); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.GetCurrentAppDomain(&retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal:x}, DAC: {retValLocal:x}"); + } +#endif + return hr; + } + + public int ResolveAssembly(ulong vmScope, uint tkAssemblyRef, ulong* pRetVal) + => _legacy is not null ? _legacy.ResolveAssembly(vmScope, tkAssemblyRef, pRetVal) : HResults.E_NOTIMPL; + + public int GetNativeCodeSequencePointsAndVarInfo(ulong vmMethodDesc, ulong startAddress, Interop.BOOL fCodeAvailable, nint pNativeVarData, nint pSequencePoints) + => _legacy is not null ? _legacy.GetNativeCodeSequencePointsAndVarInfo(vmMethodDesc, startAddress, fCodeAvailable, pNativeVarData, pSequencePoints) : HResults.E_NOTIMPL; + + public int GetManagedStoppedContext(ulong vmThread, ulong* pRetVal) + => _legacy is not null ? _legacy.GetManagedStoppedContext(vmThread, pRetVal) : HResults.E_NOTIMPL; + + public int CreateStackWalk(ulong vmThread, nint pInternalContextBuffer, nuint* ppSFIHandle) + => _legacy is not null ? _legacy.CreateStackWalk(vmThread, pInternalContextBuffer, ppSFIHandle) : HResults.E_NOTIMPL; + + public int DeleteStackWalk(nuint ppSFIHandle) + => _legacy is not null ? _legacy.DeleteStackWalk(ppSFIHandle) : HResults.E_NOTIMPL; + + public int GetStackWalkCurrentContext(nuint pSFIHandle, nint pContext) + => _legacy is not null ? _legacy.GetStackWalkCurrentContext(pSFIHandle, pContext) : HResults.E_NOTIMPL; + + public int SetStackWalkCurrentContext(ulong vmThread, nuint pSFIHandle, int flag, nint pContext) + => _legacy is not null ? _legacy.SetStackWalkCurrentContext(vmThread, pSFIHandle, flag, pContext) : HResults.E_NOTIMPL; + + public int UnwindStackWalkFrame(nuint pSFIHandle, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.UnwindStackWalkFrame(pSFIHandle, pResult) : HResults.E_NOTIMPL; + + public int CheckContext(ulong vmThread, nint pContext) + => _legacy is not null ? _legacy.CheckContext(vmThread, pContext) : HResults.E_NOTIMPL; + + public int GetStackWalkCurrentFrameInfo(nuint pSFIHandle, nint pFrameData, int* pRetVal) + => _legacy is not null ? _legacy.GetStackWalkCurrentFrameInfo(pSFIHandle, pFrameData, pRetVal) : HResults.E_NOTIMPL; + + public int GetCountOfInternalFrames(ulong vmThread, uint* pRetVal) + => _legacy is not null ? _legacy.GetCountOfInternalFrames(vmThread, pRetVal) : HResults.E_NOTIMPL; + + public int EnumerateInternalFrames(ulong vmThread, nint fpCallback, nint pUserData) + => _legacy is not null ? _legacy.EnumerateInternalFrames(vmThread, fpCallback, pUserData) : HResults.E_NOTIMPL; + + public int IsMatchingParentFrame(ulong fpToCheck, ulong fpParent, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.IsMatchingParentFrame(fpToCheck, fpParent, pResult) : HResults.E_NOTIMPL; + + public int GetStackParameterSize(ulong controlPC, uint* pRetVal) + => _legacy is not null ? _legacy.GetStackParameterSize(controlPC, pRetVal) : HResults.E_NOTIMPL; + + public int GetFramePointer(nuint pSFIHandle, ulong* pRetVal) + => _legacy is not null ? _legacy.GetFramePointer(pSFIHandle, pRetVal) : HResults.E_NOTIMPL; + + public int IsLeafFrame(ulong vmThread, nint pContext, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.IsLeafFrame(vmThread, pContext, pResult) : HResults.E_NOTIMPL; + + public int GetContext(ulong vmThread, nint pContextBuffer) + => _legacy is not null ? _legacy.GetContext(vmThread, pContextBuffer) : HResults.E_NOTIMPL; + + public int ConvertContextToDebuggerRegDisplay(nint pInContext, nint pOutDRD, Interop.BOOL fActive) + => _legacy is not null ? _legacy.ConvertContextToDebuggerRegDisplay(pInContext, pOutDRD, fActive) : HResults.E_NOTIMPL; + + public int IsDiagnosticsHiddenOrLCGMethod(ulong vmMethodDesc, int* pRetVal) + => _legacy is not null ? _legacy.IsDiagnosticsHiddenOrLCGMethod(vmMethodDesc, pRetVal) : HResults.E_NOTIMPL; + + public int GetVarArgSig(ulong VASigCookieAddr, ulong* pArgBase, DacDbiTargetBuffer* pRetVal) + => _legacy is not null ? _legacy.GetVarArgSig(VASigCookieAddr, pArgBase, pRetVal) : HResults.E_NOTIMPL; + + public int RequiresAlign8(ulong thExact, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.RequiresAlign8(thExact, pResult) : HResults.E_NOTIMPL; + + public int ResolveExactGenericArgsToken(uint dwExactGenericArgsTokenIndex, ulong rawToken, ulong* pRetVal) + => _legacy is not null ? _legacy.ResolveExactGenericArgsToken(dwExactGenericArgsTokenIndex, rawToken, pRetVal) : HResults.E_NOTIMPL; + + public int GetILCodeAndSig(ulong vmDomainAssembly, uint functionToken, DacDbiTargetBuffer* pTargetBuffer, uint* pLocalSigToken) + => _legacy is not null ? _legacy.GetILCodeAndSig(vmDomainAssembly, functionToken, pTargetBuffer, pLocalSigToken) : HResults.E_NOTIMPL; + + public int GetNativeCodeInfo(ulong vmDomainAssembly, uint functionToken, nint pJitManagerList) + => _legacy is not null ? _legacy.GetNativeCodeInfo(vmDomainAssembly, functionToken, pJitManagerList) : HResults.E_NOTIMPL; + + public int GetNativeCodeInfoForAddr(ulong codeAddress, nint pCodeInfo, ulong* pVmModule, uint* pFunctionToken) + => _legacy is not null ? _legacy.GetNativeCodeInfoForAddr(codeAddress, pCodeInfo, pVmModule, pFunctionToken) : HResults.E_NOTIMPL; + + public int IsValueType(ulong vmTypeHandle, Interop.BOOL* pResult) + { + *pResult = Interop.BOOL.FALSE; + int hr = HResults.S_OK; + try + { + Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + Contracts.TypeHandle th = rts.GetTypeHandle(new TargetPointer(vmTypeHandle)); + CorElementType corType = rts.GetSignatureCorElementType(th); + *pResult = corType == CorElementType.ValueType ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + Interop.BOOL resultLocal; + int hrLocal = _legacy.IsValueType(vmTypeHandle, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); + } +#endif + return hr; + } + + public int HasTypeParams(ulong vmTypeHandle, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.HasTypeParams(vmTypeHandle, pResult) : HResults.E_NOTIMPL; + + public int GetClassInfo(ulong vmAppDomain, ulong thExact, nint pData) + => _legacy is not null ? _legacy.GetClassInfo(vmAppDomain, thExact, pData) : HResults.E_NOTIMPL; + + public int GetInstantiationFieldInfo(ulong vmDomainAssembly, ulong vmTypeHandle, ulong vmExactMethodTable, nint pFieldList, nuint* pObjectSize) + => _legacy is not null ? _legacy.GetInstantiationFieldInfo(vmDomainAssembly, vmTypeHandle, vmExactMethodTable, pFieldList, pObjectSize) : HResults.E_NOTIMPL; + + public int TypeHandleToExpandedTypeInfo(int boxed, ulong vmAppDomain, ulong vmTypeHandle, nint pData) + => _legacy is not null ? _legacy.TypeHandleToExpandedTypeInfo(boxed, vmAppDomain, vmTypeHandle, pData) : HResults.E_NOTIMPL; + + public int GetObjectExpandedTypeInfo(int boxed, ulong vmAppDomain, ulong addr, nint pTypeInfo) + => _legacy is not null ? _legacy.GetObjectExpandedTypeInfo(boxed, vmAppDomain, addr, pTypeInfo) : HResults.E_NOTIMPL; + + public int GetObjectExpandedTypeInfoFromID(int boxed, ulong vmAppDomain, COR_TYPEID id, nint pTypeInfo) + => _legacy is not null ? _legacy.GetObjectExpandedTypeInfoFromID(boxed, vmAppDomain, id, pTypeInfo) : HResults.E_NOTIMPL; + + public int GetTypeHandle(ulong vmModule, uint metadataToken, ulong* pRetVal) + => _legacy is not null ? _legacy.GetTypeHandle(vmModule, metadataToken, pRetVal) : HResults.E_NOTIMPL; + + public int GetApproxTypeHandle(nint pTypeData, ulong* pRetVal) + => _legacy is not null ? _legacy.GetApproxTypeHandle(pTypeData, pRetVal) : HResults.E_NOTIMPL; + + public int GetExactTypeHandle(nint pTypeData, nint pArgInfo, ulong* pVmTypeHandle) + => _legacy is not null ? _legacy.GetExactTypeHandle(pTypeData, pArgInfo, pVmTypeHandle) : HResults.E_NOTIMPL; + + public int GetMethodDescParams(ulong vmAppDomain, ulong vmMethodDesc, ulong genericsToken, uint* pcGenericClassTypeParams, nint pGenericTypeParams) + => _legacy is not null ? _legacy.GetMethodDescParams(vmAppDomain, vmMethodDesc, genericsToken, pcGenericClassTypeParams, pGenericTypeParams) : HResults.E_NOTIMPL; + + public int GetThreadStaticAddress(ulong vmField, ulong vmRuntimeThread, ulong* pRetVal) + => _legacy is not null ? _legacy.GetThreadStaticAddress(vmField, vmRuntimeThread, pRetVal) : HResults.E_NOTIMPL; + + public int GetCollectibleTypeStaticAddress(ulong vmField, ulong vmAppDomain, ulong* pRetVal) + => _legacy is not null ? _legacy.GetCollectibleTypeStaticAddress(vmField, vmAppDomain, pRetVal) : HResults.E_NOTIMPL; + + public int GetEnCHangingFieldInfo(nint pEnCFieldInfo, nint pFieldData, Interop.BOOL* pfStatic) + => _legacy is not null ? _legacy.GetEnCHangingFieldInfo(pEnCFieldInfo, pFieldData, pfStatic) : HResults.E_NOTIMPL; + + public int GetTypeHandleParams(ulong vmAppDomain, ulong vmTypeHandle, nint pParams) + => _legacy is not null ? _legacy.GetTypeHandleParams(vmAppDomain, vmTypeHandle, pParams) : HResults.E_NOTIMPL; + + public int GetSimpleType(ulong vmAppDomain, int simpleType, uint* pMetadataToken, ulong* pVmModule, ulong* pVmDomainAssembly) + => _legacy is not null ? _legacy.GetSimpleType(vmAppDomain, simpleType, pMetadataToken, pVmModule, pVmDomainAssembly) : HResults.E_NOTIMPL; + + public int IsExceptionObject(ulong vmObject, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.IsExceptionObject(vmObject, pResult) : HResults.E_NOTIMPL; + + public int GetStackFramesFromException(ulong vmObject, nint pDacStackFrames) + => _legacy is not null ? _legacy.GetStackFramesFromException(vmObject, pDacStackFrames) : HResults.E_NOTIMPL; + + public int IsRcw(ulong vmObject, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.IsRcw(vmObject, pResult) : HResults.E_NOTIMPL; + + public int GetRcwCachedInterfaceTypes(ulong vmObject, ulong vmAppDomain, Interop.BOOL bIInspectableOnly, nint pDacInterfaces) + => _legacy is not null ? _legacy.GetRcwCachedInterfaceTypes(vmObject, vmAppDomain, bIInspectableOnly, pDacInterfaces) : HResults.E_NOTIMPL; + + public int GetRcwCachedInterfacePointers(ulong vmObject, Interop.BOOL bIInspectableOnly, nint pDacItfPtrs) + => _legacy is not null ? _legacy.GetRcwCachedInterfacePointers(vmObject, bIInspectableOnly, pDacItfPtrs) : HResults.E_NOTIMPL; + + public int GetCachedWinRTTypesForIIDs(ulong vmAppDomain, nint pIids, nint pTypes) + => _legacy is not null ? _legacy.GetCachedWinRTTypesForIIDs(vmAppDomain, pIids, pTypes) : HResults.E_NOTIMPL; + + public int GetCachedWinRTTypes(ulong vmAppDomain, nint piids, nint pTypes) + => _legacy is not null ? _legacy.GetCachedWinRTTypes(vmAppDomain, piids, pTypes) : HResults.E_NOTIMPL; + + public int GetTypedByRefInfo(ulong pTypedByRef, ulong vmAppDomain, nint pObjectData) + => _legacy is not null ? _legacy.GetTypedByRefInfo(pTypedByRef, vmAppDomain, pObjectData) : HResults.E_NOTIMPL; + + public int GetStringData(ulong objectAddress, nint pObjectData) + => _legacy is not null ? _legacy.GetStringData(objectAddress, pObjectData) : HResults.E_NOTIMPL; + + public int GetArrayData(ulong objectAddress, nint pObjectData) + => _legacy is not null ? _legacy.GetArrayData(objectAddress, pObjectData) : HResults.E_NOTIMPL; + + public int GetBasicObjectInfo(ulong objectAddress, int type, ulong vmAppDomain, nint pObjectData) + => _legacy is not null ? _legacy.GetBasicObjectInfo(objectAddress, type, vmAppDomain, pObjectData) : HResults.E_NOTIMPL; + + public int TestCrst(ulong vmCrst) + => _legacy is not null ? _legacy.TestCrst(vmCrst) : HResults.E_NOTIMPL; + + public int TestRWLock(ulong vmRWLock) + => _legacy is not null ? _legacy.TestRWLock(vmRWLock) : HResults.E_NOTIMPL; + + public int GetDebuggerControlBlockAddress(ulong* pRetVal) + => _legacy is not null ? _legacy.GetDebuggerControlBlockAddress(pRetVal) : HResults.E_NOTIMPL; + + public int GetObjectFromRefPtr(ulong ptr, ulong* pRetVal) + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + *pRetVal = _target.ReadPointer(new TargetPointer(ptr)).Value; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.GetObjectFromRefPtr(ptr, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal:x}, DAC: {retValLocal:x}"); + } +#endif + return hr; + } + + public int GetObject(ulong ptr, ulong* pRetVal) + { + // Native GetObject wraps the address directly in a VMPTR_Object without dereferencing. + *pRetVal = ptr; + int hr = HResults.S_OK; +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.GetObject(ptr, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal:x}, DAC: {retValLocal:x}"); + } +#endif + return hr; + } + + public int EnableNGENPolicy(int ePolicy) + => HResults.E_NOTIMPL; + + public int SetNGENCompilerFlags(uint dwFlags) + => _legacy is not null ? _legacy.SetNGENCompilerFlags(dwFlags) : HResults.E_NOTIMPL; + + public int GetNGENCompilerFlags(uint* pdwFlags) + => _legacy is not null ? _legacy.GetNGENCompilerFlags(pdwFlags) : HResults.E_NOTIMPL; + + public int GetVmObjectHandle(ulong handleAddress, ulong* pRetVal) + { + *pRetVal = handleAddress; + int hr = HResults.S_OK; +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.GetVmObjectHandle(handleAddress, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal:x}, DAC: {retValLocal:x}"); + } +#endif + return hr; + } + + public int IsVmObjectHandleValid(ulong vmHandle, Interop.BOOL* pResult) + { + *pResult = Interop.BOOL.FALSE; + int hr = HResults.S_OK; + try + { + TargetPointer obj = _target.ReadPointer(new TargetPointer(vmHandle)); + *pResult = obj != TargetPointer.Null ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + Interop.BOOL resultLocal; + int hrLocal = _legacy.IsVmObjectHandleValid(vmHandle, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); + } +#endif + return hr; + } + + public int IsWinRTModule(ulong vmModule, Interop.BOOL* isWinRT) + { + *isWinRT = Interop.BOOL.FALSE; + int hr = HResults.S_OK; +#if DEBUG + if (_legacy is not null) + { + Interop.BOOL isWinRTLocal; + int hrLocal = _legacy.IsWinRTModule(vmModule, &isWinRTLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*isWinRT == isWinRTLocal, $"cDAC: {*isWinRT}, DAC: {isWinRTLocal}"); + } +#endif + return hr; + } + + public int GetAppDomainIdFromVmObjectHandle(ulong vmHandle, uint* pRetVal) + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + // In modern .NET there is only one AppDomain (id=1). + // Return 0 for null handles to match native behavior. + *pRetVal = vmHandle != 0 ? 1u : 0u; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + uint retValLocal; + int hrLocal = _legacy.GetAppDomainIdFromVmObjectHandle(vmHandle, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}"); + } +#endif + return hr; + } + + public int GetHandleAddressFromVmHandle(ulong vmHandle, ulong* pRetVal) + { + *pRetVal = vmHandle; + int hr = HResults.S_OK; +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.GetHandleAddressFromVmHandle(vmHandle, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal:x}, DAC: {retValLocal:x}"); + } +#endif + return hr; + } + + public int GetObjectContents(ulong obj, DacDbiTargetBuffer* pRetVal) + => _legacy is not null ? _legacy.GetObjectContents(obj, pRetVal) : HResults.E_NOTIMPL; + + public int GetThreadOwningMonitorLock(ulong vmObject, DacDbiMonitorLockInfo* pRetVal) + => _legacy is not null ? _legacy.GetThreadOwningMonitorLock(vmObject, pRetVal) : HResults.E_NOTIMPL; + + public int EnumerateMonitorEventWaitList(ulong vmObject, nint fpCallback, nint pUserData) + => _legacy is not null ? _legacy.EnumerateMonitorEventWaitList(vmObject, fpCallback, pUserData) : HResults.E_NOTIMPL; + + public int GetAttachStateFlags(int* pRetVal) + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + *pRetVal = _target.Contracts.Debugger.GetAttachStateFlags(); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacy is not null) + { + int resultLocal; + int hrLocal = _legacy.GetAttachStateFlags(&resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == resultLocal); + } +#endif + + return hr; + } + + public int GetMetaDataFileInfoFromPEFile(ulong vmPEAssembly, uint* dwTimeStamp, uint* dwImageSize, nint pStrFilename, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.GetMetaDataFileInfoFromPEFile(vmPEAssembly, dwTimeStamp, dwImageSize, pStrFilename, pResult) : HResults.E_NOTIMPL; + + public int IsThreadSuspendedOrHijacked(ulong vmThread, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.IsThreadSuspendedOrHijacked(vmThread, pResult) : HResults.E_NOTIMPL; + + public int AreGCStructuresValid(Interop.BOOL* pResult) + { + // Native DacDbiInterfaceImpl::AreGCStructuresValid always returns TRUE. + // DacDbi callers assume the runtime is suspended, so GC structures are always valid. + *pResult = Interop.BOOL.TRUE; + int hr = HResults.S_OK; +#if DEBUG + if (_legacy is not null) + { + Interop.BOOL resultLocal; + int hrLocal = _legacy.AreGCStructuresValid(&resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); + } +#endif + return hr; + } + + public int CreateHeapWalk(nuint* pHandle) + => _legacy is not null ? _legacy.CreateHeapWalk(pHandle) : HResults.E_NOTIMPL; + + public int DeleteHeapWalk(nuint handle) + => _legacy is not null ? _legacy.DeleteHeapWalk(handle) : HResults.E_NOTIMPL; + + public int WalkHeap(nuint handle, uint count, COR_HEAPOBJECT* objects, uint* fetched) + => _legacy is not null ? _legacy.WalkHeap(handle, count, objects, fetched) : HResults.E_NOTIMPL; + + public int GetHeapSegments(nint pSegments) + => _legacy is not null ? _legacy.GetHeapSegments(pSegments) : HResults.E_NOTIMPL; + + public int IsValidObject(ulong obj, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.IsValidObject(obj, pResult) : HResults.E_NOTIMPL; + + public int GetAppDomainForObject(ulong obj, ulong* pApp, ulong* pModule, ulong* pDomainAssembly, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.GetAppDomainForObject(obj, pApp, pModule, pDomainAssembly, pResult) : HResults.E_NOTIMPL; + + public int CreateRefWalk(nuint* pHandle, Interop.BOOL walkStacks, Interop.BOOL walkFQ, uint handleWalkMask) + => _legacy is not null ? _legacy.CreateRefWalk(pHandle, walkStacks, walkFQ, handleWalkMask) : HResults.E_NOTIMPL; + + public int DeleteRefWalk(nuint handle) + => _legacy is not null ? _legacy.DeleteRefWalk(handle) : HResults.E_NOTIMPL; + + public int WalkRefs(nuint handle, uint count, nint refs, uint* pFetched) + => _legacy is not null ? _legacy.WalkRefs(handle, count, refs, pFetched) : HResults.E_NOTIMPL; + + public int GetTypeID(ulong obj, COR_TYPEID* pType) + { + *pType = default; + int hr = HResults.S_OK; + try + { + TargetPointer mt = _target.Contracts.Object.GetMethodTableAddress(new TargetPointer(obj)); + pType->token1 = mt.Value; + pType->token2 = 0; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacy is not null) + { + COR_TYPEID resultLocal; + int hrLocal = _legacy.GetTypeID(obj, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert(pType->token1 == resultLocal.token1); + Debug.Assert(pType->token2 == resultLocal.token2); + } + } +#endif + + return hr; + } + + public int GetTypeIDForType(ulong vmTypeHandle, COR_TYPEID* pId) + { + *pId = default; + int hr = HResults.S_OK; + try + { + pId->token1 = vmTypeHandle; + pId->token2 = 0; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacy is not null) + { + COR_TYPEID resultLocal; + int hrLocal = _legacy.GetTypeIDForType(vmTypeHandle, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert(pId->token1 == resultLocal.token1); + Debug.Assert(pId->token2 == resultLocal.token2); + } + } +#endif + + return hr; + } + + public int GetObjectFields(nint id, uint celt, COR_FIELD* layout, uint* pceltFetched) + => _legacy is not null ? _legacy.GetObjectFields(id, celt, layout, pceltFetched) : HResults.E_NOTIMPL; + + public int GetTypeLayout(nint id, COR_TYPE_LAYOUT* pLayout) + => _legacy is not null ? _legacy.GetTypeLayout(id, pLayout) : HResults.E_NOTIMPL; + + public int GetArrayLayout(nint id, nint pLayout) + => _legacy is not null ? _legacy.GetArrayLayout(id, pLayout) : HResults.E_NOTIMPL; + + public int GetGCHeapInformation(COR_HEAPINFO* pHeapInfo) + { + *pHeapInfo = default; + int hr = HResults.S_OK; + try + { + Contracts.IGC gc = _target.Contracts.GC; + pHeapInfo->areGCStructuresValid = gc.GetGCStructuresValid() ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; + pHeapInfo->numHeaps = gc.GetGCHeapCount(); + pHeapInfo->pointerSize = (uint)_target.PointerSize; + + string[] identifiers = gc.GetGCIdentifiers(); + bool isServer = identifiers.Contains(GCIdentifiers.Server); + pHeapInfo->gcType = isServer ? 1 : 0; // CorDebugServerGC = 1, CorDebugWorkstationGC = 0 + pHeapInfo->concurrent = identifiers.Contains(GCIdentifiers.Background) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacy is not null) + { + COR_HEAPINFO resultLocal; + int hrLocal = _legacy.GetGCHeapInformation(&resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert(pHeapInfo->areGCStructuresValid == resultLocal.areGCStructuresValid); + Debug.Assert(pHeapInfo->numHeaps == resultLocal.numHeaps); + Debug.Assert(pHeapInfo->pointerSize == resultLocal.pointerSize); + Debug.Assert(pHeapInfo->gcType == resultLocal.gcType); + Debug.Assert(pHeapInfo->concurrent == resultLocal.concurrent); + } + } +#endif + + return hr; + } + + public int GetPEFileMDInternalRW(ulong vmPEAssembly, ulong* pAddrMDInternalRW) + => _legacy is not null ? _legacy.GetPEFileMDInternalRW(vmPEAssembly, pAddrMDInternalRW) : HResults.E_NOTIMPL; + + public int GetReJitInfo(ulong vmModule, uint methodTk, ulong* pReJitInfo) + => _legacy is not null ? _legacy.GetReJitInfo(vmModule, methodTk, pReJitInfo) : HResults.E_NOTIMPL; + + public int GetReJitInfoByAddress(ulong vmMethod, ulong codeStartAddress, ulong* pReJitInfo) + => _legacy is not null ? _legacy.GetReJitInfoByAddress(vmMethod, codeStartAddress, pReJitInfo) : HResults.E_NOTIMPL; + + public int GetSharedReJitInfo(ulong vmReJitInfo, ulong* pSharedReJitInfo) + => _legacy is not null ? _legacy.GetSharedReJitInfo(vmReJitInfo, pSharedReJitInfo) : HResults.E_NOTIMPL; + + public int GetSharedReJitInfoData(ulong sharedReJitInfo, DacDbiSharedReJitInfo* pData) + => _legacy is not null ? _legacy.GetSharedReJitInfoData(sharedReJitInfo, pData) : HResults.E_NOTIMPL; + + public int AreOptimizationsDisabled(ulong vmModule, uint methodTk, Interop.BOOL* pOptimizationsDisabled) + => _legacy is not null ? _legacy.AreOptimizationsDisabled(vmModule, methodTk, pOptimizationsDisabled) : HResults.E_NOTIMPL; + + public int GetDefinesBitField(uint* pDefines) + { + *pDefines = 0; + int hr = HResults.S_OK; + try + { + if (!_target.Contracts.Debugger.TryGetDebuggerData(out Contracts.DebuggerData data)) + throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_NOTREADY)!; + *pDefines = data.DefinesBitField; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacy is not null) + { + uint resultLocal; + int hrLocal = _legacy.GetDefinesBitField(&resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pDefines == resultLocal); + } +#endif + + return hr; + } + + public int GetMDStructuresVersion(uint* pMDStructuresVersion) + { + *pMDStructuresVersion = 0; + int hr = HResults.S_OK; + try + { + if (!_target.Contracts.Debugger.TryGetDebuggerData(out Contracts.DebuggerData data)) + throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_NOTREADY)!; + *pMDStructuresVersion = data.MDStructuresVersion; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacy is not null) + { + uint resultLocal; + int hrLocal = _legacy.GetMDStructuresVersion(&resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pMDStructuresVersion == resultLocal); + } +#endif + + return hr; + } + + public int GetActiveRejitILCodeVersionNode(ulong vmModule, uint methodTk, ulong* pVmILCodeVersionNode) + => _legacy is not null ? _legacy.GetActiveRejitILCodeVersionNode(vmModule, methodTk, pVmILCodeVersionNode) : HResults.E_NOTIMPL; + + public int GetNativeCodeVersionNode(ulong vmMethod, ulong codeStartAddress, ulong* pVmNativeCodeVersionNode) + => _legacy is not null ? _legacy.GetNativeCodeVersionNode(vmMethod, codeStartAddress, pVmNativeCodeVersionNode) : HResults.E_NOTIMPL; + + public int GetILCodeVersionNode(ulong vmNativeCodeVersionNode, ulong* pVmILCodeVersionNode) + => _legacy is not null ? _legacy.GetILCodeVersionNode(vmNativeCodeVersionNode, pVmILCodeVersionNode) : HResults.E_NOTIMPL; + + public int GetILCodeVersionNodeData(ulong ilCodeVersionNode, DacDbiSharedReJitInfo* pData) + => _legacy is not null ? _legacy.GetILCodeVersionNodeData(ilCodeVersionNode, pData) : HResults.E_NOTIMPL; + + public int EnableGCNotificationEvents(Interop.BOOL fEnable) + => _legacy is not null ? _legacy.EnableGCNotificationEvents(fEnable) : HResults.E_NOTIMPL; + + public int IsDelegate(ulong vmObject, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.IsDelegate(vmObject, pResult) : HResults.E_NOTIMPL; + + public int GetDelegateType(ulong delegateObject, int* delegateType) + => _legacy is not null ? _legacy.GetDelegateType(delegateObject, delegateType) : HResults.E_NOTIMPL; + + public int GetDelegateFunctionData(int delegateType, ulong delegateObject, ulong* ppFunctionDomainAssembly, uint* pMethodDef) + => _legacy is not null ? _legacy.GetDelegateFunctionData(delegateType, delegateObject, ppFunctionDomainAssembly, pMethodDef) : HResults.E_NOTIMPL; + + public int GetDelegateTargetObject(int delegateType, ulong delegateObject, ulong* ppTargetObj, ulong* ppTargetAppDomain) + => _legacy is not null ? _legacy.GetDelegateTargetObject(delegateType, delegateObject, ppTargetObj, ppTargetAppDomain) : HResults.E_NOTIMPL; + + public int GetLoaderHeapMemoryRanges(nint pRanges) + => _legacy is not null ? _legacy.GetLoaderHeapMemoryRanges(pRanges) : HResults.E_NOTIMPL; + + public int IsModuleMapped(ulong pModule, int* isModuleMapped) + => _legacy is not null ? _legacy.IsModuleMapped(pModule, isModuleMapped) : HResults.E_NOTIMPL; + + public int MetadataUpdatesApplied(Interop.BOOL* pResult) + { + *pResult = Interop.BOOL.FALSE; + int hr = HResults.S_OK; + try + { + *pResult = _target.Contracts.Debugger.MetadataUpdatesApplied() ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacy is not null) + { + Interop.BOOL resultLocal; + int hrLocal = _legacy.MetadataUpdatesApplied(&resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == resultLocal); + } +#endif + + return hr; + } + + public int GetDomainAssemblyFromModule(ulong vmModule, ulong* pVmDomainAssembly) + => _legacy is not null ? _legacy.GetDomainAssemblyFromModule(vmModule, pVmDomainAssembly) : HResults.E_NOTIMPL; + + public int ParseContinuation(ulong continuationAddress, ulong* pDiagnosticIP, ulong* pNextContinuation, uint* pState) + => _legacy is not null ? _legacy.ParseContinuation(continuationAddress, pDiagnosticIP, pNextContinuation, pState) : HResults.E_NOTIMPL; + + public int GetAsyncLocals(ulong vmMethod, ulong codeAddr, uint state, nint pAsyncLocals) + => _legacy is not null ? _legacy.GetAsyncLocals(vmMethod, codeAddr, state, pAsyncLocals) : HResults.E_NOTIMPL; + + public int GetGenericArgTokenIndex(ulong vmMethod, uint* pIndex) + => _legacy is not null ? _legacy.GetGenericArgTokenIndex(vmMethod, pIndex) : HResults.E_NOTIMPL; +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index 3a92a27eea6d89..c8bc640f12d84d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -51,6 +51,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG [typeof(IBuiltInCOM)] = new BuiltInCOMFactory(), [typeof(IConditionalWeakTable)] = new ConditionalWeakTableFactory(), [typeof(IAuxiliarySymbols)] = new AuxiliarySymbolsFactory(), + [typeof(IDebugger)] = new DebuggerFactory(), }; foreach (IContractFactory factory in additionalFactories) diff --git a/src/native/managed/cdac/inc/cdac_reader.h b/src/native/managed/cdac/inc/cdac_reader.h index b68471e77b4d7a..c13ac0e44fcb74 100644 --- a/src/native/managed/cdac/inc/cdac_reader.h +++ b/src/native/managed/cdac/inc/cdac_reader.h @@ -33,6 +33,12 @@ int cdac_reader_free(intptr_t handle); // obj: returned SOS interface that can be QI'd to ISOSDacInterface* int cdac_reader_create_sos_interface(intptr_t handle, IUnknown* legacyImpl, IUnknown** obj); +// Get the DacDbi interface from the cDAC reader +// handle: handle to the reader +// legacyImpl: optional legacy implementation of the interface that will be used as a fallback +// obj: returned DacDbi interface that can be QI'd to IDacDbiInterface* +int cdac_reader_create_dacdbi_interface(intptr_t handle, IUnknown* legacyImpl, IUnknown** obj); + #ifdef __cplusplus } #endif diff --git a/src/native/managed/cdac/mscordaccore_universal/Entrypoints.cs b/src/native/managed/cdac/mscordaccore_universal/Entrypoints.cs index acc5dec4a18775..9cd2f981951850 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Entrypoints.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Entrypoints.cs @@ -86,6 +86,47 @@ private static unsafe int CreateSosInterface(IntPtr handle, IntPtr legacyImplPtr return 0; } + /// + /// Create the DacDbi interface implementation. + /// + /// Handle created via cdac initialization + /// Optional. Pointer to legacy implementation of IDacDbiInterface + /// IUnknown pointer that can be queried for IDacDbiInterface + [UnmanagedCallersOnly(EntryPoint = $"{CDAC}create_dacdbi_interface")] + private static unsafe int CreateDacDbiInterface(IntPtr handle, IntPtr legacyImplPtr, nint* obj) + { + if (obj == null) + return HResults.E_INVALIDARG; + if (handle == IntPtr.Zero) + { + *obj = IntPtr.Zero; + return HResults.E_NOTIMPL; + } + + Target? target = GCHandle.FromIntPtr(handle).Target as Target; + if (target is null) + { + *obj = IntPtr.Zero; + return HResults.E_INVALIDARG; + } + + ComWrappers cw = new StrategyBasedComWrappers(); + object? legacyObj = null; + if (legacyImplPtr != IntPtr.Zero) + { + legacyObj = cw.GetOrCreateObjectForComInstance(legacyImplPtr, CreateObjectFlags.None); + if (legacyObj is not Legacy.IDacDbiInterface) + { + *obj = IntPtr.Zero; + return HResults.COR_E_INVALIDCAST; // E_NOINTERFACE + } + } + + Legacy.DacDbiImpl impl = new(target, legacyObj); + *obj = cw.GetOrCreateComInterfaceForObject(impl, CreateComInterfaceFlags.None); + return HResults.S_OK; + } + [UnmanagedCallersOnly(EntryPoint = "CLRDataCreateInstanceWithFallback")] private static unsafe int CLRDataCreateInstanceWithFallback(Guid* pIID, IntPtr /*ICLRDataTarget*/ pLegacyTarget, IntPtr pLegacyImpl, void** iface) { diff --git a/src/native/managed/cdac/tests/DebuggerTests.cs b/src/native/managed/cdac/tests/DebuggerTests.cs new file mode 100644 index 00000000000000..3ad7c18f6e507f --- /dev/null +++ b/src/native/managed/cdac/tests/DebuggerTests.cs @@ -0,0 +1,171 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.Tests; + +public class DebuggerTests +{ + private static TargetTestHelpers.LayoutResult GetDebuggerLayout(TargetTestHelpers helpers) + { + return helpers.LayoutFields( + [ + new(nameof(Data.Debugger.LeftSideInitialized), DataType.int32), + new(nameof(Data.Debugger.Defines), DataType.uint32), + new(nameof(Data.Debugger.MDStructuresVersion), DataType.uint32), + ]); + } + + private static TestPlaceholderTarget BuildTarget( + MockTarget.Architecture arch, + int leftSideInitialized, + uint defines, + uint mdStructuresVersion, + int? attachStateFlags = null, + byte? metadataUpdatesApplied = null) + { + TargetTestHelpers helpers = new(arch); + var builder = new TestPlaceholderTarget.Builder(arch); + MockMemorySpace.Builder memBuilder = builder.MemoryBuilder; + MockMemorySpace.BumpAllocator allocator = memBuilder.CreateAllocator(0x1_0000, 0x2_0000); + + TargetTestHelpers.LayoutResult debuggerLayout = GetDebuggerLayout(helpers); + builder.AddTypes(new() { [DataType.Debugger] = new Target.TypeInfo() { Fields = debuggerLayout.Fields, Size = debuggerLayout.Stride } }); + + // Allocate and populate the Debugger struct + MockMemorySpace.HeapFragment debuggerFrag = allocator.Allocate(debuggerLayout.Stride, "Debugger"); + helpers.Write(debuggerFrag.Data.AsSpan(debuggerLayout.Fields[nameof(Data.Debugger.LeftSideInitialized)].Offset, sizeof(int)), leftSideInitialized); + helpers.Write(debuggerFrag.Data.AsSpan(debuggerLayout.Fields[nameof(Data.Debugger.Defines)].Offset, sizeof(uint)), defines); + helpers.Write(debuggerFrag.Data.AsSpan(debuggerLayout.Fields[nameof(Data.Debugger.MDStructuresVersion)].Offset, sizeof(uint)), mdStructuresVersion); + memBuilder.AddHeapFragment(debuggerFrag); + + // g_pDebugger is a pointer-to-Debugger. The global stores the address of g_pDebugger, + // so ReadGlobalPointer returns the location, and ReadPointer dereferences it. + MockMemorySpace.HeapFragment debuggerPtrFrag = allocator.Allocate((ulong)helpers.PointerSize, "g_pDebugger"); + helpers.WritePointer(debuggerPtrFrag.Data, debuggerFrag.Address); + memBuilder.AddHeapFragment(debuggerPtrFrag); + builder.AddGlobals((Constants.Globals.Debugger, debuggerPtrFrag.Address)); + + if (attachStateFlags.HasValue) + { + MockMemorySpace.HeapFragment attachFrag = allocator.Allocate(sizeof(uint), "CLRJitAttachState"); + helpers.Write(attachFrag.Data.AsSpan(0, sizeof(uint)), (uint)attachStateFlags.Value); + memBuilder.AddHeapFragment(attachFrag); + builder.AddGlobals((Constants.Globals.CLRJitAttachState, attachFrag.Address)); + } + + if (metadataUpdatesApplied.HasValue) + { + MockMemorySpace.HeapFragment metadataFrag = allocator.Allocate(1, "MetadataUpdatesApplied"); + helpers.Write(metadataFrag.Data.AsSpan(0, 1), metadataUpdatesApplied.Value); + memBuilder.AddHeapFragment(metadataFrag); + builder.AddGlobals((Constants.Globals.MetadataUpdatesApplied, metadataFrag.Address)); + } + + builder.AddContract(target => ((IContractFactory)new DebuggerFactory()).CreateContract(target, 1)); + + return builder.Build(); + } + + private static TestPlaceholderTarget BuildNullDebuggerTarget(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + var builder = new TestPlaceholderTarget.Builder(arch); + MockMemorySpace.Builder memBuilder = builder.MemoryBuilder; + MockMemorySpace.BumpAllocator allocator = memBuilder.CreateAllocator(0x1_0000, 0x2_0000); + + // g_pDebugger is a pointer-to-Debugger that contains null. + MockMemorySpace.HeapFragment debuggerPtrFrag = allocator.Allocate((ulong)helpers.PointerSize, "g_pDebugger"); + helpers.WritePointer(debuggerPtrFrag.Data, 0); + memBuilder.AddHeapFragment(debuggerPtrFrag); + builder.AddGlobals((Constants.Globals.Debugger, debuggerPtrFrag.Address)); + builder.AddContract(target => ((IContractFactory)new DebuggerFactory()).CreateContract(target, 1)); + + return builder.Build(); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TryGetDebuggerData_ReturnsTrue_WhenInitialized(MockTarget.Architecture arch) + { + Target target = BuildTarget(arch, leftSideInitialized: 1, defines: 0xDEADBEEF, mdStructuresVersion: 42); + IDebugger debugger = target.Contracts.Debugger; + + Assert.True(debugger.TryGetDebuggerData(out DebuggerData data)); + Assert.Equal(0xDEADBEEFu, data.DefinesBitField); + Assert.Equal(42u, data.MDStructuresVersion); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TryGetDebuggerData_ReturnsFalse_WhenNotInitialized(MockTarget.Architecture arch) + { + Target target = BuildTarget(arch, leftSideInitialized: 0, defines: 0, mdStructuresVersion: 0); + IDebugger debugger = target.Contracts.Debugger; + + Assert.False(debugger.TryGetDebuggerData(out _)); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TryGetDebuggerData_ReturnsFalse_WhenDebuggerNull(MockTarget.Architecture arch) + { + Target target = BuildNullDebuggerTarget(arch); + IDebugger debugger = target.Contracts.Debugger; + + Assert.False(debugger.TryGetDebuggerData(out _)); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetAttachStateFlags_ReturnsValue(MockTarget.Architecture arch) + { + Target target = BuildTarget(arch, leftSideInitialized: 1, defines: 0, mdStructuresVersion: 0, attachStateFlags: 0x42); + IDebugger debugger = target.Contracts.Debugger; + + Assert.Equal(0x42, debugger.GetAttachStateFlags()); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetAttachStateFlags_ReturnsZero_WhenValueIsZero(MockTarget.Architecture arch) + { + Target target = BuildTarget(arch, leftSideInitialized: 1, defines: 0, mdStructuresVersion: 0, attachStateFlags: 0); + IDebugger debugger = target.Contracts.Debugger; + + Assert.Equal(0, debugger.GetAttachStateFlags()); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void MetadataUpdatesApplied_ReturnsTrue_WhenSet(MockTarget.Architecture arch) + { + Target target = BuildTarget(arch, leftSideInitialized: 1, defines: 0, mdStructuresVersion: 0, metadataUpdatesApplied: 1); + IDebugger debugger = target.Contracts.Debugger; + + Assert.True(debugger.MetadataUpdatesApplied()); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void MetadataUpdatesApplied_ReturnsFalse_WhenNotSet(MockTarget.Architecture arch) + { + Target target = BuildTarget(arch, leftSideInitialized: 1, defines: 0, mdStructuresVersion: 0, metadataUpdatesApplied: 0); + IDebugger debugger = target.Contracts.Debugger; + + Assert.False(debugger.MetadataUpdatesApplied()); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void MetadataUpdatesApplied_ReturnsFalse_WhenGlobalMissing(MockTarget.Architecture arch) + { + Target target = BuildTarget(arch, leftSideInitialized: 1, defines: 0, mdStructuresVersion: 0); + IDebugger debugger = target.Contracts.Debugger; + + Assert.False(debugger.MetadataUpdatesApplied()); + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiAppDomainDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiAppDomainDumpTests.cs new file mode 100644 index 00000000000000..feb10a49b2c69b --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiAppDomainDumpTests.cs @@ -0,0 +1,217 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Legacy; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.DumpTests; + +/// +/// Dump-based integration tests for DacDbiImpl AppDomain, misc policy, and simple thread +/// property methods. Uses the BasicThreads debuggee (heap dump). +/// +public class DacDbiAppDomainDumpTests : DumpTestBase +{ + protected override string DebuggeeName => "BasicThreads"; + + private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetAppDomainId_ReturnsOneForValidAppDomain(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + TargetPointer appDomainPtr = Target.ReadGlobalPointer(Constants.Globals.AppDomain); + ulong appDomain = Target.ReadPointer(appDomainPtr); + + uint id; + int hr = dbi.GetAppDomainId(appDomain, &id); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(1u, id); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetAppDomainId_ReturnsZeroForNull(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + uint id; + int hr = dbi.GetAppDomainId(0, &id); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(0u, id); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetAppDomainFromId_ReturnsAppDomain(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + ulong appDomain; + int hr = dbi.GetAppDomainFromId(1, &appDomain); + Assert.Equal(System.HResults.S_OK, hr); + Assert.NotEqual(0UL, appDomain); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetAppDomainFromId_FailsForInvalidId(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + ulong appDomain; + int hr = dbi.GetAppDomainFromId(99, &appDomain); + Assert.True(hr < 0, "Expected failure HRESULT for invalid AppDomain ID"); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetCurrentAppDomain_ReturnsNonNull(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + ulong appDomain; + int hr = dbi.GetCurrentAppDomain(&appDomain); + Assert.Equal(System.HResults.S_OK, hr); + Assert.NotEqual(0UL, appDomain); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetCurrentAppDomain_MatchesGetAppDomainFromId(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + ulong currentAD; + int hr1 = dbi.GetCurrentAppDomain(¤tAD); + Assert.Equal(System.HResults.S_OK, hr1); + + ulong fromId; + int hr2 = dbi.GetAppDomainFromId(1, &fromId); + Assert.Equal(System.HResults.S_OK, hr2); + + Assert.Equal(currentAD, fromId); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void EnumerateAppDomains_CallsCallbackOnce(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + int count = 0; + delegate* unmanaged callback = &CountCallback; + int hr = dbi.EnumerateAppDomains((nint)callback, (nint)(&count)); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(1, count); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void IsAssemblyFullyTrusted_ReturnsTrue(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + ILoader loader = Target.Contracts.Loader; + TargetPointer appDomainPtr = Target.ReadGlobalPointer(Constants.Globals.AppDomain); + ulong appDomain = Target.ReadPointer(appDomainPtr); + IEnumerable modules = loader.GetModuleHandles(new TargetPointer(appDomain), + AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution); + + foreach (ModuleHandle module in modules) + { + TargetPointer moduleAddr = loader.GetModule(module); + Interop.BOOL result; + int hr = dbi.IsAssemblyFullyTrusted(moduleAddr, &result); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(Interop.BOOL.TRUE, result); + break; + } + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetConnectionID_ReturnsZero(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + IThread threadContract = Target.Contracts.Thread; + ThreadStoreData storeData = threadContract.GetThreadStoreData(); + + uint connId; + int hr = dbi.GetConnectionID(storeData.FirstThread, &connId); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(0u, connId); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetTaskID_ReturnsZero(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + IThread threadContract = Target.Contracts.Thread; + ThreadStoreData storeData = threadContract.GetThreadStoreData(); + + ulong taskId; + int hr = dbi.GetTaskID(storeData.FirstThread, &taskId); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(0UL, taskId); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void IsWinRTModule_ReturnsFalse(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + ILoader loader = Target.Contracts.Loader; + TargetPointer appDomainPtr = Target.ReadGlobalPointer(Constants.Globals.AppDomain); + ulong appDomain = Target.ReadPointer(appDomainPtr); + IEnumerable modules = loader.GetModuleHandles(new TargetPointer(appDomain), + AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution); + + foreach (ModuleHandle module in modules) + { + TargetPointer moduleAddr = loader.GetModule(module); + Interop.BOOL isWinRT; + int hr = dbi.IsWinRTModule(moduleAddr, &isWinRT); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(Interop.BOOL.FALSE, isWinRT); + break; + } + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void EnableNGENPolicy_ReturnsENotImpl(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + int hr = dbi.EnableNGENPolicy(0); + Assert.Equal(System.HResults.E_NOTIMPL, hr); + } + + [UnmanagedCallersOnly] + private static unsafe void CountCallback(ulong addr, nint userData) + { + (*(int*)userData)++; + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiDebuggerDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiDebuggerDumpTests.cs new file mode 100644 index 00000000000000..718f1a9273a3a8 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiDebuggerDumpTests.cs @@ -0,0 +1,126 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Legacy; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.DumpTests; + +/// +/// Dump-based integration tests for DacDbiImpl IDebugger contract methods. +/// Uses the BasicThreads debuggee (heap dump). +/// +public class DacDbiDebuggerDumpTests : DumpTestBase +{ + protected override string DebuggeeName => "BasicThreads"; + + private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void IsLeftSideInitialized_ReturnsNonZero(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + Interop.BOOL result; + int hr = dbi.IsLeftSideInitialized(&result); + Assert.Equal(System.HResults.S_OK, hr); + Assert.NotEqual(Interop.BOOL.FALSE, result); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetAttachStateFlags_Succeeds(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + int flags; + int hr = dbi.GetAttachStateFlags(&flags); + Assert.Equal(System.HResults.S_OK, hr); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetDefinesBitField_Succeeds(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + uint defines; + int hr = dbi.GetDefinesBitField(&defines); + Assert.Equal(System.HResults.S_OK, hr); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetMDStructuresVersion_Succeeds(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + uint version; + int hr = dbi.GetMDStructuresVersion(&version); + Assert.Equal(System.HResults.S_OK, hr); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void MetadataUpdatesApplied_Succeeds(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + Interop.BOOL result; + int hr = dbi.MetadataUpdatesApplied(&result); + Assert.Equal(System.HResults.S_OK, hr); + Assert.True(result == Interop.BOOL.TRUE || result == Interop.BOOL.FALSE); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void IsLeftSideInitialized_CrossValidateWithContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + Interop.BOOL dbiResult; + int hr = dbi.IsLeftSideInitialized(&dbiResult); + Assert.Equal(System.HResults.S_OK, hr); + + bool contractResult = Target.Contracts.Debugger.TryGetDebuggerData(out _); + Assert.Equal(contractResult, dbiResult != Interop.BOOL.FALSE); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetDefinesBitField_CrossValidateWithContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + uint dbiResult; + int hr = dbi.GetDefinesBitField(&dbiResult); + Assert.Equal(System.HResults.S_OK, hr); + + Assert.True(Target.Contracts.Debugger.TryGetDebuggerData(out Contracts.DebuggerData data)); + Assert.Equal(data.DefinesBitField, dbiResult); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetMDStructuresVersion_CrossValidateWithContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + uint dbiResult; + int hr = dbi.GetMDStructuresVersion(&dbiResult); + Assert.Equal(System.HResults.S_OK, hr); + + Assert.True(Target.Contracts.Debugger.TryGetDebuggerData(out Contracts.DebuggerData data)); + Assert.Equal(data.MDStructuresVersion, dbiResult); + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiGCDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiGCDumpTests.cs new file mode 100644 index 00000000000000..55de104c4c978c --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiGCDumpTests.cs @@ -0,0 +1,85 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Legacy; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.DumpTests; + +/// +/// Dump-based integration tests for DacDbiImpl GC methods. +/// Uses the BasicThreads debuggee (heap dump). +/// +public class DacDbiGCDumpTests : DumpTestBase +{ + protected override string DebuggeeName => "BasicThreads"; + + private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void AreGCStructuresValid_CrossValidateWithContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + Interop.BOOL result; + int hr = dbi.AreGCStructuresValid(&result); + Assert.Equal(System.HResults.S_OK, hr); + + bool contractResult = Target.Contracts.GC.GetGCStructuresValid(); + Assert.Equal(contractResult, result == Interop.BOOL.TRUE); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetGCHeapInformation_Succeeds(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + COR_HEAPINFO heapInfo; + int hr = dbi.GetGCHeapInformation(&heapInfo); + Assert.Equal(System.HResults.S_OK, hr); + + Assert.True(heapInfo.pointerSize == 4 || heapInfo.pointerSize == 8); + Assert.True(heapInfo.numHeaps >= 1); + Assert.True(heapInfo.areGCStructuresValid == Interop.BOOL.TRUE || heapInfo.areGCStructuresValid == Interop.BOOL.FALSE); + Assert.True(heapInfo.concurrent == Interop.BOOL.TRUE || heapInfo.concurrent == Interop.BOOL.FALSE); + Assert.True(heapInfo.gcType == 0 || heapInfo.gcType == 1); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetGCHeapInformation_MatchesPointerSize(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + COR_HEAPINFO heapInfo; + int hr = dbi.GetGCHeapInformation(&heapInfo); + Assert.Equal(System.HResults.S_OK, hr); + + Assert.Equal((uint)Target.PointerSize, heapInfo.pointerSize); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetGCHeapInformation_CrossValidateWithContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + COR_HEAPINFO heapInfo; + int hr = dbi.GetGCHeapInformation(&heapInfo); + Assert.Equal(System.HResults.S_OK, hr); + + IGC gc = Target.Contracts.GC; + bool contractValid = gc.GetGCStructuresValid(); + Assert.Equal(contractValid, heapInfo.areGCStructuresValid == Interop.BOOL.TRUE); + + uint heapCount = gc.GetGCHeapCount(); + Assert.Equal(heapCount, heapInfo.numHeaps); + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiLoaderDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiLoaderDumpTests.cs new file mode 100644 index 00000000000000..a24ac2cb9a6768 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiLoaderDumpTests.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Legacy; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.DumpTests; + +/// +/// Dump-based integration tests for DacDbiImpl loader, assembly, and module methods. +/// Uses the MultiModule debuggee (full dump), which loads assemblies from multiple ALCs. +/// +public class DacDbiLoaderDumpTests : DumpTestBase +{ + protected override string DebuggeeName => "MultiModule"; + protected override string DumpType => "full"; + + private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public void GetAppDomainFullName_ReturnsNonEmpty(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + TargetPointer appDomainPtr = Target.ReadGlobalPointer(Constants.Globals.AppDomain); + ulong appDomain = Target.ReadPointer(appDomainPtr); + + using var holder = new NativeStringHolder(); + int hr = dbi.GetAppDomainFullName(appDomain, holder.Ptr); + Assert.Equal(System.HResults.S_OK, hr); + Assert.False(string.IsNullOrEmpty(holder.Value), "AppDomain name should not be empty"); + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiObjectDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiObjectDumpTests.cs new file mode 100644 index 00000000000000..e47021a41484e2 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiObjectDumpTests.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.DataContractReader.Legacy; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.DumpTests; + +/// +/// Dump-based integration tests for DacDbiImpl object handle methods. +/// Uses the BasicThreads debuggee (heap dump). +/// +public class DacDbiObjectDumpTests : DumpTestBase +{ + protected override string DebuggeeName => "BasicThreads"; + + private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetVmObjectHandle_IsIdentity(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + ulong testAddr = 0x12345678; + ulong result; + int hr = dbi.GetVmObjectHandle(testAddr, &result); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(testAddr, result); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetHandleAddressFromVmHandle_IsIdentity(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + ulong testAddr = 0xABCDEF00; + ulong result; + int hr = dbi.GetHandleAddressFromVmHandle(testAddr, &result); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(testAddr, result); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetAppDomainIdFromVmObjectHandle_ReturnsOneForNonZero(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + uint id; + int hr = dbi.GetAppDomainIdFromVmObjectHandle(0x12345678, &id); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(1u, id); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetAppDomainIdFromVmObjectHandle_ReturnsZeroForNull(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + uint id; + int hr = dbi.GetAppDomainIdFromVmObjectHandle(0, &id); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(0u, id); + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiThreadDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiThreadDumpTests.cs new file mode 100644 index 00000000000000..11017bf43c1be6 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiThreadDumpTests.cs @@ -0,0 +1,168 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Legacy; +using Microsoft.DotNet.XUnitExtensions; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.DumpTests; + +/// +/// Dump-based integration tests for DacDbiImpl thread methods. +/// Uses the BasicThreads debuggee (heap dump), which spawns 5 named threads then crashes. +/// +public class DacDbiThreadDumpTests : DumpTestBase +{ + protected override string DebuggeeName => "BasicThreads"; + + private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void EnumerateThreads_CountMatchesContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + IThread threadContract = Target.Contracts.Thread; + ThreadStoreData storeData = threadContract.GetThreadStoreData(); + + int dbiCount = 0; + delegate* unmanaged callback = &CountThreadCallback; + int hr = dbi.EnumerateThreads((nint)callback, (nint)(&dbiCount)); + Assert.Equal(System.HResults.S_OK, hr); + + int expectedCount = 0; + TargetPointer current = storeData.FirstThread; + while (current != TargetPointer.Null) + { + ThreadData data = threadContract.GetThreadData(current); + bool isDead = (data.State & ThreadState.Dead) != 0; + bool isUnstarted = (data.State & ThreadState.Unstarted) != 0; + if (!isDead && !isUnstarted) + { + expectedCount++; + } + + current = data.NextThread; + } + + Assert.Equal(expectedCount, dbiCount); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void IsThreadMarkedDead_CrossValidateWithContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + IThread threadContract = Target.Contracts.Thread; + ThreadStoreData storeData = threadContract.GetThreadStoreData(); + + TargetPointer current = storeData.FirstThread; + while (current != TargetPointer.Null) + { + Interop.BOOL isDead; + int hr = dbi.IsThreadMarkedDead(current, &isDead); + Assert.Equal(System.HResults.S_OK, hr); + + ThreadData data = threadContract.GetThreadData(current); + bool contractSaysDead = (data.State & ThreadState.Dead) != 0; + Assert.Equal(contractSaysDead, isDead == Interop.BOOL.TRUE); + + current = data.NextThread; + } + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void TryGetVolatileOSThreadID_MatchesContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + IThread threadContract = Target.Contracts.Thread; + ThreadStoreData storeData = threadContract.GetThreadStoreData(); + + TargetPointer current = storeData.FirstThread; + while (current != TargetPointer.Null) + { + uint osId; + int hr = dbi.TryGetVolatileOSThreadID(current, &osId); + Assert.Equal(System.HResults.S_OK, hr); + + ThreadData data = threadContract.GetThreadData(current); + // DacDbi normalizes SWITCHED_OUT_FIBER_OSID (0xbaadf00d) to 0 + const uint SWITCHED_OUT_FIBER_OSID = 0xbaadf00d; + uint expectedOsId = (uint)data.OSId.Value; + if (expectedOsId == SWITCHED_OUT_FIBER_OSID) + expectedOsId = 0; + Assert.Equal(expectedOsId, osId); + + current = data.NextThread; + } + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetUniqueThreadID_MatchesContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + IThread threadContract = Target.Contracts.Thread; + ThreadStoreData storeData = threadContract.GetThreadStoreData(); + + TargetPointer current = storeData.FirstThread; + HashSet seenIds = new(); + + while (current != TargetPointer.Null) + { + uint uniqueId; + int hr = dbi.GetUniqueThreadID(current, &uniqueId); + Assert.Equal(System.HResults.S_OK, hr); + + ThreadData data = threadContract.GetThreadData(current); + Assert.Equal((uint)data.OSId.Value, uniqueId); + Assert.True(seenIds.Add(uniqueId), $"Duplicate thread ID: {uniqueId}"); + + current = data.NextThread; + } + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetCurrentException_CrossValidateWithContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + IThread threadContract = Target.Contracts.Thread; + ThreadStoreData storeData = threadContract.GetThreadStoreData(); + + TargetPointer current = storeData.FirstThread; + Assert.NotEqual(TargetPointer.Null, current); + + ulong exception; + int hr = dbi.GetCurrentException(current, &exception); + + // GetCurrentException depends on Thread.GetThrowableObject which is not yet + // implemented in the Thread contract. Skip until the contract is available. + if (hr == unchecked((int)0x80004001)) // E_NOTIMPL — GetThrowableObject not yet in Thread contract + { + throw new SkipTestException("GetThrowableObject not yet implemented in Thread contract"); + } + + Assert.Equal(System.HResults.S_OK, hr); + } + + [UnmanagedCallersOnly] + private static unsafe void CountThreadCallback(ulong addr, nint userData) + { + (*(int*)userData)++; + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/NativeStringHolder.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/NativeStringHolder.cs new file mode 100644 index 00000000000000..8bca8e70b1bb24 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/NativeStringHolder.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.Diagnostics.DataContractReader.DumpTests; + +/// +/// Creates a native-memory object that mimics the C++ IStringHolder vtable layout. +/// DacDbiImpl.StringHolderAssignCopy reads: objPtr -> vtable -> AssignCopy fn ptr. +/// This helper allocates that exact structure in unmanaged memory so we can test +/// string-returning DacDbiImpl methods directly (without COM marshaling). +/// +internal sealed class NativeStringHolder : IDisposable +{ + // Layout in native memory: + // _objectPtr -> [vtablePtr] (nint) + // _vtablePtr -> [fnPtr] (nint, the AssignCopy function pointer) + private readonly IntPtr _objectPtr; + private readonly IntPtr _vtablePtr; + private readonly GCHandle _delegateHandle; + private bool _disposed; + + // Delegate matching the native IStringHolder::AssignCopy(this, const WCHAR* psz) signature. + // Use ThisCall to match the C++ virtual method calling convention (thiscall on x86, no-op on x64/arm64). + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate int AssignCopyDelegate(IntPtr thisPtr, IntPtr psz); + + public string? Value { get; private set; } + + public NativeStringHolder() + { + AssignCopyDelegate assignCopy = AssignCopyImpl; + _delegateHandle = GCHandle.Alloc(assignCopy); + IntPtr fnPtr = Marshal.GetFunctionPointerForDelegate(assignCopy); + + // Allocate vtable (one slot: AssignCopy) + _vtablePtr = Marshal.AllocHGlobal(IntPtr.Size); + Marshal.WriteIntPtr(_vtablePtr, fnPtr); + + // Allocate object (one field: vtable pointer) + _objectPtr = Marshal.AllocHGlobal(IntPtr.Size); + Marshal.WriteIntPtr(_objectPtr, _vtablePtr); + } + + /// + /// The native pointer to pass as the nint IStringHolder parameter. + /// + public nint Ptr => _objectPtr; + + private int AssignCopyImpl(IntPtr thisPtr, IntPtr psz) + { + Value = Marshal.PtrToStringUni(psz); + return System.HResults.S_OK; + } + + public void Dispose() + { + if (!_disposed) + { + Marshal.FreeHGlobal(_objectPtr); + Marshal.FreeHGlobal(_vtablePtr); + _delegateHandle.Free(); + _disposed = true; + } + } +}