Skip to content

Commit 713491c

Browse files
CopilotbarosiakCopilot
authored
[cDAC] Implement EnumerateAssembliesInAppDomain for cDAC (#127540)
## Description Replaces the legacy-delegation stub in `DacDbiImpl.EnumerateAssembliesInAppDomain` with a managed implementation using the `ILoader` contract, mirroring the native C++ logic in `dacdbiimpl.cpp:4412–4449`. ### Changes - **`IDacDbiInterface.cs`**: Changed `fpCallback` parameter type from `nint` to `delegate* unmanaged<ulong, nint, void>` (consistent with `EnumerateThreads`) - **`DacDbiImpl.cs`**: Full implementation that: - Returns `S_OK` early on null `vmAppDomain` - Enumerates via `ILoader.GetModuleHandles` with `IncludeLoading | IncludeLoaded | IncludeExecution` - Resolves each `ModuleHandle` to an assembly pointer via `ILoader.GetAssembly` - Calls `fpCallback(assembly, pUserData)` per entry - `#if DEBUG` validation against legacy DAC (same pattern as `EnumerateThreads`) - DacDbiImplTests.cs - Add 5 tests covering zero AppDomain, null callback, single/multiple/empty assembly enumeration with mocked ILoade --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: barosiak <76071368+barosiak@users.noreply.github.com> Co-authored-by: Barbara Rosiak <brosiak@microsoft.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent cc5d589 commit 713491c

3 files changed

Lines changed: 205 additions & 3 deletions

File tree

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,8 +395,66 @@ public int SetCompilerFlags(ulong vmAssembly, Interop.BOOL fAllowJitOpts, Intero
395395
return hr;
396396
}
397397

398-
public int EnumerateAssembliesInAppDomain(ulong vmAppDomain, nint fpCallback, nint pUserData)
399-
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.EnumerateAssembliesInAppDomain(vmAppDomain, fpCallback, pUserData) : HResults.E_NOTIMPL;
398+
public int EnumerateAssembliesInAppDomain(ulong vmAppDomain, delegate* unmanaged<ulong, nint, void> fpCallback, nint pUserData)
399+
{
400+
int hr = HResults.S_OK;
401+
#if DEBUG
402+
List<ulong>? cdacAssemblies = _legacy is not null ? new() : null;
403+
#endif
404+
try
405+
{
406+
if (fpCallback == null)
407+
{
408+
throw new ArgumentNullException(nameof(fpCallback));
409+
}
410+
411+
if (vmAppDomain == 0)
412+
{
413+
return hr;
414+
}
415+
416+
Contracts.ILoader loader = _target.Contracts.Loader;
417+
foreach (Contracts.ModuleHandle handle in loader.GetModuleHandles(
418+
new TargetPointer(vmAppDomain),
419+
AssemblyIterationFlags.IncludeLoading | AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution))
420+
{
421+
TargetPointer assembly = loader.GetAssembly(handle);
422+
fpCallback(assembly.Value, pUserData);
423+
#if DEBUG
424+
cdacAssemblies?.Add(assembly.Value);
425+
#endif
426+
}
427+
}
428+
catch (System.Exception ex)
429+
{
430+
hr = ex.HResult;
431+
}
432+
#if DEBUG
433+
if (_legacy is not null && fpCallback != null)
434+
{
435+
List<ulong> dacAssemblies = new();
436+
GCHandle dacHandle = GCHandle.Alloc(dacAssemblies);
437+
try
438+
{
439+
int hrLocal = _legacy.EnumerateAssembliesInAppDomain(vmAppDomain, &CollectEnumerationCallback, GCHandle.ToIntPtr(dacHandle));
440+
Debug.ValidateHResult(hr, hrLocal);
441+
if (hr == HResults.S_OK)
442+
{
443+
Debug.Assert(
444+
cdacAssemblies!.SequenceEqual(dacAssemblies),
445+
$"Assembly enumeration mismatch - "
446+
+ $"cDAC: [{string.Join(",", cdacAssemblies!.Select(a => $"0x{a:x}"))}], "
447+
+ $"DAC: [{string.Join(",", dacAssemblies.Select(a => $"0x{a:x}"))}]");
448+
}
449+
}
450+
finally
451+
{
452+
dacHandle.Free();
453+
}
454+
}
455+
#endif
456+
return hr;
457+
}
400458

401459
public int EnumerateModulesInAssembly(ulong vmAssembly, nint fpCallback, nint pUserData)
402460
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.EnumerateModulesInAssembly(vmAssembly, fpCallback, pUserData) : HResults.E_NOTIMPL;

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ public unsafe partial interface IDacDbiInterface
206206
int SetCompilerFlags(ulong vmAssembly, Interop.BOOL fAllowJitOpts, Interop.BOOL fEnableEnC);
207207

208208
[PreserveSig]
209-
int EnumerateAssembliesInAppDomain(ulong vmAppDomain, nint fpCallback, nint pUserData);
209+
int EnumerateAssembliesInAppDomain(ulong vmAppDomain, delegate* unmanaged<ulong, nint, void> fpCallback, nint pUserData);
210210

211211
[PreserveSig]
212212
int EnumerateModulesInAssembly(ulong vmAssembly, nint fpCallback, nint pUserData);

src/native/managed/cdac/tests/DacDbiImplTests.cs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Runtime.InteropServices;
67
using Microsoft.Diagnostics.DataContractReader.Contracts;
78
using Microsoft.Diagnostics.DataContractReader.Legacy;
9+
using Moq;
810
using Xunit;
911

1012
namespace Microsoft.Diagnostics.DataContractReader.Tests;
@@ -253,4 +255,146 @@ public void SetCompilerFlags_EnCBlocked_NotificationProfiler(MockTarget.Architec
253255
uint rawFlags = target.Read<uint>(moduleAddr + (ulong)flagsOffset);
254256
Assert.Equal(0u, rawFlags & IsEditAndContinue);
255257
}
258+
259+
private static DacDbiImpl CreateDacDbiWithMockLoader(
260+
MockTarget.Architecture arch,
261+
Mock<ILoader> mockLoader)
262+
{
263+
var target = new TestPlaceholderTarget.Builder(arch)
264+
.UseReader((_, _) => -1)
265+
.AddMockContract(mockLoader)
266+
.Build();
267+
return new DacDbiImpl(target, legacyObj: null);
268+
}
269+
270+
[UnmanagedCallersOnly]
271+
private static unsafe void CollectAssemblyCallback(ulong value, nint pUserData)
272+
{
273+
GCHandle handle = GCHandle.FromIntPtr(pUserData);
274+
((List<ulong>)handle.Target!).Add(value);
275+
}
276+
277+
[Theory]
278+
[ClassData(typeof(MockTarget.StdArch))]
279+
public void EnumerateAssembliesInAppDomain_ZeroAppDomain(MockTarget.Architecture arch)
280+
{
281+
var mockLoader = new Mock<ILoader>();
282+
DacDbiImpl dacDbi = CreateDacDbiWithMockLoader(arch, mockLoader);
283+
284+
List<ulong> assemblies = new();
285+
GCHandle gcHandle = GCHandle.Alloc(assemblies);
286+
int hr = dacDbi.EnumerateAssembliesInAppDomain(0, &CollectAssemblyCallback, GCHandle.ToIntPtr(gcHandle));
287+
gcHandle.Free();
288+
289+
Assert.Equal(System.HResults.S_OK, hr);
290+
Assert.Empty(assemblies);
291+
mockLoader.Verify(
292+
l => l.GetModuleHandles(It.IsAny<TargetPointer>(), It.IsAny<AssemblyIterationFlags>()),
293+
Times.Never);
294+
}
295+
296+
[Theory]
297+
[ClassData(typeof(MockTarget.StdArch))]
298+
public void EnumerateAssembliesInAppDomain_NullCallback(MockTarget.Architecture arch)
299+
{
300+
var mockLoader = new Mock<ILoader>();
301+
DacDbiImpl dacDbi = CreateDacDbiWithMockLoader(arch, mockLoader);
302+
303+
int hr = dacDbi.EnumerateAssembliesInAppDomain(0x1000, null, nint.Zero);
304+
305+
Assert.NotEqual(System.HResults.S_OK, hr);
306+
}
307+
308+
[Theory]
309+
[ClassData(typeof(MockTarget.StdArch))]
310+
public void EnumerateAssembliesInAppDomain_SingleAssembly_CallsCallback(MockTarget.Architecture arch)
311+
{
312+
ulong appDomainAddr = 0x1000;
313+
ulong assemblyAddr = 0x2000;
314+
TargetPointer moduleAddr = new(0x3000);
315+
316+
var mockLoader = new Mock<ILoader>();
317+
mockLoader
318+
.Setup(l => l.GetModuleHandles(
319+
new TargetPointer(appDomainAddr),
320+
AssemblyIterationFlags.IncludeLoading | AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution))
321+
.Returns(new[] { new Contracts.ModuleHandle(moduleAddr) });
322+
mockLoader
323+
.Setup(l => l.GetAssembly(It.Is<Contracts.ModuleHandle>(h => h.Address == moduleAddr)))
324+
.Returns(new TargetPointer(assemblyAddr));
325+
326+
DacDbiImpl dacDbi = CreateDacDbiWithMockLoader(arch, mockLoader);
327+
328+
List<ulong> assemblies = new();
329+
GCHandle gcHandle = GCHandle.Alloc(assemblies);
330+
int hr = dacDbi.EnumerateAssembliesInAppDomain(appDomainAddr, &CollectAssemblyCallback, GCHandle.ToIntPtr(gcHandle));
331+
gcHandle.Free();
332+
333+
Assert.Equal(System.HResults.S_OK, hr);
334+
Assert.Single(assemblies);
335+
Assert.Equal(assemblyAddr, assemblies[0]);
336+
}
337+
338+
[Theory]
339+
[ClassData(typeof(MockTarget.StdArch))]
340+
public void EnumerateAssembliesInAppDomain_MultipleAssemblies(MockTarget.Architecture arch)
341+
{
342+
ulong appDomainAddr = 0x1000;
343+
ulong[] expectedAssemblies = [0x2000, 0x3000, 0x4000];
344+
TargetPointer[] moduleAddrs = [new(0x5000), new(0x6000), new(0x7000)];
345+
346+
var mockLoader = new Mock<ILoader>();
347+
mockLoader
348+
.Setup(l => l.GetModuleHandles(
349+
new TargetPointer(appDomainAddr),
350+
AssemblyIterationFlags.IncludeLoading | AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution))
351+
.Returns(new[]
352+
{
353+
new Contracts.ModuleHandle(moduleAddrs[0]),
354+
new Contracts.ModuleHandle(moduleAddrs[1]),
355+
new Contracts.ModuleHandle(moduleAddrs[2]),
356+
});
357+
358+
for (int i = 0; i < 3; i++)
359+
{
360+
int index = i;
361+
mockLoader
362+
.Setup(l => l.GetAssembly(It.Is<Contracts.ModuleHandle>(h => h.Address == moduleAddrs[index])))
363+
.Returns(new TargetPointer(expectedAssemblies[index]));
364+
}
365+
366+
DacDbiImpl dacDbi = CreateDacDbiWithMockLoader(arch, mockLoader);
367+
368+
List<ulong> assemblies = new();
369+
GCHandle gcHandle = GCHandle.Alloc(assemblies);
370+
int hr = dacDbi.EnumerateAssembliesInAppDomain(appDomainAddr, &CollectAssemblyCallback, GCHandle.ToIntPtr(gcHandle));
371+
gcHandle.Free();
372+
373+
Assert.Equal(System.HResults.S_OK, hr);
374+
Assert.Equal(expectedAssemblies, assemblies.ToArray());
375+
}
376+
377+
[Theory]
378+
[ClassData(typeof(MockTarget.StdArch))]
379+
public void EnumerateAssembliesInAppDomain_NoAssemblies(MockTarget.Architecture arch)
380+
{
381+
ulong appDomainAddr = 0x1000;
382+
383+
var mockLoader = new Mock<ILoader>();
384+
mockLoader
385+
.Setup(l => l.GetModuleHandles(
386+
new TargetPointer(appDomainAddr),
387+
AssemblyIterationFlags.IncludeLoading | AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution))
388+
.Returns(Array.Empty<Contracts.ModuleHandle>());
389+
390+
DacDbiImpl dacDbi = CreateDacDbiWithMockLoader(arch, mockLoader);
391+
392+
List<ulong> assemblies = new();
393+
GCHandle gcHandle = GCHandle.Alloc(assemblies);
394+
int hr = dacDbi.EnumerateAssembliesInAppDomain(appDomainAddr, &CollectAssemblyCallback, GCHandle.ToIntPtr(gcHandle));
395+
gcHandle.Free();
396+
397+
Assert.Equal(System.HResults.S_OK, hr);
398+
Assert.Empty(assemblies);
399+
}
256400
}

0 commit comments

Comments
 (0)