From 6f5584ef767866b9286fdc290b4502db5915dee3 Mon Sep 17 00:00:00 2001 From: Juan Hoyos <19413848+hoyosjs@users.noreply.github.com> Date: Thu, 16 Jul 2026 02:14:06 -0700 Subject: [PATCH 1/2] Add DacDbiInterfaceInstance to cdac Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Dbi/DacDbiImpl.cs | 4 +- .../Dbi/IDacDbiInterface.cs | 14 +++ .../ICLRData.cs | 8 ++ .../mscordaccore_universal/Entrypoints.cs | 87 +++++++++++++++++++ .../cdac/tests/UnitTests/DacDbiImplTests.cs | 11 +++ 5 files changed, 123 insertions(+), 1 deletion(-) 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 index 0481f2c9bd129e..7469d332124916 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -72,7 +72,9 @@ public int FlushCache() } public int DacSetTargetConsistencyChecks(Interop.BOOL fEnableAsserts) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.DacSetTargetConsistencyChecks(fEnableAsserts) : HResults.E_NOTIMPL; + => LegacyFallbackHelper.CanFallback() && _legacy is not null + ? _legacy.DacSetTargetConsistencyChecks(fEnableAsserts) + : HResults.S_OK; public int IsLeftSideInitialized(Interop.BOOL* pResult) { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs index 66cdc0ca6c121e..cc96dd9fc20abd 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs @@ -8,6 +8,20 @@ namespace Microsoft.Diagnostics.DataContractReader.Legacy; +[GeneratedComInterface] +[Guid("FE06DC28-49FB-4636-A4A3-E80DB4AE116C")] +public unsafe partial interface ICorDebugDataTarget +{ + [PreserveSig] + int GetPlatform(int* pTargetPlatform); + + [PreserveSig] + int ReadVirtual(ulong address, byte* pBuffer, uint bytesRequested, uint* pBytesRead); + + [PreserveSig] + int GetThreadContext(uint threadId, uint contextFlags, uint contextSize, byte* pContext); +} + [StructLayout(LayoutKind.Sequential)] public struct COR_TYPEID { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ICLRData.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ICLRData.cs index 838180d4323edd..2e81d2e00a9f63 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ICLRData.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ICLRData.cs @@ -81,6 +81,14 @@ public unsafe partial interface ICLRDataTarget3 : ICLRDataTarget2 int GetExceptionThreadID(uint* threadID); } +[GeneratedComInterface] +[Guid("b760bf44-9377-4597-8be7-58083bdc5146")] +public unsafe partial interface ICLRRuntimeLocator +{ + [PreserveSig] + int GetRuntimeBase(ulong* baseAddress); +} + [GeneratedComInterface] [Guid("17d5b8c6-34a9-407f-af4f-a930201d4e02")] public unsafe partial interface ICLRContractLocator diff --git a/src/native/managed/cdac/mscordaccore_universal/Entrypoints.cs b/src/native/managed/cdac/mscordaccore_universal/Entrypoints.cs index 26fd7bdec35152..7527bf5300b721 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Entrypoints.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Entrypoints.cs @@ -253,6 +253,52 @@ private static unsafe int CLRDataCreateInstanceWithFallback(Guid* pIID, IntPtr / return CLRDataCreateInstanceImpl(pIID, pLegacyTarget, pLegacyImpl, iface); } + [UnmanagedCallersOnly(EntryPoint = "DacDbiInterfaceInstance")] + private static unsafe int DacDbiInterfaceInstance( + IntPtr /*ICorDebugDataTarget*/ pTarget, + ulong runtimeBase, + IntPtr /*IDacDbiInterface::IAllocator*/ pAllocator, + IntPtr /*IDacDbiInterface::IMetaDataLookup*/ pMetaDataLookup, + void** iface) + { + if (pTarget == IntPtr.Zero + || runtimeBase == 0 + || pAllocator == IntPtr.Zero + || pMetaDataLookup == IntPtr.Zero + || iface == null) + { + return HResults.E_INVALIDARG; + } + + *iface = null; + + try + { + object dataTarget = ComInterfaceMarshaller.ConvertToManaged((void*)pTarget)!; + if (dataTarget is ICLRRuntimeLocator runtimeLocator) + { + ulong locatedRuntimeBase; + int hr = runtimeLocator.GetRuntimeBase(&locatedRuntimeBase); + if (hr < 0) + return hr; + if (locatedRuntimeBase != runtimeBase) + return HResults.E_INVALIDARG; + } + + ContractDescriptorTarget target = CreateTargetFromCorDebugDataTarget(dataTarget); + Legacy.DacDbiImpl impl = new(target, legacyObj: null); + *iface = ComInterfaceMarshaller.ConvertToUnmanaged(impl); + return HResults.S_OK; + } + catch (Exception ex) + { + if (iface != null) + *iface = null; + int hr = ex.HResult; + return hr < 0 ? hr : HResults.E_FAIL; + } + } + // Same export name and signature as DAC CLRDataCreateInstance in daccess.cpp [UnmanagedCallersOnly(EntryPoint = "CLRDataCreateInstance")] private static unsafe int CLRDataCreateInstance(Guid* pIID, IntPtr /*ICLRDataTarget*/ pLegacyTarget, void** iface) @@ -385,4 +431,45 @@ private static unsafe int CLRDataCreateInstanceCore(Guid* pIID, IntPtr /*ICLRDat return 0; } + + private static unsafe ContractDescriptorTarget CreateTargetFromCorDebugDataTarget(object targetObject) + { + ICorDebugDataTarget dataTarget = targetObject as ICorDebugDataTarget ?? throw new ArgumentException( + $"Data target does not implement {nameof(ICorDebugDataTarget)}", nameof(targetObject)); + ICLRContractLocator contractLocator = targetObject as ICLRContractLocator ?? throw new ArgumentException( + $"Data target does not implement {nameof(ICLRContractLocator)}", nameof(targetObject)); + + ulong contractAddress; + int hr = contractLocator.GetContractDescriptor(&contractAddress); + if (hr != 0) + { + throw new InvalidOperationException( + $"{nameof(ICLRContractLocator)} failed to fetch the contract descriptor with HRESULT: 0x{hr:x}."); + } + + return ContractDescriptorTarget.Create( + contractAddress, + (address, buffer) => + { + fixed (byte* bufferPtr = buffer) + { + uint bytesRead; + return dataTarget.ReadVirtual(address, bufferPtr, (uint)buffer.Length, &bytesRead); + } + }, + (address, buffer) => HResults.E_NOTIMPL, + (threadId, contextFlags, bufferToFill) => + { + fixed (byte* bufferPtr = bufferToFill) + { + return dataTarget.GetThreadContext(threadId, contextFlags, (uint)bufferToFill.Length, bufferPtr); + } + }, + (ulong size, out ulong allocatedAddress) => + { + allocatedAddress = 0; + return HResults.E_NOTIMPL; + }, + [Contracts.CoreCLRContracts.Register]); + } } diff --git a/src/native/managed/cdac/tests/UnitTests/DacDbiImplTests.cs b/src/native/managed/cdac/tests/UnitTests/DacDbiImplTests.cs index b5ebff64043f28..1f91dd724dc280 100644 --- a/src/native/managed/cdac/tests/UnitTests/DacDbiImplTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/DacDbiImplTests.cs @@ -37,6 +37,17 @@ private static (DacDbiImpl DacDbi, TestPlaceholderTarget Target) CreateDacDbiWit return (dacDbi, target); } + [Fact] + public void DacSetTargetConsistencyChecks_Standalone_ReturnsSuccess() + { + MockTarget.Architecture architecture = new() { IsLittleEndian = true, Is64Bit = true }; + TestPlaceholderTarget target = new TestPlaceholderTarget.Builder(architecture).Build(); + DacDbiImpl dacDbi = new(target, legacyObj: null); + + Assert.Equal(System.HResults.S_OK, dacDbi.DacSetTargetConsistencyChecks(Interop.BOOL.TRUE)); + Assert.Equal(System.HResults.S_OK, dacDbi.DacSetTargetConsistencyChecks(Interop.BOOL.FALSE)); + } + [Theory] [ClassData(typeof(MockTarget.StdArch))] public void SetCompilerFlags_BothFlagsSet_EncCapable(MockTarget.Architecture arch) From 557fd728171abcbfb1c744e7fdb34b97c05fd673 Mon Sep 17 00:00:00 2001 From: Juan Hoyos <19413848+hoyosjs@users.noreply.github.com> Date: Thu, 16 Jul 2026 11:00:17 -0700 Subject: [PATCH 2/2] Relax DacDbiInterfaceInstance null checks and fix ContractDescriptorTarget.TryCreate call Match the native DAC export by not requiring the allocator/metadata lookup pointers, and update CreateTargetFromCorDebugDataTarget to use the TryCreate factory (the address-based Create overload no longer exists on main). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../cdac/mscordaccore_universal/Entrypoints.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/native/managed/cdac/mscordaccore_universal/Entrypoints.cs b/src/native/managed/cdac/mscordaccore_universal/Entrypoints.cs index 7527bf5300b721..fb7983b7154987 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Entrypoints.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Entrypoints.cs @@ -261,10 +261,11 @@ private static unsafe int DacDbiInterfaceInstance( IntPtr /*IDacDbiInterface::IMetaDataLookup*/ pMetaDataLookup, void** iface) { + // Match the native DAC export (DacDbiInterfaceInstance in dacdbiimpl.cpp), which only + // validates the target, base address, and out parameter. The allocator and metadata + // lookup pointers are not used by the managed implementation, so don't require them. if (pTarget == IntPtr.Zero || runtimeBase == 0 - || pAllocator == IntPtr.Zero - || pMetaDataLookup == IntPtr.Zero || iface == null) { return HResults.E_INVALIDARG; @@ -447,7 +448,7 @@ private static unsafe ContractDescriptorTarget CreateTargetFromCorDebugDataTarge $"{nameof(ICLRContractLocator)} failed to fetch the contract descriptor with HRESULT: 0x{hr:x}."); } - return ContractDescriptorTarget.Create( + if (!ContractDescriptorTarget.TryCreate( contractAddress, (address, buffer) => { @@ -465,11 +466,19 @@ private static unsafe ContractDescriptorTarget CreateTargetFromCorDebugDataTarge return dataTarget.GetThreadContext(threadId, contextFlags, (uint)bufferToFill.Length, bufferPtr); } }, + (threadId, context) => HResults.E_NOTIMPL, (ulong size, out ulong allocatedAddress) => { allocatedAddress = 0; return HResults.E_NOTIMPL; }, - [Contracts.CoreCLRContracts.Register]); + [Contracts.CoreCLRContracts.Register], + out ContractDescriptorTarget? target)) + { + throw new InvalidOperationException( + $"Failed to create a {nameof(ContractDescriptorTarget)} from the contract descriptor at 0x{contractAddress:x}."); + } + + return target!; } }