From 3d345dfbaadf36c708fc75a8848b45c98ebee6ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Apr 2026 20:46:01 +0000 Subject: [PATCH 01/10] Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/3366f85c-89fb-4c74-a840-d9c748b9f2a3 Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/3366f85c-89fb-4c74-a840-d9c748b9f2a3 Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/3366f85c-89fb-4c74-a840-d9c748b9f2a3 Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> add interop APIs --- .../Dbi/DacDbiImpl.cs | 83 ++++++++++++++++++- .../DumpTests/DacDbi/DacDbiCCWDumpTests.cs | 74 +++++++++++++++++ .../DacDbi/DacDbiComWrappersDumpTests.cs | 60 ++++++++++++++ .../DumpTests/DacDbi/DacDbiRCWDumpTests.cs | 80 ++++++++++++++++++ 4 files changed, 295 insertions(+), 2 deletions(-) create mode 100644 src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiCCWDumpTests.cs create mode 100644 src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiComWrappersDumpTests.cs create mode 100644 src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiRCWDumpTests.cs 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 95fdef60493597..b11a6bc8610cee 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 @@ -16,6 +16,7 @@ public sealed unsafe partial class DacDbiImpl : IDacDbiInterface { private readonly Target _target; private readonly IDacDbiInterface? _legacy; + private readonly ulong _rcwMask = 1UL; // IStringHolder is a native C++ abstract class (not COM) with a single virtual method: // virtual HRESULT AssignCopy(const WCHAR* psz) = 0; @@ -546,7 +547,61 @@ public int GetCurrentException(ulong vmThread, ulong* pRetVal) } public int GetObjectForCCW(ulong ccwPtr, ulong* pRetVal) - => _legacy is not null ? _legacy.GetObjectForCCW(ccwPtr, pRetVal) : HResults.E_NOTIMPL; + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + TargetPointer objectHandle = TargetPointer.Null; + TargetPointer ccwAddress = new(ccwPtr); + + try + { + IComWrappers comWrappers = _target.Contracts.ComWrappers; + TargetPointer managedObjectWrapper = comWrappers.GetManagedObjectWrapperFromCCW(ccwAddress); + if (managedObjectWrapper != TargetPointer.Null) + { + objectHandle = _target.ReadPointer(managedObjectWrapper); + } + } + catch (NotImplementedException) + { + // Targets without ComWrappers support should still try BuiltInCOM. + } + + if (objectHandle == TargetPointer.Null) + { + IBuiltInCOM builtInCOM = _target.Contracts.BuiltInCOM; + TargetPointer ccw = builtInCOM.GetCCWFromInterfacePointer(ccwAddress); + if (ccw != TargetPointer.Null) + { + objectHandle = builtInCOM.GetObjectHandle(ccw); + } + else + { + // not an interface pointer + objectHandle = builtInCOM.GetObjectHandle(ccwAddress); + } + } + + *pRetVal = objectHandle.Value; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.GetObjectForCCW(ccwPtr, &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 GetCurrentCustomDebuggerNotification(ulong vmThread, ulong* pRetVal) => _legacy is not null ? _legacy.GetCurrentCustomDebuggerNotification(vmThread, pRetVal) : HResults.E_NOTIMPL; @@ -822,7 +877,31 @@ 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; + { + *pResult = Interop.BOOL.FALSE; + int hr = HResults.S_OK; + try + { + IObject obj = _target.Contracts.Object; + _ = obj.GetBuiltInComData(new TargetPointer(vmObject), out TargetPointer rcw, out _, out _); + *pResult = (rcw & _rcwMask) != 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.IsRcw(vmObject, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); + } +#endif + return hr; + } 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; diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiCCWDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiCCWDumpTests.cs new file mode 100644 index 00000000000000..6c765b236f467e --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiCCWDumpTests.cs @@ -0,0 +1,74 @@ +// 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.Linq; +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.GetObjectForCCW. +/// Uses the CCW debuggee (full dump). +/// +public class DacDbiCCWDumpTests : DumpTestBase +{ + protected override string DebuggeeName => "CCW"; + protected override string DumpType => "full"; + + private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); + + private (TargetPointer Ccw, TargetPointer InterfacePointer) FindBuiltInComCcwWithInterface() + { + IGC gc = Target.Contracts.GC; + IObject obj = Target.Contracts.Object; + IBuiltInCOM builtInCOM = Target.Contracts.BuiltInCOM; + + foreach (HandleData handleData in gc.GetHandles([HandleType.Strong])) + { + TargetPointer objectAddress = Target.ReadPointer(handleData.Handle); + if (objectAddress == TargetPointer.Null) + continue; + + if (!obj.GetBuiltInComData(objectAddress, out _, out TargetPointer ccw, out _) + || ccw == TargetPointer.Null) + { + continue; + } + + List interfaces = builtInCOM.GetCCWInterfaces(ccw).ToList(); + if (interfaces.Count == 0) + continue; + + TargetPointer interfacePointer = Target.ReadPointer(interfaces[0].InterfacePointerAddress); + if (interfacePointer == TargetPointer.Null) + continue; + + return (ccw, interfacePointer); + } + + throw new SkipTestException("No BuiltInCOM CCW interface pointer found in dump."); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnOS(IncludeOnly = "windows", Reason = "COM callable wrappers require Windows")] + public unsafe void GetObjectForCCW_ReturnsBuiltInComObjectHandle(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + IBuiltInCOM builtInCOM = Target.Contracts.BuiltInCOM; + + (TargetPointer ccw, TargetPointer interfacePointer) = FindBuiltInComCcwWithInterface(); + + ulong resultHandle; + int hr = dbi.GetObjectForCCW(interfacePointer.Value, &resultHandle); + + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(builtInCOM.GetObjectHandle(ccw).Value, resultHandle); + Assert.NotEqual(TargetPointer.Null, Target.ReadPointer(new TargetPointer(resultHandle))); + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiComWrappersDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiComWrappersDumpTests.cs new file mode 100644 index 00000000000000..f7d3a980aa6553 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiComWrappersDumpTests.cs @@ -0,0 +1,60 @@ +// 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 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.GetObjectForCCW via the ComWrappers path. +/// Uses the ComWrappers debuggee (full dump). +/// +public class DacDbiComWrappersDumpTests : DumpTestBase +{ + protected override string DebuggeeName => "ComWrappers"; + protected override string DumpType => "full"; + + private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "ComWrappers cDAC support not available in .NET 10")] + [SkipOnOS(IncludeOnly = "windows", Reason = "COM/ComWrappers CCW scenarios are validated on Windows")] + public unsafe void GetObjectForCCW_ComWrappersIdentityPointer_ReturnsManagedObjectWrapperHandle(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + IGC gc = Target.Contracts.GC; + IComWrappers comWrappers = Target.Contracts.ComWrappers; + + foreach (HandleData handleData in gc.GetHandles([HandleType.Strong])) + { + TargetPointer objectAddress = Target.ReadPointer(handleData.Handle); + if (objectAddress == TargetPointer.Null) + continue; + + List mows = comWrappers.GetMOWs(objectAddress, out bool hasMowTable); + if (!hasMowTable || mows.Count == 0) + continue; + + TargetPointer mow = mows[0]; + TargetPointer identity = comWrappers.GetIdentityForMOW(mow); + if (identity == TargetPointer.Null) + continue; + + ulong expectedHandle = Target.ReadPointer(mow).Value; + ulong actualHandle; + int hr = dbi.GetObjectForCCW(identity.Value, &actualHandle); + + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(expectedHandle, actualHandle); + return; + } + + throw new SkipTestException("No ComWrappers MOW/CCW identity found in dump."); + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiRCWDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiRCWDumpTests.cs new file mode 100644 index 00000000000000..47f6ed89748625 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiRCWDumpTests.cs @@ -0,0 +1,80 @@ +// 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 Microsoft.DotNet.XUnitExtensions; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.DumpTests; + +/// +/// Dump-based integration tests for DacDbiImpl.IsRcw. +/// Uses the RCW debuggee (full dump). +/// +public class DacDbiRCWDumpTests : DumpTestBase +{ + protected override string DebuggeeName => "RCW"; + protected override string DumpType => "full"; + + private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnOS(IncludeOnly = "windows", Reason = "COM interop (RCW) is only supported on Windows")] + public unsafe void IsRcw_ReturnsTrueForBuiltInRcwObject(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + IGC gc = Target.Contracts.GC; + IObject obj = Target.Contracts.Object; + + foreach (HandleData handleData in gc.GetHandles([HandleType.Strong])) + { + TargetPointer objectAddress = Target.ReadPointer(handleData.Handle); + if (objectAddress == TargetPointer.Null) + continue; + + if (!obj.GetBuiltInComData(objectAddress, out TargetPointer rcw, out _, out _) || rcw == TargetPointer.Null) + continue; + + Interop.BOOL result; + int hr = dbi.IsRcw(objectAddress.Value, &result); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(Interop.BOOL.TRUE, result); + return; + } + + throw new SkipTestException("No built-in RCW object found in dump."); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnOS(IncludeOnly = "windows", Reason = "COM interop (RCW) is only supported on Windows")] + public unsafe void IsRcw_ReturnsFalseForNonRcwObject(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + IGC gc = Target.Contracts.GC; + IObject obj = Target.Contracts.Object; + + foreach (HandleData handleData in gc.GetHandles([HandleType.Strong])) + { + TargetPointer objectAddress = Target.ReadPointer(handleData.Handle); + if (objectAddress == TargetPointer.Null) + continue; + + _ = obj.GetBuiltInComData(objectAddress, out TargetPointer rcw, out _, out _); + if (rcw != TargetPointer.Null) + continue; + + Interop.BOOL result; + int hr = dbi.IsRcw(objectAddress.Value, &result); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(Interop.BOOL.FALSE, result); + return; + } + + throw new SkipTestException("No non-RCW object found in dump."); + } +} From 80e52d17a79250d0cee7f526ca26a96d8ecb370b Mon Sep 17 00:00:00 2001 From: Rachel Date: Wed, 15 Apr 2026 12:16:52 -0700 Subject: [PATCH 02/10] Update DacDbiComWrappersDumpTests.cs --- .../cdac/tests/DumpTests/DacDbi/DacDbiComWrappersDumpTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiComWrappersDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiComWrappersDumpTests.cs index f7d3a980aa6553..ab282ce322fc09 100644 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiComWrappersDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiComWrappersDumpTests.cs @@ -23,7 +23,6 @@ public class DacDbiComWrappersDumpTests : DumpTestBase [ConditionalTheory] [MemberData(nameof(TestConfigurations))] [SkipOnVersion("net10.0", "ComWrappers cDAC support not available in .NET 10")] - [SkipOnOS(IncludeOnly = "windows", Reason = "COM/ComWrappers CCW scenarios are validated on Windows")] public unsafe void GetObjectForCCW_ComWrappersIdentityPointer_ReturnsManagedObjectWrapperHandle(TestConfiguration config) { InitializeDumpTest(config); From 7258f1addb5e4fa62616e2e6b597dd9a86899625 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Wed, 15 Apr 2026 12:51:57 -0700 Subject: [PATCH 03/10] copilot comments --- .../Dbi/DacDbiImpl.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) 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 b11a6bc8610cee..801d6d796ef032 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 @@ -16,7 +16,6 @@ public sealed unsafe partial class DacDbiImpl : IDacDbiInterface { private readonly Target _target; private readonly IDacDbiInterface? _legacy; - private readonly ulong _rcwMask = 1UL; // IStringHolder is a native C++ abstract class (not COM) with a single virtual method: // virtual HRESULT AssignCopy(const WCHAR* psz) = 0; @@ -575,13 +574,10 @@ public int GetObjectForCCW(ulong ccwPtr, ulong* pRetVal) TargetPointer ccw = builtInCOM.GetCCWFromInterfacePointer(ccwAddress); if (ccw != TargetPointer.Null) { - objectHandle = builtInCOM.GetObjectHandle(ccw); - } - else - { - // not an interface pointer - objectHandle = builtInCOM.GetObjectHandle(ccwAddress); + ccw = ccwAddress; } + ccw = builtInCOM.GetStartWrapper(ccw); + objectHandle = builtInCOM.GetObjectFromCCW(ccw); } *pRetVal = objectHandle.Value; @@ -884,7 +880,7 @@ public int IsRcw(ulong vmObject, Interop.BOOL* pResult) { IObject obj = _target.Contracts.Object; _ = obj.GetBuiltInComData(new TargetPointer(vmObject), out TargetPointer rcw, out _, out _); - *pResult = (rcw & _rcwMask) != TargetPointer.Null ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; + *pResult = rcw != TargetPointer.Null ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; } catch (System.Exception ex) { From c82b05e7568c97119caeabc528706b7ba18f3ffb Mon Sep 17 00:00:00 2001 From: Rachel Date: Wed, 15 Apr 2026 17:42:10 -0700 Subject: [PATCH 04/10] Rename method GetObjectFromCCW to GetObjectHandle --- .../Dbi/DacDbiImpl.cs | 2 +- 1 file changed, 1 insertion(+), 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 801d6d796ef032..3cffa06e8fbd84 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 @@ -577,7 +577,7 @@ public int GetObjectForCCW(ulong ccwPtr, ulong* pRetVal) ccw = ccwAddress; } ccw = builtInCOM.GetStartWrapper(ccw); - objectHandle = builtInCOM.GetObjectFromCCW(ccw); + objectHandle = builtInCOM.GetObjectHandle(ccw); } *pRetVal = objectHandle.Value; From acb652fe146f6e2729c64dc7029fec56328d7706 Mon Sep 17 00:00:00 2001 From: Rachel Date: Wed, 15 Apr 2026 22:17:02 -0700 Subject: [PATCH 05/10] Update DacDbiImpl.cs --- .../Dbi/DacDbiImpl.cs | 2 +- 1 file changed, 1 insertion(+), 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 3cffa06e8fbd84..d728f0892bf005 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 @@ -572,7 +572,7 @@ public int GetObjectForCCW(ulong ccwPtr, ulong* pRetVal) { IBuiltInCOM builtInCOM = _target.Contracts.BuiltInCOM; TargetPointer ccw = builtInCOM.GetCCWFromInterfacePointer(ccwAddress); - if (ccw != TargetPointer.Null) + if (ccw == TargetPointer.Null) { ccw = ccwAddress; } From f66279f4ff7fdfdb769932741d1b6a1a0143a631 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Apr 2026 16:47:26 +0000 Subject: [PATCH 06/10] Fix two unresolved dump test review comments Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/65246430-549d-4bcc-b123-778dbb73db29 Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- .../cdac/tests/DumpTests/DacDbi/DacDbiCCWDumpTests.cs | 8 ++++++-- .../tests/DumpTests/DacDbi/DacDbiComWrappersDumpTests.cs | 5 +++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiCCWDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiCCWDumpTests.cs index 6c765b236f467e..a855ebe056b3a1 100644 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiCCWDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiCCWDumpTests.cs @@ -39,7 +39,11 @@ public class DacDbiCCWDumpTests : DumpTestBase continue; } - List interfaces = builtInCOM.GetCCWInterfaces(ccw).ToList(); + // Normalize to the start wrapper, matching what DacDbiImpl.GetObjectForCCW does + // before calling GetObjectHandle, so the expected handle in the test is consistent. + TargetPointer startCcw = builtInCOM.GetStartWrapper(ccw); + + List interfaces = builtInCOM.GetCCWInterfaces(startCcw).ToList(); if (interfaces.Count == 0) continue; @@ -47,7 +51,7 @@ public class DacDbiCCWDumpTests : DumpTestBase if (interfacePointer == TargetPointer.Null) continue; - return (ccw, interfacePointer); + return (startCcw, interfacePointer); } throw new SkipTestException("No BuiltInCOM CCW interface pointer found in dump."); diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiComWrappersDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiComWrappersDumpTests.cs index ab282ce322fc09..0bcb2521ee5f92 100644 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiComWrappersDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiComWrappersDumpTests.cs @@ -51,6 +51,11 @@ public unsafe void GetObjectForCCW_ComWrappersIdentityPointer_ReturnsManagedObje Assert.Equal(System.HResults.S_OK, hr); Assert.Equal(expectedHandle, actualHandle); + + // Verify the returned handle dereferences to a live object in the dump. + TargetPointer actualHandleTarget = new(actualHandle); + TargetPointer actualObjectAddress = Target.ReadPointer(actualHandleTarget); + Assert.NotEqual(TargetPointer.Null, actualObjectAddress); return; } From 54275e73ecd642e3172d21015384a682b1bb2598 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Fri, 17 Apr 2026 10:00:57 -0700 Subject: [PATCH 07/10] use trygetcontract --- .../Dbi/DacDbiImpl.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) 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 eaf9bf0a5b191c..756f137d6fa5a5 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 @@ -488,22 +488,18 @@ public int GetObjectForCCW(ulong ccwPtr, ulong* pRetVal) { TargetPointer objectHandle = TargetPointer.Null; TargetPointer ccwAddress = new(ccwPtr); + bool comWrappersSuccess = false; - try + if (_target.Contracts.TryGetContract(out IComWrappers? comWrappers)) { - IComWrappers comWrappers = _target.Contracts.ComWrappers; TargetPointer managedObjectWrapper = comWrappers.GetManagedObjectWrapperFromCCW(ccwAddress); if (managedObjectWrapper != TargetPointer.Null) { - objectHandle = _target.ReadPointer(managedObjectWrapper); + comWrappersSuccess = _target.TryReadPointer(managedObjectWrapper, out objectHandle); } } - catch (NotImplementedException) - { - // Targets without ComWrappers support should still try BuiltInCOM. - } - if (objectHandle == TargetPointer.Null) + if (!comWrappersSuccess || objectHandle == TargetPointer.Null) { IBuiltInCOM builtInCOM = _target.Contracts.BuiltInCOM; TargetPointer ccw = builtInCOM.GetCCWFromInterfacePointer(ccwAddress); From 2478f6147dd6690d7cf8ffd51b90ebc2f4f42780 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Thu, 23 Apr 2026 10:28:30 -0700 Subject: [PATCH 08/10] fix test --- .../cdac/tests/DumpTests/DacDbi/DacDbiCCWDumpTests.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiCCWDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiCCWDumpTests.cs index a855ebe056b3a1..a08cfe17300e1c 100644 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiCCWDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiCCWDumpTests.cs @@ -47,11 +47,7 @@ public class DacDbiCCWDumpTests : DumpTestBase if (interfaces.Count == 0) continue; - TargetPointer interfacePointer = Target.ReadPointer(interfaces[0].InterfacePointerAddress); - if (interfacePointer == TargetPointer.Null) - continue; - - return (startCcw, interfacePointer); + return (startCcw, interfaces[0].InterfacePointerAddress); } throw new SkipTestException("No BuiltInCOM CCW interface pointer found in dump."); From 0f3960533d21e8bd720810929b28ea02f38b33d4 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Thu, 23 Apr 2026 13:31:12 -0700 Subject: [PATCH 09/10] code review --- .../Contracts/ComWrappers_1.cs | 5 +++-- .../Dbi/DacDbiImpl.cs | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs index 6d12808352319a..b3fcc3c5b255f8 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs @@ -56,8 +56,9 @@ private bool IsComWrappersCCW(TargetPointer ccw) if (!GetComWrappersCCWVTableQIAddress(ccw, out _, out TargetPointer qiAddress)) return false; - TargetPointer comWrappersVtablePtrs = _target.ReadGlobalPointer(Constants.Globals.ComWrappersVtablePtrs); - Data.ComWrappersVtablePtrs comWrappersVtableStruct = _target.ProcessedData.GetOrAdd(comWrappersVtablePtrs); + if (!_target.TryReadPointer(Constants.Globals.ComWrappersVtablePtrs, out TargetPointer? comWrappersVtablePtrs)) + return false; + Data.ComWrappersVtablePtrs comWrappersVtableStruct = _target.ProcessedData.GetOrAdd(comWrappersVtablePtrs.Value); return comWrappersVtableStruct.ComWrappersInterfacePointers.Contains(CodePointerUtils.CodePointerFromAddress(qiAddress, _target)); } 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 fcc846c2a52c56..3103c306e4a3d2 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 @@ -670,9 +670,8 @@ public int GetObjectForCCW(ulong ccwPtr, ulong* pRetVal) } } - if (!comWrappersSuccess || objectHandle == TargetPointer.Null) + if (!comWrappersSuccess && _target.Contracts.TryGetContract(out IBuiltInCOM? builtInCOM)) { - IBuiltInCOM builtInCOM = _target.Contracts.BuiltInCOM; TargetPointer ccw = builtInCOM.GetCCWFromInterfacePointer(ccwAddress); if (ccw == TargetPointer.Null) { From e27006e732764ed6d6c71abbe673b7e09dbf4111 Mon Sep 17 00:00:00 2001 From: Rachel Jarvi Date: Thu, 23 Apr 2026 14:58:13 -0700 Subject: [PATCH 10/10] Update pointer reading method in ComWrappers_1.cs --- .../Contracts/ComWrappers_1.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs index b3fcc3c5b255f8..e7cdfc0400c0f7 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs @@ -56,7 +56,7 @@ private bool IsComWrappersCCW(TargetPointer ccw) if (!GetComWrappersCCWVTableQIAddress(ccw, out _, out TargetPointer qiAddress)) return false; - if (!_target.TryReadPointer(Constants.Globals.ComWrappersVtablePtrs, out TargetPointer? comWrappersVtablePtrs)) + if (!_target.TryReadGlobalPointer(Constants.Globals.ComWrappersVtablePtrs, out TargetPointer? comWrappersVtablePtrs)) return false; Data.ComWrappersVtablePtrs comWrappersVtableStruct = _target.ProcessedData.GetOrAdd(comWrappersVtablePtrs.Value); return comWrappersVtableStruct.ComWrappersInterfacePointers.Contains(CodePointerUtils.CodePointerFromAddress(qiAddress, _target));