From c30dd07ecc4140b36a2f54d6ee2d1eb1f099280a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Apr 2026 19:55:48 +0000 Subject: [PATCH 1/3] Implement EnumerateAssembliesInAppDomain using ILoader contract Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/143d1b2c-c0c6-4b32-9226-dd02eb187236 Co-authored-by: barosiak <76071368+barosiak@users.noreply.github.com> --- .../Dbi/DacDbiImpl.cs | 50 ++++++++++++++++++- .../Dbi/IDacDbiInterface.cs | 2 +- 2 files changed, 49 insertions(+), 3 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 547f4bbbf59621..cc5f14609d2e2a 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 @@ -394,8 +394,54 @@ public int SetCompilerFlags(ulong vmAssembly, Interop.BOOL fAllowJitOpts, Intero return hr; } - public int EnumerateAssembliesInAppDomain(ulong vmAppDomain, nint fpCallback, nint pUserData) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.EnumerateAssembliesInAppDomain(vmAppDomain, fpCallback, pUserData) : HResults.E_NOTIMPL; + public int EnumerateAssembliesInAppDomain(ulong vmAppDomain, delegate* unmanaged fpCallback, nint pUserData) + { + int hr = HResults.S_OK; +#if DEBUG + List? cdacAssemblies = _legacy is not null ? new() : null; +#endif + try + { + if (fpCallback == null) + throw new ArgumentNullException(nameof(fpCallback)); + + if (vmAppDomain == 0) + return hr; + + Contracts.ILoader loader = _target.Contracts.Loader; + foreach (Contracts.ModuleHandle handle in loader.GetModuleHandles( + new TargetPointer(vmAppDomain), + AssemblyIterationFlags.IncludeLoading | AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution)) + { + TargetPointer assembly = loader.GetAssembly(handle); + fpCallback(assembly.Value, pUserData); +#if DEBUG + cdacAssemblies?.Add(assembly.Value); +#endif + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + List dacAssemblies = new(); + GCHandle dacHandle = GCHandle.Alloc(dacAssemblies); + int hrLocal = _legacy.EnumerateAssembliesInAppDomain(vmAppDomain, &CollectEnumerationCallback, GCHandle.ToIntPtr(dacHandle)); + dacHandle.Free(); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert( + cdacAssemblies!.SequenceEqual(dacAssemblies), + $"Assembly enumeration mismatch - cDAC: [{string.Join(",", cdacAssemblies!.Select(a => $"0x{a:x}"))}], DAC: [{string.Join(",", dacAssemblies.Select(a => $"0x{a:x}"))}]"); + } + } +#endif + return hr; + } public int EnumerateModulesInAssembly(ulong vmAssembly, nint fpCallback, nint pUserData) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.EnumerateModulesInAssembly(vmAssembly, fpCallback, pUserData) : HResults.E_NOTIMPL; 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 fe2d48073b3961..3e046d952dd23d 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 @@ -206,7 +206,7 @@ public unsafe partial interface IDacDbiInterface int SetCompilerFlags(ulong vmAssembly, Interop.BOOL fAllowJitOpts, Interop.BOOL fEnableEnC); [PreserveSig] - int EnumerateAssembliesInAppDomain(ulong vmAppDomain, nint fpCallback, nint pUserData); + int EnumerateAssembliesInAppDomain(ulong vmAppDomain, delegate* unmanaged fpCallback, nint pUserData); [PreserveSig] int EnumerateModulesInAssembly(ulong vmAssembly, nint fpCallback, nint pUserData); From 034d78d3d9d0caff0ad86d0419fd3afdf243d30f Mon Sep 17 00:00:00 2001 From: Barbara Rosiak Date: Tue, 28 Apr 2026 17:27:54 -0700 Subject: [PATCH 2/3] Add tests --- .../Dbi/DacDbiImpl.cs | 8 +- .../managed/cdac/tests/DacDbiImplTests.cs | 144 ++++++++++++++++++ 2 files changed, 151 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 cc5f14609d2e2a..08241dbdcb88e1 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 @@ -403,10 +403,14 @@ public int EnumerateAssembliesInAppDomain(ulong vmAppDomain, delegate* unmanaged try { if (fpCallback == null) + { throw new ArgumentNullException(nameof(fpCallback)); + } if (vmAppDomain == 0) + { return hr; + } Contracts.ILoader loader = _target.Contracts.Loader; foreach (Contracts.ModuleHandle handle in loader.GetModuleHandles( @@ -436,7 +440,9 @@ public int EnumerateAssembliesInAppDomain(ulong vmAppDomain, delegate* unmanaged { Debug.Assert( cdacAssemblies!.SequenceEqual(dacAssemblies), - $"Assembly enumeration mismatch - cDAC: [{string.Join(",", cdacAssemblies!.Select(a => $"0x{a:x}"))}], DAC: [{string.Join(",", dacAssemblies.Select(a => $"0x{a:x}"))}]"); + $"Assembly enumeration mismatch - " + + $"cDAC: [{string.Join(",", cdacAssemblies!.Select(a => $"0x{a:x}"))}], " + + $"DAC: [{string.Join(",", dacAssemblies.Select(a => $"0x{a:x}"))}]"); } } #endif diff --git a/src/native/managed/cdac/tests/DacDbiImplTests.cs b/src/native/managed/cdac/tests/DacDbiImplTests.cs index 08d6b89b10f318..0f9268acbeb255 100644 --- a/src/native/managed/cdac/tests/DacDbiImplTests.cs +++ b/src/native/managed/cdac/tests/DacDbiImplTests.cs @@ -3,8 +3,10 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using Microsoft.Diagnostics.DataContractReader.Contracts; using Microsoft.Diagnostics.DataContractReader.Legacy; +using Moq; using Xunit; namespace Microsoft.Diagnostics.DataContractReader.Tests; @@ -253,4 +255,146 @@ public void SetCompilerFlags_EnCBlocked_NotificationProfiler(MockTarget.Architec uint rawFlags = target.Read(moduleAddr + (ulong)flagsOffset); Assert.Equal(0u, rawFlags & IsEditAndContinue); } + + private static DacDbiImpl CreateDacDbiWithMockLoader( + MockTarget.Architecture arch, + Mock mockLoader) + { + var target = new TestPlaceholderTarget.Builder(arch) + .UseReader((_, _) => -1) + .AddMockContract(mockLoader) + .Build(); + return new DacDbiImpl(target, legacyObj: null); + } + + [UnmanagedCallersOnly] + private static unsafe void CollectAssemblyCallback(ulong value, nint pUserData) + { + GCHandle handle = GCHandle.FromIntPtr(pUserData); + ((List)handle.Target!).Add(value); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void EnumerateAssembliesInAppDomain_ZeroAppDomain(MockTarget.Architecture arch) + { + var mockLoader = new Mock(); + DacDbiImpl dacDbi = CreateDacDbiWithMockLoader(arch, mockLoader); + + List assemblies = new(); + GCHandle gcHandle = GCHandle.Alloc(assemblies); + int hr = dacDbi.EnumerateAssembliesInAppDomain(0, &CollectAssemblyCallback, GCHandle.ToIntPtr(gcHandle)); + gcHandle.Free(); + + Assert.Equal(System.HResults.S_OK, hr); + Assert.Empty(assemblies); + mockLoader.Verify( + l => l.GetModuleHandles(It.IsAny(), It.IsAny()), + Times.Never); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void EnumerateAssembliesInAppDomain_NullCallback(MockTarget.Architecture arch) + { + var mockLoader = new Mock(); + DacDbiImpl dacDbi = CreateDacDbiWithMockLoader(arch, mockLoader); + + int hr = dacDbi.EnumerateAssembliesInAppDomain(0x1000, null, nint.Zero); + + Assert.NotEqual(System.HResults.S_OK, hr); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void EnumerateAssembliesInAppDomain_SingleAssembly_CallsCallback(MockTarget.Architecture arch) + { + ulong appDomainAddr = 0x1000; + ulong assemblyAddr = 0x2000; + TargetPointer moduleAddr = new(0x3000); + + var mockLoader = new Mock(); + mockLoader + .Setup(l => l.GetModuleHandles( + new TargetPointer(appDomainAddr), + AssemblyIterationFlags.IncludeLoading | AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution)) + .Returns(new[] { new Contracts.ModuleHandle(moduleAddr) }); + mockLoader + .Setup(l => l.GetAssembly(It.Is(h => h.Address == moduleAddr))) + .Returns(new TargetPointer(assemblyAddr)); + + DacDbiImpl dacDbi = CreateDacDbiWithMockLoader(arch, mockLoader); + + List assemblies = new(); + GCHandle gcHandle = GCHandle.Alloc(assemblies); + int hr = dacDbi.EnumerateAssembliesInAppDomain(appDomainAddr, &CollectAssemblyCallback, GCHandle.ToIntPtr(gcHandle)); + gcHandle.Free(); + + Assert.Equal(System.HResults.S_OK, hr); + Assert.Single(assemblies); + Assert.Equal(assemblyAddr, assemblies[0]); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void EnumerateAssembliesInAppDomain_MultipleAssemblies(MockTarget.Architecture arch) + { + ulong appDomainAddr = 0x1000; + ulong[] expectedAssemblies = [0x2000, 0x3000, 0x4000]; + TargetPointer[] moduleAddrs = [new(0x5000), new(0x6000), new(0x7000)]; + + var mockLoader = new Mock(); + mockLoader + .Setup(l => l.GetModuleHandles( + new TargetPointer(appDomainAddr), + AssemblyIterationFlags.IncludeLoading | AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution)) + .Returns(new[] + { + new Contracts.ModuleHandle(moduleAddrs[0]), + new Contracts.ModuleHandle(moduleAddrs[1]), + new Contracts.ModuleHandle(moduleAddrs[2]), + }); + + for (int i = 0; i < 3; i++) + { + int index = i; + mockLoader + .Setup(l => l.GetAssembly(It.Is(h => h.Address == moduleAddrs[index]))) + .Returns(new TargetPointer(expectedAssemblies[index])); + } + + DacDbiImpl dacDbi = CreateDacDbiWithMockLoader(arch, mockLoader); + + List assemblies = new(); + GCHandle gcHandle = GCHandle.Alloc(assemblies); + int hr = dacDbi.EnumerateAssembliesInAppDomain(appDomainAddr, &CollectAssemblyCallback, GCHandle.ToIntPtr(gcHandle)); + gcHandle.Free(); + + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(expectedAssemblies, assemblies.ToArray()); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void EnumerateAssembliesInAppDomain_NoAssemblies(MockTarget.Architecture arch) + { + ulong appDomainAddr = 0x1000; + + var mockLoader = new Mock(); + mockLoader + .Setup(l => l.GetModuleHandles( + new TargetPointer(appDomainAddr), + AssemblyIterationFlags.IncludeLoading | AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution)) + .Returns(Array.Empty()); + + DacDbiImpl dacDbi = CreateDacDbiWithMockLoader(arch, mockLoader); + + List assemblies = new(); + GCHandle gcHandle = GCHandle.Alloc(assemblies); + int hr = dacDbi.EnumerateAssembliesInAppDomain(appDomainAddr, &CollectAssemblyCallback, GCHandle.ToIntPtr(gcHandle)); + gcHandle.Free(); + + Assert.Equal(System.HResults.S_OK, hr); + Assert.Empty(assemblies); + } } From 6f7b91a5823c484e15865af875c1065a1e16411b Mon Sep 17 00:00:00 2001 From: Barbara Rosiak <76071368+barosiak@users.noreply.github.com> Date: Wed, 29 Apr 2026 10:14:56 -0700 Subject: [PATCH 3/3] Update src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Dbi/DacDbiImpl.cs | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 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 35d2873cc012cf..dc3f6b7d93cdb9 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 @@ -430,20 +430,26 @@ public int EnumerateAssembliesInAppDomain(ulong vmAppDomain, delegate* unmanaged hr = ex.HResult; } #if DEBUG - if (_legacy is not null) + if (_legacy is not null && fpCallback != null) { List dacAssemblies = new(); GCHandle dacHandle = GCHandle.Alloc(dacAssemblies); - int hrLocal = _legacy.EnumerateAssembliesInAppDomain(vmAppDomain, &CollectEnumerationCallback, GCHandle.ToIntPtr(dacHandle)); - dacHandle.Free(); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) + try { - Debug.Assert( - cdacAssemblies!.SequenceEqual(dacAssemblies), - $"Assembly enumeration mismatch - " - + $"cDAC: [{string.Join(",", cdacAssemblies!.Select(a => $"0x{a:x}"))}], " - + $"DAC: [{string.Join(",", dacAssemblies.Select(a => $"0x{a:x}"))}]"); + int hrLocal = _legacy.EnumerateAssembliesInAppDomain(vmAppDomain, &CollectEnumerationCallback, GCHandle.ToIntPtr(dacHandle)); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert( + cdacAssemblies!.SequenceEqual(dacAssemblies), + $"Assembly enumeration mismatch - " + + $"cDAC: [{string.Join(",", cdacAssemblies!.Select(a => $"0x{a:x}"))}], " + + $"DAC: [{string.Join(",", dacAssemblies.Select(a => $"0x{a:x}"))}]"); + } + } + finally + { + dacHandle.Free(); } } #endif