diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 4fd867a1024477..0be000bb750063 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -86,6 +86,7 @@ TargetPointer GetStubHeap(TargetPointer loaderAllocatorPointer); TargetPointer GetObjectHandle(TargetPointer loaderAllocatorPointer); TargetPointer GetILHeader(ModuleHandle handle, uint token); TargetPointer GetDynamicIL(ModuleHandle handle, uint token); +IReadOnlyDictionary GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer); ``` ## Version 1 @@ -140,7 +141,15 @@ TargetPointer GetDynamicIL(ModuleHandle handle, uint token); | `LoaderAllocator` | `HighFrequencyHeap` | High-frequency heap of LoaderAllocator | | `LoaderAllocator` | `LowFrequencyHeap` | Low-frequency heap of LoaderAllocator | | `LoaderAllocator` | `StubHeap` | Stub heap of LoaderAllocator | +| `LoaderAllocator` | `StaticsHeap` | Statics heap of LoaderAllocator | +| `LoaderAllocator` | `ExecutableHeap` | Executable heap of LoaderAllocator | +| `LoaderAllocator` | `FixupPrecodeHeap` | FixupPrecode heap of LoaderAllocator (optional, present when `HAS_FIXUP_PRECODE`) | +| `LoaderAllocator` | `NewStubPrecodeHeap` | NewStubPrecode heap of LoaderAllocator (optional, present when not `FEATURE_PORTABLE_ENTRYPOINTS`) | +| `LoaderAllocator` | `DynamicHelpersStubHeap` | DynamicHelpers stub heap of LoaderAllocator (optional, present when `FEATURE_READYTORUN && FEATURE_STUBPRECODE_DYNAMIC_HELPERS`) | +| `LoaderAllocator` | `VirtualCallStubManager` | Pointer to the VirtualCallStubManager of LoaderAllocator | | `LoaderAllocator` | `ObjectHandle` | object handle of LoaderAllocator | +| `VirtualCallStubManager` | `IndcellHeap` | Indirection cell heap of VirtualCallStubManager | +| `VirtualCallStubManager` | `CacheEntryHeap` | Cache entry heap of VirtualCallStubManager (optional, present when `FEATURE_VIRTUAL_STUB_DISPATCH`) | | `ArrayListBase` | `Count` | Total number of elements in the ArrayListBase | | `ArrayListBase` | `FirstBlock` | First ArrayListBlock | | `ArrayListBlock` | `Next` | Next ArrayListBlock in chain | @@ -650,6 +659,44 @@ TargetPointer GetObjectHandle(TargetPointer loaderAllocatorPointer) return target.ReadPointer(loaderAllocatorPointer + /* LoaderAllocator::ObjectHandle offset */); } +IReadOnlyDictionary GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer) +{ + // Read LoaderAllocator data + LoaderAllocator la = // read LoaderAllocator object at loaderAllocatorPointer + + // Always-present heaps + Dictionary heaps = { + ["LowFrequencyHeap"] = la.LowFrequencyHeap, + ["HighFrequencyHeap"] = la.HighFrequencyHeap, + ["StaticsHeap"] = la.StaticsHeap, + ["StubHeap"] = la.StubHeap, + ["ExecutableHeap"] = la.ExecutableHeap, + }; + + // Feature-conditional heaps: only included when the data descriptor field exists + if (LoaderAllocator type has "FixupPrecodeHeap" field) + heaps["FixupPrecodeHeap"] = la.FixupPrecodeHeap; + + if (LoaderAllocator type has "NewStubPrecodeHeap" field) + heaps["NewStubPrecodeHeap"] = la.NewStubPrecodeHeap; + + if (LoaderAllocator type has "DynamicHelpersStubHeap" field) + heaps["DynamicHelpersStubHeap"] = la.DynamicHelpersStubHeap; + + // VirtualCallStubManager heaps: only included when VirtualCallStubManager is non-null + if (la.VirtualCallStubManager != null) + { + VirtualCallStubManager vcsMgr = // read VirtualCallStubManager object at la.VirtualCallStubManager + + heaps["IndcellHeap"] = vcsMgr.IndcellHeap; + + if (VirtualCallStubManager type has "CacheEntryHeap" field) + heaps["CacheEntryHeap"] = vcsMgr.CacheEntryHeap; + } + + return heaps; +} + private sealed class DynamicILBlobTraits : ITraits { public uint GetKey(DynamicILBlobEntry entry) => entry.EntryMethodToken; diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.h b/src/coreclr/vm/datadescriptor/datadescriptor.h index 651975ba0cc4c7..6e5aed62529741 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.h +++ b/src/coreclr/vm/datadescriptor/datadescriptor.h @@ -20,6 +20,7 @@ #include "configure.h" +#include "virtualcallstub.h" #include "../debug/ee/debugger.h" #include "patchpointinfo.h" diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 2d2582a2df02e3..8c2a06b0bd1167 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -225,10 +225,30 @@ CDAC_TYPE_INDETERMINATE(LoaderAllocator) CDAC_TYPE_FIELD(LoaderAllocator, /*uint32*/, ReferenceCount, cdac_data::ReferenceCount) CDAC_TYPE_FIELD(LoaderAllocator, /*pointer*/, HighFrequencyHeap, cdac_data::HighFrequencyHeap) CDAC_TYPE_FIELD(LoaderAllocator, /*pointer*/, LowFrequencyHeap, cdac_data::LowFrequencyHeap) +CDAC_TYPE_FIELD(LoaderAllocator, /*pointer*/, StaticsHeap, cdac_data::StaticsHeap) CDAC_TYPE_FIELD(LoaderAllocator, /*pointer*/, StubHeap, cdac_data::StubHeap) +CDAC_TYPE_FIELD(LoaderAllocator, /*pointer*/, ExecutableHeap, cdac_data::ExecutableHeap) +#ifdef HAS_FIXUP_PRECODE +CDAC_TYPE_FIELD(LoaderAllocator, /*pointer*/, FixupPrecodeHeap, cdac_data::FixupPrecodeHeap) +#endif // HAS_FIXUP_PRECODE +#ifndef FEATURE_PORTABLE_ENTRYPOINTS +CDAC_TYPE_FIELD(LoaderAllocator, /*pointer*/, NewStubPrecodeHeap, cdac_data::NewStubPrecodeHeap) +#endif // !FEATURE_PORTABLE_ENTRYPOINTS +#if defined(FEATURE_READYTORUN) && defined(FEATURE_STUBPRECODE_DYNAMIC_HELPERS) +CDAC_TYPE_FIELD(LoaderAllocator, /*pointer*/, DynamicHelpersStubHeap, cdac_data::DynamicHelpersStubHeap) +#endif // defined(FEATURE_READYTORUN) && defined(FEATURE_STUBPRECODE_DYNAMIC_HELPERS) +CDAC_TYPE_FIELD(LoaderAllocator, /*pointer*/, VirtualCallStubManager, cdac_data::VirtualCallStubManager) CDAC_TYPE_FIELD(LoaderAllocator, /*pointer*/, ObjectHandle, cdac_data::ObjectHandle) CDAC_TYPE_END(LoaderAllocator) +CDAC_TYPE_BEGIN(VirtualCallStubManager) +CDAC_TYPE_INDETERMINATE(VirtualCallStubManager) +CDAC_TYPE_FIELD(VirtualCallStubManager, /*pointer*/, IndcellHeap, cdac_data::IndcellHeap) +#ifdef FEATURE_VIRTUAL_STUB_DISPATCH +CDAC_TYPE_FIELD(VirtualCallStubManager, /*pointer*/, CacheEntryHeap, cdac_data::CacheEntryHeap) +#endif // FEATURE_VIRTUAL_STUB_DISPATCH +CDAC_TYPE_END(VirtualCallStubManager) + CDAC_TYPE_BEGIN(PEAssembly) CDAC_TYPE_INDETERMINATE(PEAssembly) CDAC_TYPE_FIELD(PEAssembly, /*pointer*/, PEImage, cdac_data::PEImage) diff --git a/src/coreclr/vm/loaderallocator.hpp b/src/coreclr/vm/loaderallocator.hpp index 884e5c54daec7c..ae1d8f4b40cfc1 100644 --- a/src/coreclr/vm/loaderallocator.hpp +++ b/src/coreclr/vm/loaderallocator.hpp @@ -917,7 +917,19 @@ struct cdac_data static constexpr size_t ReferenceCount = offsetof(LoaderAllocator, m_cReferences); static constexpr size_t HighFrequencyHeap = offsetof(LoaderAllocator, m_pHighFrequencyHeap); static constexpr size_t LowFrequencyHeap = offsetof(LoaderAllocator, m_pLowFrequencyHeap); + static constexpr size_t StaticsHeap = offsetof(LoaderAllocator, m_pStaticsHeap); static constexpr size_t StubHeap = offsetof(LoaderAllocator, m_pStubHeap); + static constexpr size_t ExecutableHeap = offsetof(LoaderAllocator, m_pExecutableHeap); +#ifdef HAS_FIXUP_PRECODE + static constexpr size_t FixupPrecodeHeap = offsetof(LoaderAllocator, m_pFixupPrecodeHeap); +#endif // HAS_FIXUP_PRECODE +#ifndef FEATURE_PORTABLE_ENTRYPOINTS + static constexpr size_t NewStubPrecodeHeap = offsetof(LoaderAllocator, m_pNewStubPrecodeHeap); +#endif // !FEATURE_PORTABLE_ENTRYPOINTS +#if defined(FEATURE_READYTORUN) && defined(FEATURE_STUBPRECODE_DYNAMIC_HELPERS) + static constexpr size_t DynamicHelpersStubHeap = offsetof(LoaderAllocator, m_pDynamicHelpersStubHeap); +#endif // defined(FEATURE_READYTORUN) && defined(FEATURE_STUBPRECODE_DYNAMIC_HELPERS) + static constexpr size_t VirtualCallStubManager = offsetof(LoaderAllocator, m_pVirtualCallStubManager); static constexpr size_t ObjectHandle = offsetof(LoaderAllocator, m_hLoaderAllocatorObjectHandle); }; diff --git a/src/coreclr/vm/virtualcallstub.h b/src/coreclr/vm/virtualcallstub.h index 243fb311ed1278..b3b8ad261ba7ba 100644 --- a/src/coreclr/vm/virtualcallstub.h +++ b/src/coreclr/vm/virtualcallstub.h @@ -744,6 +744,17 @@ class VirtualCallStubManager : public StubManager return W("Unexpected. RangeSectionStubManager should report the name"); } #endif + + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t IndcellHeap = offsetof(VirtualCallStubManager, indcell_heap); +#ifdef FEATURE_VIRTUAL_STUB_DISPATCH + static constexpr size_t CacheEntryHeap = offsetof(VirtualCallStubManager, cache_entry_heap); +#endif // FEATURE_VIRTUAL_STUB_DISPATCH }; /******************************************************************************************************** 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 5320035f899108..6945e1377bc810 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 @@ -94,6 +94,7 @@ 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(); + IReadOnlyDictionary GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer) => throw new NotImplementedException(); } public readonly struct Loader : ILoader 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 74e4f327f5e073..c9f43e9662be5a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -123,6 +123,7 @@ public enum DataType DynamicILBlobTable, EEJitManager, PatchpointInfo, + VirtualCallStubManager, PortableEntryPoint, TransitionBlock, 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 7e5329c444ea79..6f789c36dc3986 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 @@ -552,4 +552,41 @@ TargetPointer ILoader.GetDynamicIL(ModuleHandle handle, uint token) ISHash shashContract = _target.Contracts.SHash; return shashContract.LookupSHash(dynamicILBlobTable.HashTable, token).EntryIL; } + + IReadOnlyDictionary ILoader.GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer) + { + Data.LoaderAllocator loaderAllocator = _target.ProcessedData.GetOrAdd(loaderAllocatorPointer); + Target.TypeInfo laType = _target.GetTypeInfo(DataType.LoaderAllocator); + + Dictionary heaps = new() + { + [nameof(Data.LoaderAllocator.LowFrequencyHeap)] = loaderAllocator.LowFrequencyHeap, + [nameof(Data.LoaderAllocator.HighFrequencyHeap)] = loaderAllocator.HighFrequencyHeap, + [nameof(Data.LoaderAllocator.StaticsHeap)] = loaderAllocator.StaticsHeap, + [nameof(Data.LoaderAllocator.StubHeap)] = loaderAllocator.StubHeap, + [nameof(Data.LoaderAllocator.ExecutableHeap)] = loaderAllocator.ExecutableHeap, + }; + + if (laType.Fields.ContainsKey(nameof(Data.LoaderAllocator.FixupPrecodeHeap))) + heaps[nameof(Data.LoaderAllocator.FixupPrecodeHeap)] = loaderAllocator.FixupPrecodeHeap!.Value; + + if (laType.Fields.ContainsKey(nameof(Data.LoaderAllocator.NewStubPrecodeHeap))) + heaps[nameof(Data.LoaderAllocator.NewStubPrecodeHeap)] = loaderAllocator.NewStubPrecodeHeap!.Value; + + if (laType.Fields.ContainsKey(nameof(Data.LoaderAllocator.DynamicHelpersStubHeap))) + heaps[nameof(Data.LoaderAllocator.DynamicHelpersStubHeap)] = loaderAllocator.DynamicHelpersStubHeap!.Value; + + if (loaderAllocator.VirtualCallStubManager != TargetPointer.Null) + { + Data.VirtualCallStubManager vcsMgr = _target.ProcessedData.GetOrAdd(loaderAllocator.VirtualCallStubManager); + Target.TypeInfo vcsType = _target.GetTypeInfo(DataType.VirtualCallStubManager); + + heaps[nameof(Data.VirtualCallStubManager.IndcellHeap)] = vcsMgr.IndcellHeap; + + if (vcsType.Fields.ContainsKey(nameof(Data.VirtualCallStubManager.CacheEntryHeap))) + heaps[nameof(Data.VirtualCallStubManager.CacheEntryHeap)] = vcsMgr.CacheEntryHeap!.Value; + } + + return heaps; + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/LoaderAllocator.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/LoaderAllocator.cs index c00af4c5264f06..3269079ff305e3 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/LoaderAllocator.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/LoaderAllocator.cs @@ -15,7 +15,19 @@ public LoaderAllocator(Target target, TargetPointer address) ReferenceCount = target.Read(address + (ulong)type.Fields[nameof(ReferenceCount)].Offset); HighFrequencyHeap = target.ReadPointer(address + (ulong)type.Fields[nameof(HighFrequencyHeap)].Offset); LowFrequencyHeap = target.ReadPointer(address + (ulong)type.Fields[nameof(LowFrequencyHeap)].Offset); + StaticsHeap = target.ReadPointer(address + (ulong)type.Fields[nameof(StaticsHeap)].Offset); StubHeap = target.ReadPointer(address + (ulong)type.Fields[nameof(StubHeap)].Offset); + ExecutableHeap = target.ReadPointer(address + (ulong)type.Fields[nameof(ExecutableHeap)].Offset); + + if (type.Fields.ContainsKey(nameof(FixupPrecodeHeap))) + FixupPrecodeHeap = target.ReadPointer(address + (ulong)type.Fields[nameof(FixupPrecodeHeap)].Offset); + if (type.Fields.ContainsKey(nameof(NewStubPrecodeHeap))) + NewStubPrecodeHeap = target.ReadPointer(address + (ulong)type.Fields[nameof(NewStubPrecodeHeap)].Offset); + if (type.Fields.ContainsKey(nameof(DynamicHelpersStubHeap))) + DynamicHelpersStubHeap = target.ReadPointer(address + (ulong)type.Fields[nameof(DynamicHelpersStubHeap)].Offset); + + VirtualCallStubManager = target.ReadPointer(address + (ulong)type.Fields[nameof(VirtualCallStubManager)].Offset); + ObjectHandle = target.ProcessedData.GetOrAdd( target.ReadPointer(address + (ulong)type.Fields[nameof(ObjectHandle)].Offset)); } @@ -23,7 +35,13 @@ public LoaderAllocator(Target target, TargetPointer address) public uint ReferenceCount { get; init; } public TargetPointer HighFrequencyHeap { get; init; } public TargetPointer LowFrequencyHeap { get; init; } + public TargetPointer StaticsHeap { get; init; } public TargetPointer StubHeap { get; init; } + public TargetPointer ExecutableHeap { get; init; } + public TargetPointer? FixupPrecodeHeap { get; init; } + public TargetPointer? NewStubPrecodeHeap { get; init; } + public TargetPointer? DynamicHelpersStubHeap { get; init; } + public TargetPointer VirtualCallStubManager { get; init; } public ObjectHandle ObjectHandle { get; init; } public bool IsAlive => ReferenceCount != 0; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/VirtualCallStubManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/VirtualCallStubManager.cs new file mode 100644 index 00000000000000..3db6402ad829fc --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/VirtualCallStubManager.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 VirtualCallStubManager : IData +{ + static VirtualCallStubManager IData.Create(Target target, TargetPointer address) + => new VirtualCallStubManager(target, address); + + public VirtualCallStubManager(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.VirtualCallStubManager); + + IndcellHeap = target.ReadPointer(address + (ulong)type.Fields[nameof(IndcellHeap)].Offset); + + if (type.Fields.ContainsKey(nameof(CacheEntryHeap))) + CacheEntryHeap = target.ReadPointer(address + (ulong)type.Fields[nameof(CacheEntryHeap)].Offset); + } + + public TargetPointer IndcellHeap { get; init; } + public TargetPointer? CacheEntryHeap { get; init; } +} 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 c665f8f717dad8..ff2bae3e48eb92 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -4459,10 +4459,136 @@ int ISOSDacInterface13.GetDomainLoaderAllocator(ClrDataAddress domainAddress, Cl #endif return hr; } + + private nint[]? _loaderAllocatorHeapNamePtrs; + + private nint[] GetOrInitializeHeapNamePtrs() + { + if (_loaderAllocatorHeapNamePtrs is not null) + return _loaderAllocatorHeapNamePtrs; + + Contracts.ILoader contract = _target.Contracts.Loader; + TargetPointer globalLoaderAllocator = contract.GetGlobalLoaderAllocator(); + IReadOnlyDictionary heaps = contract.GetLoaderAllocatorHeaps(globalLoaderAllocator); + nint[] ptrs = new nint[heaps.Count]; + int i = 0; + foreach (string name in heaps.Keys) + { + ptrs[i++] = Marshal.StringToHGlobalAnsi(name); + } + _loaderAllocatorHeapNamePtrs = ptrs; + return ptrs; + } + int ISOSDacInterface13.GetLoaderAllocatorHeapNames(int count, char** ppNames, int* pNeeded) - => _legacyImpl13 is not null ? _legacyImpl13.GetLoaderAllocatorHeapNames(count, ppNames, pNeeded) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + try + { + nint[] heapNamePtrs = GetOrInitializeHeapNamePtrs(); + int loaderHeapCount = heapNamePtrs.Length; + if (pNeeded != null) + *pNeeded = loaderHeapCount; + + if (ppNames != null) + { + for (int i = 0; i < Math.Min(count, loaderHeapCount); i++) + ppNames[i] = (char*)heapNamePtrs[i]; + } + + if (count < loaderHeapCount) + hr = HResults.S_FALSE; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyImpl13 is not null) + { + int pNeededLocal; + _legacyImpl13.GetLoaderAllocatorHeapNames(0, null, &pNeededLocal); + Debug.Assert(pNeeded is null || *pNeeded == pNeededLocal, $"cDAC needed: {(pNeeded != null ? *pNeeded : -1)}, DAC needed: {pNeededLocal}"); + if (hr >= 0 && ppNames != null && pNeededLocal > 0) + { + char** ppNamesLocal = stackalloc char*[pNeededLocal]; + _legacyImpl13.GetLoaderAllocatorHeapNames(pNeededLocal, ppNamesLocal, null); + int compareCount = Math.Min(count, pNeededLocal); + for (int i = 0; i < compareCount; i++) + { + string cdacName = Marshal.PtrToStringAnsi((nint)ppNames[i])!; + string dacName = Marshal.PtrToStringAnsi((nint)ppNamesLocal[i])!; + Debug.Assert(cdacName == dacName, $"HeapName[{i}] - cDAC: {cdacName}, DAC: {dacName}"); + } + } + } +#endif + return hr; + } int ISOSDacInterface13.GetLoaderAllocatorHeaps(ClrDataAddress loaderAllocator, int count, ClrDataAddress* pLoaderHeaps, /*LoaderHeapKind*/ int* pKinds, int* pNeeded) - => _legacyImpl13 is not null ? _legacyImpl13.GetLoaderAllocatorHeaps(loaderAllocator, count, pLoaderHeaps, pKinds, pNeeded) : HResults.E_NOTIMPL; + { + if (loaderAllocator == 0) + return HResults.E_INVALIDARG; + + int hr = HResults.S_OK; + try + { + Contracts.ILoader contract = _target.Contracts.Loader; + IReadOnlyDictionary heaps = contract.GetLoaderAllocatorHeaps(loaderAllocator.ToTargetPointer(_target)); + int loaderHeapCount = heaps.Count; + + if (pNeeded != null) + *pNeeded = loaderHeapCount; + + if (pLoaderHeaps != null) + { + if (count < loaderHeapCount) + { + hr = HResults.E_INVALIDARG; + } + else + { + int i = 0; + foreach (TargetPointer heap in heaps.Values) + { + pLoaderHeaps[i] = heap.ToClrDataAddress(_target); + pKinds[i] = 0; // LoaderHeapKindNormal + i++; + } + } + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyImpl13 is not null) + { + int pNeededLocal; + int hrLocal = _legacyImpl13.GetLoaderAllocatorHeaps(loaderAllocator, 0, null, null, &pNeededLocal); + Debug.Assert(pNeeded is null || *pNeeded == pNeededLocal, $"cDAC needed: {(pNeeded != null ? *pNeeded : -1)}, DAC needed: {pNeededLocal}"); + if (hr >= 0 && pLoaderHeaps != null && pNeededLocal > 0) + { + ClrDataAddress* pLoaderHeapsLocal = stackalloc ClrDataAddress[pNeededLocal]; + int* pKindsLocal = stackalloc int[pNeededLocal]; + hrLocal = _legacyImpl13.GetLoaderAllocatorHeaps(loaderAllocator, pNeededLocal, pLoaderHeapsLocal, pKindsLocal, null); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + if (hrLocal >= 0) + { + for (int i = 0; i < pNeededLocal; i++) + { + Debug.Assert(pLoaderHeaps[i] == pLoaderHeapsLocal[i], $"Heap[{i}] - cDAC: {pLoaderHeaps[i]:x}, DAC: {pLoaderHeapsLocal[i]:x}"); + Debug.Assert(pKinds[i] == pKindsLocal[i], $"Kind[{i}] - cDAC: {pKinds[i]}, DAC: {pKindsLocal[i]}"); + } + } + } + } +#endif + return hr; + } int ISOSDacInterface13.GetHandleTableMemoryRegions(/*ISOSMemoryEnum*/ void** ppEnum) => _legacyImpl13 is not null ? _legacyImpl13.GetHandleTableMemoryRegions(ppEnum) : HResults.E_NOTIMPL; int ISOSDacInterface13.GetGCBookkeepingMemoryRegions(/*ISOSMemoryEnum*/ void** ppEnum) diff --git a/src/native/managed/cdac/tests/LoaderTests.cs b/src/native/managed/cdac/tests/LoaderTests.cs index 0cbb93e9481c77..9e987d560e0e08 100644 --- a/src/native/managed/cdac/tests/LoaderTests.cs +++ b/src/native/managed/cdac/tests/LoaderTests.cs @@ -2,8 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Runtime.InteropServices; using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Legacy; using Moq; using Xunit; @@ -80,4 +84,158 @@ public void GetFileName(MockTarget.Architecture arch) Assert.Equal(string.Empty, actual); } } + + private static readonly Dictionary MockHeapDictionary = new() + { + ["LowFrequencyHeap"] = new(0x1000), + ["HighFrequencyHeap"] = new(0x2000), + ["StaticsHeap"] = new(0x3000), + ["StubHeap"] = new(0x4000), + ["ExecutableHeap"] = new(0x5000), + ["FixupPrecodeHeap"] = new(0x6000), + ["NewStubPrecodeHeap"] = new(0x7000), + ["IndcellHeap"] = new(0x8000), + ["CacheEntryHeap"] = new(0x9000), + }; + + private static SOSDacImpl CreateSOSDacImplForHeapTests(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockLoader loader = new(builder); + var target = new TestPlaceholderTarget(arch, builder.GetMemoryContext().ReadFromTarget, loader.Types); + target.SetContracts(Mock.Of( + c => c.Loader == Mock.Of( + l => l.GetLoaderAllocatorHeaps(It.IsAny()) == (IReadOnlyDictionary)MockHeapDictionary + && l.GetGlobalLoaderAllocator() == new TargetPointer(0x100)))); + return new SOSDacImpl(target, null); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetLoaderAllocatorHeapNames_GetCount(MockTarget.Architecture arch) + { + ISOSDacInterface13 impl = CreateSOSDacImplForHeapTests(arch); + + int needed; + int hr = impl.GetLoaderAllocatorHeapNames(0, null, &needed); + + Assert.Equal(HResults.S_FALSE, hr); + Assert.Equal(MockHeapDictionary.Count, needed); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetLoaderAllocatorHeapNames_GetNames(MockTarget.Architecture arch) + { + ISOSDacInterface13 impl = CreateSOSDacImplForHeapTests(arch); + + int needed; + int hr = impl.GetLoaderAllocatorHeapNames(0, null, &needed); + Assert.Equal(MockHeapDictionary.Count, needed); + + char** names = stackalloc char*[needed]; + hr = impl.GetLoaderAllocatorHeapNames(needed, names, &needed); + + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(MockHeapDictionary.Count, needed); + HashSet expectedNames = new(MockHeapDictionary.Keys); + for (int i = 0; i < needed; i++) + { + string actual = Marshal.PtrToStringAnsi((nint)names[i])!; + Assert.Contains(actual, expectedNames); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetLoaderAllocatorHeapNames_InsufficientBuffer(MockTarget.Architecture arch) + { + ISOSDacInterface13 impl = CreateSOSDacImplForHeapTests(arch); + + int needed; + char** names = stackalloc char*[2]; + int hr = impl.GetLoaderAllocatorHeapNames(2, names, &needed); + + Assert.Equal(HResults.S_FALSE, hr); + Assert.Equal(MockHeapDictionary.Count, needed); + HashSet expectedNames = new(MockHeapDictionary.Keys); + for (int i = 0; i < 2; i++) + { + string actual = Marshal.PtrToStringAnsi((nint)names[i])!; + Assert.Contains(actual, expectedNames); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetLoaderAllocatorHeapNames_NullPNeeded(MockTarget.Architecture arch) + { + ISOSDacInterface13 impl = CreateSOSDacImplForHeapTests(arch); + + int hr = impl.GetLoaderAllocatorHeapNames(0, null, null); + Assert.Equal(HResults.S_FALSE, hr); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetLoaderAllocatorHeaps_GetCount(MockTarget.Architecture arch) + { + ISOSDacInterface13 impl = CreateSOSDacImplForHeapTests(arch); + + int needed; + int hr = impl.GetLoaderAllocatorHeaps(new ClrDataAddress(0x100), 0, null, null, &needed); + + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(MockHeapDictionary.Count, needed); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetLoaderAllocatorHeaps_GetHeaps(MockTarget.Architecture arch) + { + ISOSDacInterface13 impl = CreateSOSDacImplForHeapTests(arch); + + int needed; + impl.GetLoaderAllocatorHeaps(new ClrDataAddress(0x100), 0, null, null, &needed); + + ClrDataAddress* heaps = stackalloc ClrDataAddress[needed]; + int* kinds = stackalloc int[needed]; + int hr = impl.GetLoaderAllocatorHeaps(new ClrDataAddress(0x100), needed, heaps, kinds, &needed); + + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(MockHeapDictionary.Count, needed); + HashSet expectedAddresses = new(MockHeapDictionary.Values.Select(p => (ulong)p)); + for (int i = 0; i < needed; i++) + { + Assert.Contains((ulong)heaps[i], expectedAddresses); + Assert.Equal(0, kinds[i]); // LoaderHeapKindNormal + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetLoaderAllocatorHeaps_InsufficientBuffer(MockTarget.Architecture arch) + { + ISOSDacInterface13 impl = CreateSOSDacImplForHeapTests(arch); + + ClrDataAddress* heaps = stackalloc ClrDataAddress[2]; + int* kinds = stackalloc int[2]; + int needed; + int hr = impl.GetLoaderAllocatorHeaps(new ClrDataAddress(0x100), 2, heaps, kinds, &needed); + + Assert.Equal(HResults.E_INVALIDARG, hr); + Assert.Equal(MockHeapDictionary.Count, needed); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetLoaderAllocatorHeaps_NullAddress(MockTarget.Architecture arch) + { + ISOSDacInterface13 impl = CreateSOSDacImplForHeapTests(arch); + + int hr = impl.GetLoaderAllocatorHeaps(new ClrDataAddress(0), 0, null, null, null); + + Assert.Equal(HResults.E_INVALIDARG, hr); + } }