diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index ad5d135b3b97dc..50e3a954ba6f4e 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -96,8 +96,8 @@ public readonly struct GCOomData // Returns pointers to all GC heaps IEnumerable GetGCHeaps(); - // The following APIs have both a workstation and serer variant. - // The workstation variant implitly operates on the global heap. + // The following APIs have both a workstation and server variant. + // The workstation variant implicitly operates on the global heap. // The server variants allow passing in a heap pointer. // Gets data about a GC heap diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 70aa41dd499fd8..0e08898b647c67 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -52,6 +52,13 @@ record struct ModuleLookupTables( TargetPointer TypeDefToMethodTable, TargetPointer TypeRefToMethodTable, TargetPointer MethodDefToILCodeVersioningState); + +readonly struct LoaderHeapBlockData +{ + TargetPointer Address { get; init; } + TargetNUInt Size { get; init; } + TargetPointer NextBlock { get; init; } +} ``` ``` csharp @@ -92,6 +99,10 @@ TargetPointer GetStubHeap(TargetPointer loaderAllocatorPointer); TargetPointer GetObjectHandle(TargetPointer loaderAllocatorPointer); TargetPointer GetILHeader(ModuleHandle handle, uint token); TargetPointer GetDynamicIL(ModuleHandle handle, uint token); +// Returns the first block of the loader heap linked list, or TargetPointer.Null if the heap has no blocks. +TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap); +// Returns the data for the given loader heap block (address, size, and next block pointer). +LoaderHeapBlockData GetLoaderHeapBlockData(TargetPointer block); IReadOnlyDictionary GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer); DebuggerAssemblyControlFlags GetDebuggerInfoBits(ModuleHandle handle); @@ -201,6 +212,10 @@ enum ClrModifiableAssemblies : uint | `DynamicILBlobTable` | `EntrySize` | Size of each table entry | | `DynamicILBlobTable` | `EntryMethodToken` | Offset of each entry method token from entry address | | `DynamicILBlobTable` | `EntryIL` | Offset of each entry IL from entry address | +| `LoaderHeap` | `FirstBlock` | Pointer to the first `LoaderHeapBlock` in the linked list | +| `LoaderHeapBlock` | `Next` | Pointer to the next `LoaderHeapBlock` in the linked list | +| `LoaderHeapBlock` | `VirtualAddress` | Pointer to the start of the reserved virtual memory | +| `LoaderHeapBlock` | `VirtualSize` | Size in bytes of the reserved virtual memory region | | `EEConfig` | `ModifiableAssemblies` | Controls Edit and Continue support (ClrModifiableAssemblies enum) | @@ -947,3 +962,22 @@ class InstMethodHashTable } } ``` + +#### GetFirstLoaderHeapBlock, GetLoaderHeapBlockData + +```csharp +TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap) +{ + return target.ReadPointer(loaderHeap + /* LoaderHeap::FirstBlock offset */); +} + +LoaderHeapBlockData ILoader.GetLoaderHeapBlockData(TargetPointer block) +{ + return new LoaderHeapBlockData + { + Address = target.ReadPointer(block + /* LoaderHeapBlock::VirtualAddress offset */), + Size = target.ReadNUInt(block + /* LoaderHeapBlock::VirtualSize offset */), + NextBlock = target.ReadPointer(block + /* LoaderHeapBlock::Next offset */), + }; +} +``` diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 9e72098dd863bb..23540881039d9f 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -3575,7 +3575,7 @@ ClrDataAccess::TraverseLoaderHeap(CLRDATA_ADDRESS loaderHeapAddr, VISITHEAP pFun SOSDacEnter(); - hr = TraverseLoaderHeapBlock(PTR_UnlockedLoaderHeapBase(TO_TADDR(loaderHeapAddr))->m_pFirstBlock, pFunc); + hr = TraverseLoaderHeapBlock(PTR_UnlockedLoaderHeapBaseTraversable(TO_TADDR(loaderHeapAddr))->m_pFirstBlock, pFunc); SOSDacLeave(); return hr; @@ -3591,20 +3591,7 @@ ClrDataAccess::TraverseLoaderHeap(CLRDATA_ADDRESS loaderHeapAddr, LoaderHeapKind SOSDacEnter(); - switch (kind) - { - case LoaderHeapKindNormal: - hr = TraverseLoaderHeapBlock(PTR_UnlockedLoaderHeapBase(TO_TADDR(loaderHeapAddr))->m_pFirstBlock, pCallback); - break; - - case LoaderHeapKindExplicitControl: - hr = TraverseLoaderHeapBlock(PTR_ExplicitControlLoaderHeap(TO_TADDR(loaderHeapAddr))->m_pFirstBlock, pCallback); - break; - - default: - hr = E_NOTIMPL; - break; - } + hr = TraverseLoaderHeapBlock(PTR_UnlockedLoaderHeapBaseTraversable(TO_TADDR(loaderHeapAddr))->m_pFirstBlock, pCallback); SOSDacLeave(); return hr; diff --git a/src/coreclr/inc/loaderheap.h b/src/coreclr/inc/loaderheap.h index 79228c7eb5410d..56b494701d6898 100644 --- a/src/coreclr/inc/loaderheap.h +++ b/src/coreclr/inc/loaderheap.h @@ -17,6 +17,7 @@ #include "utilcode.h" #include "ex.h" #include "executableallocator.h" +#include "cdacdata.h" //============================================================================== // Interface used to back out loader heap allocations. @@ -174,8 +175,13 @@ enum class LoaderHeapImplementationKind Interleaved }; +typedef DPTR(class UnlockedLoaderHeapBaseTraversable) PTR_UnlockedLoaderHeapBaseTraversable; class UnlockedLoaderHeapBaseTraversable { + friend struct cdac_data; +#ifdef DACCESS_COMPILE + friend class ClrDataAccess; +#endif protected: #ifdef DACCESS_COMPILE UnlockedLoaderHeapBaseTraversable() {} @@ -188,6 +194,8 @@ class UnlockedLoaderHeapBaseTraversable #endif public: + // DO NOT REMOVE : This is needed for layout stability. + virtual ~UnlockedLoaderHeapBaseTraversable() {} #ifdef DACCESS_COMPILE public: void EnumMemoryRegions(enum CLRDataEnumMemoryFlags flags); @@ -201,12 +209,17 @@ typedef bool EnumPageRegionsCallback (PTR_VOID pvArgs, PTR_VOID pvAllocationBase PTR_LoaderHeapBlock m_pFirstBlock; }; +template<> +struct cdac_data +{ + static constexpr size_t FirstBlock = offsetof(UnlockedLoaderHeapBaseTraversable, m_pFirstBlock); +}; + //=============================================================================== // This is the base class for LoaderHeap and InterleavedLoaderHeap. It holds the // common handling for LoaderHeap events, and the data structures used for bump // pointer allocation (although not the actual allocation routines). //=============================================================================== -typedef DPTR(class UnlockedLoaderHeapBase) PTR_UnlockedLoaderHeapBase; class UnlockedLoaderHeapBase : public UnlockedLoaderHeapBaseTraversable, public ILoaderHeapBackout { #ifdef _DEBUG @@ -599,9 +612,6 @@ class UnlockedInterleavedLoaderHeap : public UnlockedLoaderHeapBase typedef DPTR(class ExplicitControlLoaderHeap) PTR_ExplicitControlLoaderHeap; class ExplicitControlLoaderHeap : public UnlockedLoaderHeapBaseTraversable { -#ifdef DACCESS_COMPILE - friend class ClrDataAccess; -#endif private: // Allocation pointer in current block diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index a13b9b275ce31c..b81afab6d88ead 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -309,6 +309,17 @@ CDAC_TYPE_FIELD(LoaderAllocator, T_POINTER, VirtualCallStubManager, cdac_data::ObjectHandle) CDAC_TYPE_END(LoaderAllocator) +CDAC_TYPE_BEGIN(LoaderHeap) +CDAC_TYPE_INDETERMINATE(LoaderHeap) +CDAC_TYPE_FIELD(LoaderHeap, T_POINTER, FirstBlock, cdac_data::FirstBlock) +CDAC_TYPE_END(LoaderHeap) + +CDAC_TYPE_BEGIN(LoaderHeapBlock) +CDAC_TYPE_FIELD(LoaderHeapBlock, T_POINTER, Next, offsetof(LoaderHeapBlock, pNext)) +CDAC_TYPE_FIELD(LoaderHeapBlock, T_POINTER, VirtualAddress, offsetof(LoaderHeapBlock, pVirtualAddress)) +CDAC_TYPE_FIELD(LoaderHeapBlock, T_NUINT, VirtualSize, offsetof(LoaderHeapBlock, dwVirtualSize)) +CDAC_TYPE_END(LoaderHeapBlock) + CDAC_TYPE_BEGIN(VirtualCallStubManager) CDAC_TYPE_INDETERMINATE(VirtualCallStubManager) CDAC_TYPE_FIELD(VirtualCallStubManager, T_POINTER, IndcellHeap, cdac_data::IndcellHeap) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs index 0b1a63db75ea96..8c2ec5e834c4cb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs @@ -73,6 +73,13 @@ public record struct ModuleLookupTables( TargetPointer TypeRefToMethodTable, TargetPointer MethodDefToILCodeVersioningState); +public readonly struct LoaderHeapBlockData +{ + public TargetPointer Address { get; init; } + public TargetNUInt Size { get; init; } + public TargetPointer NextBlock { get; init; } +} + public interface ILoader : IContract { static string IContract.Name => nameof(Loader); @@ -116,6 +123,11 @@ public interface ILoader : IContract TargetPointer GetILHeader(ModuleHandle handle, uint token) => throw new NotImplementedException(); TargetPointer GetObjectHandle(TargetPointer loaderAllocatorPointer) => throw new NotImplementedException(); TargetPointer GetDynamicIL(ModuleHandle handle, uint token) => throw new NotImplementedException(); + + // Returns the first block of the loader heap linked list, or TargetPointer.Null if the heap has no blocks. + TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap) => throw new NotImplementedException(); + // Returns the data for the given loader heap block (address, size, and next block pointer). + LoaderHeapBlockData GetLoaderHeapBlockData(TargetPointer block) => throw new NotImplementedException(); IReadOnlyDictionary GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer) => throw new NotImplementedException(); DebuggerAssemblyControlFlags GetDebuggerInfoBits(ModuleHandle handle) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index 521d3b56783f2d..ab8798d9de22bb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -47,6 +47,8 @@ public enum DataType SystemDomain, Assembly, LoaderAllocator, + LoaderHeap, + LoaderHeapBlock, PEAssembly, AssemblyBinder, PEImage, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs index ab9d0dcaf08e2f..5558ff404d51c1 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs @@ -667,6 +667,22 @@ TargetPointer ILoader.GetDynamicIL(ModuleHandle handle, uint token) return shashContract.LookupSHash(dynamicILBlobTable.HashTable, token).EntryIL; } + TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap) + { + return _target.ProcessedData.GetOrAdd(loaderHeap).FirstBlock; + } + + LoaderHeapBlockData ILoader.GetLoaderHeapBlockData(TargetPointer block) + { + Data.LoaderHeapBlock blockData = _target.ProcessedData.GetOrAdd(block); + return new LoaderHeapBlockData + { + Address = blockData.VirtualAddress, + Size = blockData.VirtualSize, + NextBlock = blockData.Next, + }; + } + IReadOnlyDictionary ILoader.GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer) { Data.LoaderAllocator loaderAllocator = _target.ProcessedData.GetOrAdd(loaderAllocatorPointer); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/LoaderHeap.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/LoaderHeap.cs new file mode 100644 index 00000000000000..582f12b862490b --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/LoaderHeap.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class LoaderHeap : IData +{ + static LoaderHeap IData.Create(Target target, TargetPointer address) + => new LoaderHeap(target, address); + + public LoaderHeap(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.LoaderHeap); + + FirstBlock = target.ReadPointer(address + (ulong)type.Fields[nameof(FirstBlock)].Offset); + } + + public TargetPointer FirstBlock { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/LoaderHeapBlock.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/LoaderHeapBlock.cs new file mode 100644 index 00000000000000..31fe97adc3e49d --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/LoaderHeapBlock.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class LoaderHeapBlock : IData +{ + static LoaderHeapBlock IData.Create(Target target, TargetPointer address) + => new LoaderHeapBlock(target, address); + + public LoaderHeapBlock(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.LoaderHeapBlock); + + Next = target.ReadPointer(address + (ulong)type.Fields[nameof(Next)].Offset); + VirtualAddress = target.ReadPointer(address + (ulong)type.Fields[nameof(VirtualAddress)].Offset); + VirtualSize = target.ReadNUInt(address + (ulong)type.Fields[nameof(VirtualSize)].Offset); + } + + public TargetPointer Next { get; init; } + public TargetPointer VirtualAddress { get; init; } + public TargetNUInt VirtualSize { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs index 4b4c7d6c02f081..120ca75b6c328e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs @@ -672,7 +672,7 @@ public unsafe partial interface ISOSDacInterface [PreserveSig] int GetModuleData(ClrDataAddress moduleAddr, DacpModuleData* data); [PreserveSig] - int TraverseModuleMap(ModuleMapType mmt, ClrDataAddress moduleAddr, delegate* unmanaged[Stdcall] pCallback, void* token); + int TraverseModuleMap(ModuleMapType mmt, ClrDataAddress moduleAddr, delegate* unmanaged pCallback, void* token); [PreserveSig] int GetAssemblyModuleList(ClrDataAddress assembly, uint count, [In, Out, MarshalUsing(CountElementName = nameof(count))] ClrDataAddress[] modules, uint* pNeeded); [PreserveSig] @@ -814,7 +814,7 @@ public unsafe partial interface ISOSDacInterface // Heaps [PreserveSig] - int TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*VISITHEAP*/ void* pCallback); + int TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*VISITHEAP*/ delegate* unmanaged pCallback); [PreserveSig] int GetCodeHeapList(ClrDataAddress jitManager, uint count, [In, MarshalUsing(CountElementName = nameof(count)), Out] DacpJitCodeHeapInfo[]? codeHeaps, uint* pNeeded); [PreserveSig] @@ -840,7 +840,7 @@ public unsafe partial interface ISOSDacInterface [PreserveSig] int GetCCWInterfaces(ClrDataAddress ccw, uint count, [In, MarshalUsing(CountElementName = nameof(count)), Out] DacpCOMInterfacePointerData[]? interfaces, uint* pNeeded); [PreserveSig] - int TraverseRCWCleanupList(ClrDataAddress cleanupListPtr, /*VISITRCWFORCLEANUP*/ delegate* unmanaged[Stdcall] pCallback, void* token); + int TraverseRCWCleanupList(ClrDataAddress cleanupListPtr, /*VISITRCWFORCLEANUP*/ delegate* unmanaged pCallback, void* token); // GC Reference Functions @@ -1119,7 +1119,7 @@ public unsafe partial interface ISOSDacInterface12 public unsafe partial interface ISOSDacInterface13 { [PreserveSig] - int TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*LoaderHeapKind*/ int kind, /*VISITHEAP*/ delegate* unmanaged pCallback); + int TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*LoaderHeapKind*/ int kind, /*VISITHEAP*/ delegate* unmanaged< /*ClrDataAddress*/ ulong, nuint, Interop.BOOL, void> pCallback); [PreserveSig] int GetDomainLoaderAllocator(ClrDataAddress domainAddress, ClrDataAddress* pLoaderAllocator); [PreserveSig] diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index e1f2f405cb23cc..d67adff3ab3489 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -4648,11 +4648,97 @@ int ISOSDacInterface.TraverseEHInfo(ClrDataAddress ip, delegate* unmanaged LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.TraverseLoaderHeap(loaderHeapAddr, pCallback) : HResults.E_NOTIMPL; #if DEBUG - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })] + [ThreadStatic] + private static List<(ulong VirtualAddress, nuint VirtualSize)>? _debugTraverseLoaderHeapBlocks; + [ThreadStatic] + private static uint _debugTraverseLoaderDebugCount; + + private static List<(ulong VirtualAddress, nuint VirtualSize)> DebugTraverseLoaderHeapBlocks + => _debugTraverseLoaderHeapBlocks ??= new(); + + [UnmanagedCallersOnly] + private static void TraverseLoaderHeapDebugCallback(ulong virtualAddress, nuint virtualSize, Interop.BOOL _) + { + List<(ulong VirtualAddress, nuint VirtualSize)> expected = DebugTraverseLoaderHeapBlocks; + bool found = expected.Remove((virtualAddress, virtualSize)); + _debugTraverseLoaderDebugCount++; + Debug.Assert(found, $"Unexpected loader heap block: address={virtualAddress:x}, size={virtualSize:x}"); + } +#endif + + private int TraverseLoaderHeapCore(ClrDataAddress loaderHeapAddr, delegate* unmanaged pCallback) + { + int hr = HResults.S_OK; +#if DEBUG + DebugTraverseLoaderHeapBlocks.Clear(); + _debugTraverseLoaderDebugCount = 0; +#endif + try + { + if (loaderHeapAddr == 0 || 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 firstBlock = block; + int i = 0; + while (block != TargetPointer.Null && i++ < iterationMax) + { + Contracts.LoaderHeapBlockData blockData; + try + { + blockData = loader.GetLoaderHeapBlockData(block); + } + catch (VirtualReadException) + { + throw new NullReferenceException(); + } + pCallback(blockData.Address.Value, (nuint)blockData.Size.Value, block == firstBlock ? Interop.BOOL.TRUE : Interop.BOOL.FALSE); +#if DEBUG + DebugTraverseLoaderHeapBlocks.Add((blockData.Address.Value, (nuint)blockData.Size.Value)); +#endif + block = blockData.NextBlock; + if (block == firstBlock) + throw new NullReferenceException(); + } + if (i >= iterationMax) + hr = HResults.S_FALSE; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + return hr; + } + + int ISOSDacInterface.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, delegate* unmanaged pCallback) + { + int hr = TraverseLoaderHeapCore(loaderHeapAddr, pCallback); +#if DEBUG + if (_legacyImpl is not null) + { + int cdacCount = DebugTraverseLoaderHeapBlocks.Count; + delegate* unmanaged debugCallbackPtr = &TraverseLoaderHeapDebugCallback; + int hrLocal = _legacyImpl.TraverseLoaderHeap(loaderHeapAddr, 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; + } + +#if DEBUG + [UnmanagedCallersOnly] private static void TraverseModuleMapCallback(uint index, ulong moduleAddr, void* expectedElements) { var expectedElementsDict = (Dictionary)GCHandle.FromIntPtr((nint)expectedElements).Target!; @@ -4666,7 +4752,7 @@ private static void TraverseModuleMapCallback(uint index, ulong moduleAddr, void } } #endif - int ISOSDacInterface.TraverseModuleMap(ModuleMapType mmt, ClrDataAddress moduleAddr, delegate* unmanaged[Stdcall] pCallback, void* token) + int ISOSDacInterface.TraverseModuleMap(ModuleMapType mmt, ClrDataAddress moduleAddr, delegate* unmanaged pCallback, void* token) { int hr = HResults.S_OK; IEnumerable<(TargetPointer Address, uint Index)> elements = Enumerable.Empty<(TargetPointer, uint)>(); @@ -4706,7 +4792,7 @@ int ISOSDacInterface.TraverseModuleMap(ModuleMapType mmt, ClrDataAddress moduleA Dictionary expectedElements = elements.ToDictionary(tuple => tuple.Address.ToClrDataAddress(_target).Value, tuple => tuple.Index); expectedElements.Add(default, 0); void* tokenDebug = GCHandle.ToIntPtr(GCHandle.Alloc(expectedElements)).ToPointer(); - delegate* unmanaged[Stdcall] callbackDebugPtr = &TraverseModuleMapCallback; + delegate* unmanaged callbackDebugPtr = &TraverseModuleMapCallback; int hrLocal = _legacyImpl.TraverseModuleMap(mmt, moduleAddr, callbackDebugPtr, tokenDebug); Debug.ValidateHResult(hr, hrLocal); @@ -4717,7 +4803,7 @@ int ISOSDacInterface.TraverseModuleMap(ModuleMapType mmt, ClrDataAddress moduleA return hr; } #if DEBUG - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })] + [UnmanagedCallersOnly] private static Interop.BOOL TraverseRCWCleanupListCallback(ulong rcwAddr, ulong ctx, ulong staThread, Interop.BOOL isFreeThreaded, void* expectedElements) { var expectedElementsDict = (Dictionary)GCHandle.FromIntPtr((nint)expectedElements).Target!; @@ -4732,7 +4818,7 @@ private static Interop.BOOL TraverseRCWCleanupListCallback(ulong rcwAddr, ulong return Interop.BOOL.TRUE; } #endif - int ISOSDacInterface.TraverseRCWCleanupList(ClrDataAddress cleanupListPtr, delegate* unmanaged[Stdcall] pCallback, void* token) + int ISOSDacInterface.TraverseRCWCleanupList(ClrDataAddress cleanupListPtr, delegate* unmanaged pCallback, void* token) { int hr = HResults.S_OK; IEnumerable cleanupInfos = Enumerable.Empty(); @@ -4766,7 +4852,7 @@ int ISOSDacInterface.TraverseRCWCleanupList(ClrDataAddress cleanupListPtr, deleg expectedElements.Add(default, 0); GCHandle expectedElementsHandle = GCHandle.Alloc(expectedElements); void* tokenDebug = GCHandle.ToIntPtr(expectedElementsHandle).ToPointer(); - delegate* unmanaged[Stdcall] callbackDebugPtr = &TraverseRCWCleanupListCallback; + delegate* unmanaged callbackDebugPtr = &TraverseRCWCleanupListCallback; int hrLocal = _legacyImpl.TraverseRCWCleanupList(cleanupListPtr, callbackDebugPtr, tokenDebug); Debug.ValidateHResult(hr, hrLocal); @@ -6118,8 +6204,28 @@ int ISOSDacInterface12.GetGlobalAllocationContext(ClrDataAddress* allocPtr, ClrD #endregion ISOSDacInterface12 #region ISOSDacInterface13 - int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*LoaderHeapKind*/ int kind, /*VISITHEAP*/ delegate* unmanaged pCallback) - => LegacyFallbackHelper.CanFallback() && _legacyImpl13 is not null ? _legacyImpl13.TraverseLoaderHeap(loaderHeapAddr, kind, pCallback) : HResults.E_NOTIMPL; + + int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*LoaderHeapKind*/ int kind, /*VISITHEAP*/ delegate* unmanaged pCallback) + { + int hr = TraverseLoaderHeapCore(loaderHeapAddr, pCallback); +#if DEBUG + if (_legacyImpl13 is not null) + { + int cdacCount = DebugTraverseLoaderHeapBlocks.Count; + delegate* unmanaged debugCallbackPtr = &TraverseLoaderHeapDebugCallback; + int hrLocal = _legacyImpl13.TraverseLoaderHeap(loaderHeapAddr, kind, 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; + } int ISOSDacInterface13.GetDomainLoaderAllocator(ClrDataAddress domainAddress, ClrDataAddress* pLoaderAllocator) { int hr = HResults.S_OK; diff --git a/src/native/managed/cdac/tests/LoaderHeapTests.cs b/src/native/managed/cdac/tests/LoaderHeapTests.cs new file mode 100644 index 00000000000000..4db75ffd95bce3 --- /dev/null +++ b/src/native/managed/cdac/tests/LoaderHeapTests.cs @@ -0,0 +1,108 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.Tests; + +public class LoaderHeapTests +{ + private static Dictionary CreateContractTypes(MockLoaderBuilder loader) + => new() + { + [DataType.Module] = TargetTestHelpers.CreateTypeInfo(loader.ModuleLayout), + [DataType.Assembly] = TargetTestHelpers.CreateTypeInfo(loader.AssemblyLayout), + [DataType.EEConfig] = TargetTestHelpers.CreateTypeInfo(loader.EEConfigLayout), + [DataType.LoaderHeap] = TargetTestHelpers.CreateTypeInfo(loader.LoaderHeapLayout), + [DataType.LoaderHeapBlock] = TargetTestHelpers.CreateTypeInfo(loader.LoaderHeapBlockLayout), + }; + + private static ILoader CreateLoaderContract(MockTarget.Architecture arch, Action configure) + { + var targetBuilder = new TestPlaceholderTarget.Builder(arch); + MockLoaderBuilder loader = new(targetBuilder.MemoryBuilder); + + configure(loader); + + var target = targetBuilder + .AddTypes(CreateContractTypes(loader)) + .AddContract(version: "c1") + .Build(); + return target.Contracts.Loader; + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void EmptyLoaderHeap(MockTarget.Architecture arch) + { + TargetPointer heapAddr = TargetPointer.Null; + + ILoader loader = CreateLoaderContract(arch, loaderBuilder => + { + MockLoaderHeap heap = loaderBuilder.AddLoaderHeap(firstBlockAddress: 0); + heapAddr = new TargetPointer(heap.Address); + }); + + TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapAddr); + Assert.Equal(TargetPointer.Null, firstBlock); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SingleBlockLoaderHeap(MockTarget.Architecture arch) + { + TargetPointer heapAddr = TargetPointer.Null; + const ulong virtualAddress = 0x1234_0000UL; + const ulong virtualSize = 0x1000UL; + + ILoader loader = CreateLoaderContract(arch, loaderBuilder => + { + MockLoaderHeapBlock block = loaderBuilder.AddLoaderHeapBlock(virtualAddress, virtualSize); + MockLoaderHeap heap = loaderBuilder.AddLoaderHeap(firstBlockAddress: block.Address); + heapAddr = new TargetPointer(heap.Address); + }); + + TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapAddr); + Assert.NotEqual(TargetPointer.Null, firstBlock); + + LoaderHeapBlockData blockData = loader.GetLoaderHeapBlockData(firstBlock); + Assert.Equal(virtualAddress, blockData.Address.Value); + Assert.Equal(virtualSize, blockData.Size.Value); + Assert.Equal(TargetPointer.Null, blockData.NextBlock); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void MultipleBlockLoaderHeap(MockTarget.Architecture arch) + { + TargetPointer heapAddr = TargetPointer.Null; + ulong[] virtualAddresses = [0x1000_0000UL, 0x2000_0000UL]; + ulong[] virtualSizes = [0x8000UL, 0x10000UL]; + + ILoader loader = CreateLoaderContract(arch, loaderBuilder => + { + // Build chain: heap -> block1 -> block2 -> null + MockLoaderHeapBlock block2 = loaderBuilder.AddLoaderHeapBlock(virtualAddresses[1], virtualSizes[1]); + MockLoaderHeapBlock block1 = loaderBuilder.AddLoaderHeapBlock(virtualAddresses[0], virtualSizes[0], nextBlockAddress: block2.Address); + MockLoaderHeap heap = loaderBuilder.AddLoaderHeap(firstBlockAddress: block1.Address); + heapAddr = new TargetPointer(heap.Address); + }); + + List<(ulong Address, ulong Size)> blocks = []; + TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr); + while (block != TargetPointer.Null) + { + LoaderHeapBlockData blockData = loader.GetLoaderHeapBlockData(block); + blocks.Add((blockData.Address.Value, blockData.Size.Value)); + block = blockData.NextBlock; + } + + Assert.Equal(2, blocks.Count); + Assert.Equal((virtualAddresses[0], virtualSizes[0]), blocks[0]); + Assert.Equal((virtualAddresses[1], virtualSizes[1]), blocks[1]); + } +} + diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs index b1ad52a8cd197a..45bfe9500f4b0d 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs @@ -7,6 +7,54 @@ namespace Microsoft.Diagnostics.DataContractReader.Tests; +internal sealed class MockLoaderHeap : TypedView +{ + private const string FirstBlockFieldName = "FirstBlock"; + + public static Layout CreateLayout(MockTarget.Architecture architecture) + => new SequentialLayoutBuilder("LoaderHeap", architecture) + .AddPointerField(FirstBlockFieldName) + .Build(); + + public ulong FirstBlock + { + get => ReadPointerField(FirstBlockFieldName); + set => WritePointerField(FirstBlockFieldName, value); + } +} + +internal sealed class MockLoaderHeapBlock : TypedView +{ + private const string NextFieldName = "Next"; + private const string VirtualAddressFieldName = "VirtualAddress"; + private const string VirtualSizeFieldName = "VirtualSize"; + + public static Layout CreateLayout(MockTarget.Architecture architecture) + => new SequentialLayoutBuilder("LoaderHeapBlock", architecture) + .AddPointerField(NextFieldName) + .AddPointerField(VirtualAddressFieldName) + .AddNUIntField(VirtualSizeFieldName) + .Build(); + + public ulong Next + { + get => ReadPointerField(NextFieldName); + set => WritePointerField(NextFieldName, value); + } + + public ulong VirtualAddress + { + get => ReadPointerField(VirtualAddressFieldName); + set => WritePointerField(VirtualAddressFieldName, value); + } + + public ulong VirtualSize + { + get => ReadPointerField(VirtualSizeFieldName); + set => WritePointerField(VirtualSizeFieldName, value); + } +} + internal sealed class MockLoaderModule : TypedView { private const string AssemblyFieldName = "Assembly"; @@ -144,6 +192,8 @@ internal sealed class MockLoaderBuilder internal Layout ModuleLayout { get; } internal Layout AssemblyLayout { get; } internal Layout EEConfigLayout { get; } + internal Layout LoaderHeapLayout { get; } + internal Layout LoaderHeapBlockLayout { get; } private readonly MockMemorySpace.BumpAllocator _allocator; @@ -162,6 +212,24 @@ public MockLoaderBuilder(MockMemorySpace.Builder builder, (ulong Start, ulong En ModuleLayout = MockLoaderModule.CreateLayout(builder.TargetTestHelpers.Arch); AssemblyLayout = MockLoaderAssembly.CreateLayout(builder.TargetTestHelpers.Arch); EEConfigLayout = MockEEConfig.CreateLayout(builder.TargetTestHelpers.Arch); + LoaderHeapLayout = MockLoaderHeap.CreateLayout(builder.TargetTestHelpers.Arch); + LoaderHeapBlockLayout = MockLoaderHeapBlock.CreateLayout(builder.TargetTestHelpers.Arch); + } + + internal MockLoaderHeap AddLoaderHeap(ulong firstBlockAddress = 0) + { + MockLoaderHeap heap = LoaderHeapLayout.Create(_allocator.Allocate((ulong)LoaderHeapLayout.Size, "LoaderHeap")); + heap.FirstBlock = firstBlockAddress; + return heap; + } + + internal MockLoaderHeapBlock AddLoaderHeapBlock(ulong virtualAddress, ulong virtualSize, ulong nextBlockAddress = 0) + { + MockLoaderHeapBlock block = LoaderHeapBlockLayout.Create(_allocator.Allocate((ulong)LoaderHeapBlockLayout.Size, "LoaderHeapBlock")); + block.VirtualAddress = virtualAddress; + block.VirtualSize = virtualSize; + block.Next = nextBlockAddress; + return block; } internal MockLoaderModule AddModule(