Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,16 @@ public struct DacpRCWData
public Interop.BOOL isDisconnected;
}

public enum VCSHeapType : int
{
IndcellHeap = 0,
LookupHeap = 1,
ResolveHeap = 2,
DispatchHeap = 3,
CacheEntryHeap = 4,
VtableHeap = 5
}

[GeneratedComInterface]
[Guid("436f00f2-b42a-4b9f-870c-e73db66ae930")]
public unsafe partial interface ISOSDacInterface
Expand Down Expand Up @@ -818,7 +828,7 @@ public unsafe partial interface ISOSDacInterface
[PreserveSig]
int GetCodeHeapList(ClrDataAddress jitManager, uint count, [In, MarshalUsing(CountElementName = nameof(count)), Out] DacpJitCodeHeapInfo[]? codeHeaps, uint* pNeeded);
[PreserveSig]
int TraverseVirtCallStubHeap(ClrDataAddress pAppDomain, /*VCSHeapType*/ int heaptype, /*VISITHEAP*/ void* pCallback);
int TraverseVirtCallStubHeap(ClrDataAddress pAppDomain, VCSHeapType heaptype, /*VISITHEAP*/ delegate* unmanaged<ulong, nuint, Interop.BOOL, void> pCallback);

// Other
[PreserveSig]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4668,7 +4668,7 @@ private static void TraverseLoaderHeapDebugCallback(ulong virtualAddress, nuint
}
#endif

private int TraverseLoaderHeapCore(ClrDataAddress loaderHeapAddr, delegate* unmanaged<ulong, nuint, Interop.BOOL, void> pCallback)
private int TraverseLoaderHeapCore(TargetPointer loaderHeapAddr, delegate* unmanaged<ulong, nuint, Interop.BOOL, void> pCallback)
{
int hr = HResults.S_OK;
#if DEBUG
Expand All @@ -4677,13 +4677,12 @@ private int TraverseLoaderHeapCore(ClrDataAddress loaderHeapAddr, delegate* unma
#endif
try
{
if (loaderHeapAddr == 0 || pCallback is null)
if (loaderHeapAddr == TargetPointer.Null || pCallback is null)
throw new ArgumentException();
int iterationMax = 8192;

Contracts.ILoader loader = _target.Contracts.Loader;
TargetPointer heapAddr = loaderHeapAddr.ToTargetPointer(_target);
TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr);
TargetPointer block = loader.GetFirstLoaderHeapBlock(loaderHeapAddr);
TargetPointer firstBlock = block;
int i = 0;
while (block != TargetPointer.Null && i++ < iterationMax)
Expand Down Expand Up @@ -4717,7 +4716,7 @@ private int TraverseLoaderHeapCore(ClrDataAddress loaderHeapAddr, delegate* unma

int ISOSDacInterface.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, delegate* unmanaged<ulong, nuint, Interop.BOOL, void> pCallback)
{
int hr = TraverseLoaderHeapCore(loaderHeapAddr, pCallback);
int hr = TraverseLoaderHeapCore(loaderHeapAddr.ToTargetPointer(_target), pCallback);
#if DEBUG
if (_legacyImpl is not null)
{
Expand Down Expand Up @@ -4862,8 +4861,57 @@ int ISOSDacInterface.TraverseRCWCleanupList(ClrDataAddress cleanupListPtr, deleg
#endif
return hr;
}
int ISOSDacInterface.TraverseVirtCallStubHeap(ClrDataAddress pAppDomain, int heaptype, void* pCallback)
=> LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.TraverseVirtCallStubHeap(pAppDomain, heaptype, pCallback) : HResults.E_NOTIMPL;
int ISOSDacInterface.TraverseVirtCallStubHeap(ClrDataAddress pAppDomain, VCSHeapType heaptype, delegate* unmanaged<ulong, nuint, Interop.BOOL, void> pCallback)
{
int hr = HResults.S_OK;
try
{
// Native DAC only validates pAppDomain here; traversal always uses the global loader allocator.
if (pAppDomain == 0 || pCallback is null)
throw new ArgumentException();

Comment thread
rcj1 marked this conversation as resolved.
Contracts.ILoader loader = _target.Contracts.Loader;
TargetPointer globalLoaderAllocator = loader.GetGlobalLoaderAllocator();
IReadOnlyDictionary<string, TargetPointer> heaps = loader.GetLoaderAllocatorHeaps(globalLoaderAllocator);

if (!heaps.ContainsKey("IndcellHeap"))
throw new NullReferenceException();

string? heapName = heaptype switch
{
VCSHeapType.IndcellHeap => "IndcellHeap",
VCSHeapType.CacheEntryHeap => "CacheEntryHeap",
_ => throw new ArgumentException(),
};

if (heaps.TryGetValue(heapName, out TargetPointer heap) && heap != TargetPointer.Null)
{
hr = TraverseLoaderHeapCore(heap, pCallback);
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}

#if DEBUG
if (_legacyImpl is not null)
{
int cdacCount = DebugTraverseLoaderHeapBlocks.Count;
delegate* unmanaged<ulong, nuint, Interop.BOOL, void> debugCallbackPtr = &TraverseLoaderHeapDebugCallback;
int hrLocal = _legacyImpl.TraverseVirtCallStubHeap(pAppDomain, heaptype, debugCallbackPtr);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK || hr == HResults.S_FALSE)
{
Debug.Assert(DebugTraverseLoaderHeapBlocks.Count == 0,
$"cDAC found {cdacCount} blocks, DAC matched {_debugTraverseLoaderDebugCount}, {DebugTraverseLoaderHeapBlocks.Count} unmatched");
Debug.Assert(_debugTraverseLoaderDebugCount == (uint)cdacCount,
$"cDAC: {cdacCount} blocks, DAC: {_debugTraverseLoaderDebugCount} blocks");
}
}
#endif
return hr;
}
#endregion ISOSDacInterface

#region ISOSDacInterface2
Expand Down Expand Up @@ -6207,7 +6255,7 @@ int ISOSDacInterface12.GetGlobalAllocationContext(ClrDataAddress* allocPtr, ClrD

int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*LoaderHeapKind*/ int kind, /*VISITHEAP*/ delegate* unmanaged<ulong, nuint, Interop.BOOL, void> pCallback)
{
int hr = TraverseLoaderHeapCore(loaderHeapAddr, pCallback);
int hr = TraverseLoaderHeapCore(loaderHeapAddr.ToTargetPointer(_target), pCallback);
#if DEBUG
if (_legacyImpl13 is not null)
{
Expand Down
171 changes: 171 additions & 0 deletions src/native/managed/cdac/tests/LoaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,15 @@ public void TryGetSimpleName_InvalidUtf8(MockTarget.Architecture arch)
["CacheEntryHeap"] = new(0x9000),
};

private const VCSHeapType VCSHeapTypeIndcell = VCSHeapType.IndcellHeap;
private const VCSHeapType VCSHeapTypeCacheEntry = VCSHeapType.CacheEntryHeap;
private const VCSHeapType InvalidVCSHeapType = (VCSHeapType)99;

[UnmanagedCallersOnly]
private static void VisitHeapNoOp(ulong address, nuint size, Interop.BOOL isCurrent)
{
}

private static ISOSDacInterface13 CreateSOSDacInterface13ForHeapTests(MockTarget.Architecture arch)
{
var targetBuilder = new TestPlaceholderTarget.Builder(arch);
Expand Down Expand Up @@ -207,6 +216,168 @@ private static ISOSDacInterface13 CreateSOSDacInterface13ForHeapTests(MockTarget
return new SOSDacImpl(target, null);
}

private static (ISOSDacInterface Interface, Mock<ILoader> Loader) CreateSOSDacInterfaceForVirtCallHeapTests(MockTarget.Architecture arch)
{
Mock<ILoader> loader = new(MockBehavior.Strict);
TargetPointer globalLoaderAllocator = new(0x100);
loader.Setup(l => l.GetGlobalLoaderAllocator()).Returns(globalLoaderAllocator);

var target = new TestPlaceholderTarget.Builder(arch)
.AddMockContract<ILoader>(loader.Object)
.Build();

return (new SOSDacImpl(target, null), loader);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void TraverseVirtCallStubHeap_IndcellHeap_Traverses(MockTarget.Architecture arch)
{
(ISOSDacInterface impl, Mock<ILoader> loader) = CreateSOSDacInterfaceForVirtCallHeapTests(arch);

TargetPointer indcellHeap = new(0x8000);
TargetPointer firstBlock = new(0x8100);
var heaps = new Dictionary<string, TargetPointer>
{
["IndcellHeap"] = indcellHeap,
};
loader.Setup(l => l.GetLoaderAllocatorHeaps(new TargetPointer(0x100)))
.Returns((IReadOnlyDictionary<string, TargetPointer>)heaps);
loader.Setup(l => l.GetFirstLoaderHeapBlock(indcellHeap)).Returns(firstBlock);
loader.Setup(l => l.GetLoaderHeapBlockData(firstBlock)).Returns(new LoaderHeapBlockData
{
Address = new TargetPointer(0x9000),
Size = new TargetNUInt(0x40),
NextBlock = TargetPointer.Null,
});

delegate* unmanaged<ulong, nuint, Interop.BOOL, void> callback = &VisitHeapNoOp;
int hr = impl.TraverseVirtCallStubHeap(new ClrDataAddress(0x1), VCSHeapTypeIndcell, callback);

Assert.Equal(HResults.S_OK, hr);
loader.Verify(l => l.GetFirstLoaderHeapBlock(indcellHeap), Times.Once());
loader.Verify(l => l.GetLoaderHeapBlockData(firstBlock), Times.Once());
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void TraverseVirtCallStubHeap_CacheEntryHeap_Traverses(MockTarget.Architecture arch)
{
(ISOSDacInterface impl, Mock<ILoader> loader) = CreateSOSDacInterfaceForVirtCallHeapTests(arch);

TargetPointer cacheEntryHeap = new(0x9000);
TargetPointer firstBlock = new(0x9100);
var heaps = new Dictionary<string, TargetPointer>
{
["IndcellHeap"] = new TargetPointer(0x8000),
["CacheEntryHeap"] = cacheEntryHeap,
};
loader.Setup(l => l.GetLoaderAllocatorHeaps(new TargetPointer(0x100)))
.Returns((IReadOnlyDictionary<string, TargetPointer>)heaps);
loader.Setup(l => l.GetFirstLoaderHeapBlock(cacheEntryHeap)).Returns(firstBlock);
loader.Setup(l => l.GetLoaderHeapBlockData(firstBlock)).Returns(new LoaderHeapBlockData
{
Address = new TargetPointer(0xA000),
Size = new TargetNUInt(0x40),
NextBlock = TargetPointer.Null,
});

delegate* unmanaged<ulong, nuint, Interop.BOOL, void> callback = &VisitHeapNoOp;
int hr = impl.TraverseVirtCallStubHeap(new ClrDataAddress(0x1), VCSHeapTypeCacheEntry, callback);

Assert.Equal(HResults.S_OK, hr);
loader.Verify(l => l.GetFirstLoaderHeapBlock(cacheEntryHeap), Times.Once());
loader.Verify(l => l.GetLoaderHeapBlockData(firstBlock), Times.Once());
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void TraverseVirtCallStubHeap_NoVirtualCallStubManager_ReturnsEPointer(MockTarget.Architecture arch)
{
(ISOSDacInterface impl, Mock<ILoader> loader) = CreateSOSDacInterfaceForVirtCallHeapTests(arch);

loader.Setup(l => l.GetLoaderAllocatorHeaps(new TargetPointer(0x100)))
.Returns((IReadOnlyDictionary<string, TargetPointer>)new Dictionary<string, TargetPointer>());

delegate* unmanaged<ulong, nuint, Interop.BOOL, void> callback = &VisitHeapNoOp;
int hr = impl.TraverseVirtCallStubHeap(new ClrDataAddress(0x1), VCSHeapTypeIndcell, callback);

Assert.Equal(HResults.E_POINTER, hr);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void TraverseVirtCallStubHeap_CacheEntryMissing_ReturnsSOk(MockTarget.Architecture arch)
{
(ISOSDacInterface impl, Mock<ILoader> loader) = CreateSOSDacInterfaceForVirtCallHeapTests(arch);

Comment thread
rcj1 marked this conversation as resolved.
TargetPointer indcellHeap = new(0x8000);
loader.Setup(l => l.GetLoaderAllocatorHeaps(new TargetPointer(0x100)))
Comment thread
rcj1 marked this conversation as resolved.
.Returns((IReadOnlyDictionary<string, TargetPointer>)new Dictionary<string, TargetPointer>
{
["IndcellHeap"] = indcellHeap,
});

delegate* unmanaged<ulong, nuint, Interop.BOOL, void> callback = &VisitHeapNoOp;
int hr = impl.TraverseVirtCallStubHeap(new ClrDataAddress(0x1), VCSHeapTypeCacheEntry, callback);

Assert.Equal(HResults.S_OK, hr);
loader.Verify(l => l.GetFirstLoaderHeapBlock(It.IsAny<TargetPointer>()), Times.Never());
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void TraverseVirtCallStubHeap_InvalidHeapType_ReturnsEInvalidArg(MockTarget.Architecture arch)
{
(ISOSDacInterface impl, Mock<ILoader> loader) = CreateSOSDacInterfaceForVirtCallHeapTests(arch);

loader.Setup(l => l.GetLoaderAllocatorHeaps(new TargetPointer(0x100)))
.Returns((IReadOnlyDictionary<string, TargetPointer>)new Dictionary<string, TargetPointer>
{
["IndcellHeap"] = new TargetPointer(0x8000),
});

delegate* unmanaged<ulong, nuint, Interop.BOOL, void> callback = &VisitHeapNoOp;
int hr = impl.TraverseVirtCallStubHeap(new ClrDataAddress(0x1), InvalidVCSHeapType, callback);

Assert.Equal(HResults.E_INVALIDARG, hr);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void TraverseVirtCallStubHeap_InvalidArguments_ReturnsEInvalidArg(MockTarget.Architecture arch)
Comment thread
rcj1 marked this conversation as resolved.
{
(ISOSDacInterface impl, Mock<ILoader> loader) = CreateSOSDacInterfaceForVirtCallHeapTests(arch);

loader.Setup(l => l.GetLoaderAllocatorHeaps(new TargetPointer(0x100)))
.Returns((IReadOnlyDictionary<string, TargetPointer>)new Dictionary<string, TargetPointer>
{
["IndcellHeap"] = new TargetPointer(0x8000),
});

delegate* unmanaged<ulong, nuint, Interop.BOOL, void> callback = &VisitHeapNoOp;
int hr = impl.TraverseVirtCallStubHeap(new ClrDataAddress(0), VCSHeapTypeIndcell, callback);

Assert.Equal(HResults.E_INVALIDARG, hr);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void TraverseVirtCallStubHeap_NullCallback_ReturnsEInvalidArg(MockTarget.Architecture arch)
{
(ISOSDacInterface impl, Mock<ILoader> loader) = CreateSOSDacInterfaceForVirtCallHeapTests(arch);

loader.Setup(l => l.GetLoaderAllocatorHeaps(new TargetPointer(0x100)))
.Returns((IReadOnlyDictionary<string, TargetPointer>)new Dictionary<string, TargetPointer>
{
["IndcellHeap"] = new TargetPointer(0x8000),
});

int hr = impl.TraverseVirtCallStubHeap(new ClrDataAddress(0x1), VCSHeapTypeIndcell, null);

Assert.Equal(HResults.E_INVALIDARG, hr);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void GetLoaderAllocatorHeapNames_GetCount(MockTarget.Architecture arch)
Expand Down
Loading