From d9025a155b83e3fa2f4c8c5fd39a680fad1067c8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 20:24:34 +0000 Subject: [PATCH 01/11] Initial plan From 1b5c756728cec92609e8fcd6d7460689c444b12a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 20:45:47 +0000 Subject: [PATCH 02/11] Implement GetLoaderAllocatorHeapNames in SOSDacImpl and add unit tests Co-authored-by: max-charlamb <44248479+max-charlamb@users.noreply.github.com> --- .../SOSDacImpl.cs | 59 ++++++++++++- src/native/managed/cdac/tests/LoaderTests.cs | 88 +++++++++++++++++++ 2 files changed, 146 insertions(+), 1 deletion(-) 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 f1ee4780f5968e..c796d36b8bfb43 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -4256,8 +4256,65 @@ int ISOSDacInterface13.GetDomainLoaderAllocator(ClrDataAddress domainAddress, Cl #endif return hr; } + // Must match the order and names in LoaderAllocatorLoaderHeapNames in request.cpp + private static readonly string[] s_loaderAllocatorHeapNames = + [ + "LowFrequencyHeap", + "HighFrequencyHeap", + "StaticsHeap", + "StubHeap", + "ExecutableHeap", + "FixupPrecodeHeap", + "NewStubPrecodeHeap", + "IndcellHeap", + "CacheEntryHeap", + ]; + + private static readonly nint[] s_loaderAllocatorHeapNamePtrs = InitializeHeapNamePtrs(); + + private static nint[] InitializeHeapNamePtrs() + { + nint[] ptrs = new nint[s_loaderAllocatorHeapNames.Length]; + for (int i = 0; i < s_loaderAllocatorHeapNames.Length; i++) + { + ptrs[i] = Marshal.StringToHGlobalAnsi(s_loaderAllocatorHeapNames[i]); + } + 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 + { + int loaderHeapCount = s_loaderAllocatorHeapNamePtrs.Length; + if (pNeeded != null) + *pNeeded = loaderHeapCount; + + if (ppNames != null) + { + for (int i = 0; i < Math.Min(count, loaderHeapCount); i++) + ppNames[i] = (char*)s_loaderAllocatorHeapNamePtrs[i]; + } + + if (count < loaderHeapCount) + hr = HResults.S_FALSE; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyImpl13 is not null) + { + int pNeededLocal; + int hrLocal = _legacyImpl13.GetLoaderAllocatorHeapNames(0, null, &pNeededLocal); + Debug.Assert(pNeeded is null || *pNeeded == pNeededLocal, $"cDAC needed: {(pNeeded != null ? *pNeeded : -1)}, DAC needed: {pNeededLocal}"); + } +#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; int ISOSDacInterface13.GetHandleTableMemoryRegions(/*ISOSMemoryEnum*/ void** ppEnum) diff --git a/src/native/managed/cdac/tests/LoaderTests.cs b/src/native/managed/cdac/tests/LoaderTests.cs index 0cbb93e9481c77..10f7e67471b424 100644 --- a/src/native/managed/cdac/tests/LoaderTests.cs +++ b/src/native/managed/cdac/tests/LoaderTests.cs @@ -3,7 +3,9 @@ using System; using System.IO; +using System.Runtime.InteropServices; using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Legacy; using Moq; using Xunit; @@ -80,4 +82,90 @@ public void GetFileName(MockTarget.Architecture arch) Assert.Equal(string.Empty, actual); } } + + private static readonly string[] ExpectedHeapNames = + [ + "LowFrequencyHeap", + "HighFrequencyHeap", + "StaticsHeap", + "StubHeap", + "ExecutableHeap", + "FixupPrecodeHeap", + "NewStubPrecodeHeap", + "IndcellHeap", + "CacheEntryHeap", + ]; + + private static SOSDacImpl CreateSOSDacImplForHeapNamesTest(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); + return new SOSDacImpl(target, null); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetLoaderAllocatorHeapNames_GetCount(MockTarget.Architecture arch) + { + ISOSDacInterface13 impl = CreateSOSDacImplForHeapNamesTest(arch); + + int needed; + int hr = impl.GetLoaderAllocatorHeapNames(0, null, &needed); + + Assert.Equal(HResults.S_FALSE, hr); + Assert.Equal(ExpectedHeapNames.Length, needed); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetLoaderAllocatorHeapNames_GetNames(MockTarget.Architecture arch) + { + ISOSDacInterface13 impl = CreateSOSDacImplForHeapNamesTest(arch); + + int needed; + int hr = impl.GetLoaderAllocatorHeapNames(0, null, &needed); + Assert.Equal(ExpectedHeapNames.Length, needed); + + char** names = stackalloc char*[needed]; + hr = impl.GetLoaderAllocatorHeapNames(needed, names, &needed); + + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(ExpectedHeapNames.Length, needed); + for (int i = 0; i < needed; i++) + { + string actual = Marshal.PtrToStringAnsi((nint)names[i])!; + Assert.Equal(ExpectedHeapNames[i], actual); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetLoaderAllocatorHeapNames_InsufficientBuffer(MockTarget.Architecture arch) + { + ISOSDacInterface13 impl = CreateSOSDacImplForHeapNamesTest(arch); + + int needed; + char** names = stackalloc char*[2]; + int hr = impl.GetLoaderAllocatorHeapNames(2, names, &needed); + + Assert.Equal(HResults.S_FALSE, hr); + Assert.Equal(ExpectedHeapNames.Length, needed); + for (int i = 0; i < 2; i++) + { + string actual = Marshal.PtrToStringAnsi((nint)names[i])!; + Assert.Equal(ExpectedHeapNames[i], actual); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetLoaderAllocatorHeapNames_NullPNeeded(MockTarget.Architecture arch) + { + ISOSDacInterface13 impl = CreateSOSDacImplForHeapNamesTest(arch); + + int hr = impl.GetLoaderAllocatorHeapNames(0, null, null); + Assert.Equal(HResults.S_FALSE, hr); + } } From 5cd00b4ebae1021689c0c668783675ccffd4b303 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 20:48:32 +0000 Subject: [PATCH 03/11] Remove unused hrLocal variable in DEBUG block Co-authored-by: max-charlamb <44248479+max-charlamb@users.noreply.github.com> --- .../SOSDacImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c796d36b8bfb43..113f0ccd1efb6b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -4309,7 +4309,7 @@ int ISOSDacInterface13.GetLoaderAllocatorHeapNames(int count, char** ppNames, in if (_legacyImpl13 is not null) { int pNeededLocal; - int hrLocal = _legacyImpl13.GetLoaderAllocatorHeapNames(0, null, &pNeededLocal); + _legacyImpl13.GetLoaderAllocatorHeapNames(0, null, &pNeededLocal); Debug.Assert(pNeeded is null || *pNeeded == pNeededLocal, $"cDAC needed: {(pNeeded != null ? *pNeeded : -1)}, DAC needed: {pNeededLocal}"); } #endif From 729aa85f6aa4133721a8e57f48d66738a8010a95 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 21:17:18 +0000 Subject: [PATCH 04/11] Changes before error encountered Co-authored-by: max-charlamb <44248479+max-charlamb@users.noreply.github.com> --- .../vm/datadescriptor/datadescriptor.inc | 20 +++++ src/coreclr/vm/loaderallocator.hpp | 12 +++ src/coreclr/vm/virtualcallstub.h | 11 +++ .../Contracts/ILoader.cs | 2 + .../DataType.cs | 1 + .../Contracts/Loader_1.cs | 81 +++++++++++++++++ .../Data/LoaderAllocator.cs | 18 ++++ .../Data/VirtualCallStubManager.cs | 23 +++++ .../SOSDacImpl.cs | 84 +++++++++++++----- src/native/managed/cdac/tests/LoaderTests.cs | 88 +++++++++++++++++-- 10 files changed, 312 insertions(+), 28 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/VirtualCallStubManager.cs diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 4799a56f287703..8d43966d64be27 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -223,10 +223,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..8052d5679ba16d 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,8 @@ 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(); + IReadOnlyList GetLoaderAllocatorHeapNames() => throw new NotImplementedException(); + IReadOnlyList 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 feca2918689051..2f595d0c75f0e0 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -122,6 +122,7 @@ public enum DataType DynamicILBlobTable, EEJitManager, PatchpointInfo, + VirtualCallStubManager, TransitionBlock, DebuggerEval, 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..ef27ea321f8e06 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,85 @@ TargetPointer ILoader.GetDynamicIL(ModuleHandle handle, uint token) ISHash shashContract = _target.Contracts.SHash; return shashContract.LookupSHash(dynamicILBlobTable.HashTable, token).EntryIL; } + + IReadOnlyList ILoader.GetLoaderAllocatorHeapNames() + { + Target.TypeInfo laType = _target.GetTypeInfo(DataType.LoaderAllocator); + + List names = + [ + "LowFrequencyHeap", + "HighFrequencyHeap", + "StaticsHeap", + "StubHeap", + "ExecutableHeap", + "FixupPrecodeHeap", + "NewStubPrecodeHeap", + ]; + + if (laType.Fields.ContainsKey(nameof(Data.LoaderAllocator.DynamicHelpersStubHeap))) + names.Add("DynamicHelpersStubHeap"); + + names.Add("IndcellHeap"); + + try + { + Target.TypeInfo vcsType = _target.GetTypeInfo(DataType.VirtualCallStubManager); + if (vcsType.Fields.ContainsKey(nameof(Data.VirtualCallStubManager.CacheEntryHeap))) + names.Add("CacheEntryHeap"); + } + catch (InvalidOperationException) + { + // VirtualCallStubManager type not available + } + + return names; + } + + IReadOnlyList ILoader.GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer) + { + Data.LoaderAllocator loaderAllocator = _target.ProcessedData.GetOrAdd(loaderAllocatorPointer); + + List heaps = + [ + loaderAllocator.LowFrequencyHeap, + loaderAllocator.HighFrequencyHeap, + loaderAllocator.StaticsHeap, + loaderAllocator.StubHeap, + loaderAllocator.ExecutableHeap, + loaderAllocator.FixupPrecodeHeap ?? TargetPointer.Null, + loaderAllocator.NewStubPrecodeHeap ?? TargetPointer.Null, + ]; + + if (loaderAllocator.DynamicHelpersStubHeap is not null) + heaps.Add(loaderAllocator.DynamicHelpersStubHeap.Value); + + if (loaderAllocator.VirtualCallStubManager != TargetPointer.Null) + { + try + { + Data.VirtualCallStubManager vcsMgr = _target.ProcessedData.GetOrAdd(loaderAllocator.VirtualCallStubManager); + heaps.Add(vcsMgr.IndcellHeap); + + if (vcsMgr.CacheEntryHeap is not null) + heaps.Add(vcsMgr.CacheEntryHeap.Value); + } + catch (InvalidOperationException) + { + // VirtualCallStubManager type not available - fill with nulls + IReadOnlyList names = ((ILoader)this).GetLoaderAllocatorHeapNames(); + while (heaps.Count < names.Count) + heaps.Add(TargetPointer.Null); + } + } + else + { + // No VirtualCallStubManager - fill remaining slots with null + IReadOnlyList names = ((ILoader)this).GetLoaderAllocatorHeapNames(); + while (heaps.Count < names.Count) + heaps.Add(TargetPointer.Null); + } + + 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 113f0ccd1efb6b..a53d960a7a6098 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -4256,29 +4256,22 @@ int ISOSDacInterface13.GetDomainLoaderAllocator(ClrDataAddress domainAddress, Cl #endif return hr; } - // Must match the order and names in LoaderAllocatorLoaderHeapNames in request.cpp - private static readonly string[] s_loaderAllocatorHeapNames = - [ - "LowFrequencyHeap", - "HighFrequencyHeap", - "StaticsHeap", - "StubHeap", - "ExecutableHeap", - "FixupPrecodeHeap", - "NewStubPrecodeHeap", - "IndcellHeap", - "CacheEntryHeap", - ]; - - private static readonly nint[] s_loaderAllocatorHeapNamePtrs = InitializeHeapNamePtrs(); - - private static nint[] InitializeHeapNamePtrs() + + private nint[]? _loaderAllocatorHeapNamePtrs; + + private nint[] GetOrInitializeHeapNamePtrs() { - nint[] ptrs = new nint[s_loaderAllocatorHeapNames.Length]; - for (int i = 0; i < s_loaderAllocatorHeapNames.Length; i++) + if (_loaderAllocatorHeapNamePtrs is not null) + return _loaderAllocatorHeapNamePtrs; + + Contracts.ILoader contract = _target.Contracts.Loader; + IReadOnlyList names = contract.GetLoaderAllocatorHeapNames(); + nint[] ptrs = new nint[names.Count]; + for (int i = 0; i < names.Count; i++) { - ptrs[i] = Marshal.StringToHGlobalAnsi(s_loaderAllocatorHeapNames[i]); + ptrs[i] = Marshal.StringToHGlobalAnsi(names[i]); } + _loaderAllocatorHeapNamePtrs = ptrs; return ptrs; } @@ -4287,14 +4280,15 @@ int ISOSDacInterface13.GetLoaderAllocatorHeapNames(int count, char** ppNames, in int hr = HResults.S_OK; try { - int loaderHeapCount = s_loaderAllocatorHeapNamePtrs.Length; + 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*)s_loaderAllocatorHeapNamePtrs[i]; + ppNames[i] = (char*)heapNamePtrs[i]; } if (count < loaderHeapCount) @@ -4316,7 +4310,51 @@ int ISOSDacInterface13.GetLoaderAllocatorHeapNames(int count, char** ppNames, in 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; + IReadOnlyList 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 + { + for (int i = 0; i < loaderHeapCount; i++) + { + pLoaderHeaps[i] = heaps[i].ToClrDataAddress(_target); + pKinds[i] = 0; // LoaderHeapKindNormal + } + } + } + } + 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}"); + } +#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 10f7e67471b424..41448352034435 100644 --- a/src/native/managed/cdac/tests/LoaderTests.cs +++ b/src/native/managed/cdac/tests/LoaderTests.cs @@ -96,12 +96,29 @@ public void GetFileName(MockTarget.Architecture arch) "CacheEntryHeap", ]; - private static SOSDacImpl CreateSOSDacImplForHeapNamesTest(MockTarget.Architecture arch) + private static readonly TargetPointer[] MockHeapAddresses = + [ + new(0x1000), + new(0x2000), + new(0x3000), + new(0x4000), + new(0x5000), + new(0x6000), + new(0x7000), + new(0x8000), + 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.GetLoaderAllocatorHeapNames() == (IReadOnlyList)ExpectedHeapNames + && l.GetLoaderAllocatorHeaps(It.IsAny()) == (IReadOnlyList)MockHeapAddresses))); return new SOSDacImpl(target, null); } @@ -109,7 +126,7 @@ private static SOSDacImpl CreateSOSDacImplForHeapNamesTest(MockTarget.Architectu [ClassData(typeof(MockTarget.StdArch))] public void GetLoaderAllocatorHeapNames_GetCount(MockTarget.Architecture arch) { - ISOSDacInterface13 impl = CreateSOSDacImplForHeapNamesTest(arch); + ISOSDacInterface13 impl = CreateSOSDacImplForHeapTests(arch); int needed; int hr = impl.GetLoaderAllocatorHeapNames(0, null, &needed); @@ -122,7 +139,7 @@ public void GetLoaderAllocatorHeapNames_GetCount(MockTarget.Architecture arch) [ClassData(typeof(MockTarget.StdArch))] public void GetLoaderAllocatorHeapNames_GetNames(MockTarget.Architecture arch) { - ISOSDacInterface13 impl = CreateSOSDacImplForHeapNamesTest(arch); + ISOSDacInterface13 impl = CreateSOSDacImplForHeapTests(arch); int needed; int hr = impl.GetLoaderAllocatorHeapNames(0, null, &needed); @@ -144,7 +161,7 @@ public void GetLoaderAllocatorHeapNames_GetNames(MockTarget.Architecture arch) [ClassData(typeof(MockTarget.StdArch))] public void GetLoaderAllocatorHeapNames_InsufficientBuffer(MockTarget.Architecture arch) { - ISOSDacInterface13 impl = CreateSOSDacImplForHeapNamesTest(arch); + ISOSDacInterface13 impl = CreateSOSDacImplForHeapTests(arch); int needed; char** names = stackalloc char*[2]; @@ -163,9 +180,70 @@ public void GetLoaderAllocatorHeapNames_InsufficientBuffer(MockTarget.Architectu [ClassData(typeof(MockTarget.StdArch))] public void GetLoaderAllocatorHeapNames_NullPNeeded(MockTarget.Architecture arch) { - ISOSDacInterface13 impl = CreateSOSDacImplForHeapNamesTest(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(MockHeapAddresses.Length, 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(MockHeapAddresses.Length, needed); + for (int i = 0; i < needed; i++) + { + Assert.Equal((ulong)MockHeapAddresses[i], (ulong)heaps[i]); + 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(MockHeapAddresses.Length, 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); + } } From 0554c438e59ccbf5a677b6825d8c2692af4b42a1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 21:26:08 +0000 Subject: [PATCH 05/11] Remove fragile try-catch for VirtualCallStubManager type lookup and add missing using Co-authored-by: max-charlamb <44248479+max-charlamb@users.noreply.github.com> --- .../Contracts/Loader_1.cs | 31 +++++-------------- src/native/managed/cdac/tests/LoaderTests.cs | 1 + 2 files changed, 8 insertions(+), 24 deletions(-) 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 ef27ea321f8e06..84fd1478dfd6f6 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 @@ -556,6 +556,7 @@ TargetPointer ILoader.GetDynamicIL(ModuleHandle handle, uint token) IReadOnlyList ILoader.GetLoaderAllocatorHeapNames() { Target.TypeInfo laType = _target.GetTypeInfo(DataType.LoaderAllocator); + Target.TypeInfo vcsType = _target.GetTypeInfo(DataType.VirtualCallStubManager); List names = [ @@ -573,16 +574,8 @@ IReadOnlyList ILoader.GetLoaderAllocatorHeapNames() names.Add("IndcellHeap"); - try - { - Target.TypeInfo vcsType = _target.GetTypeInfo(DataType.VirtualCallStubManager); - if (vcsType.Fields.ContainsKey(nameof(Data.VirtualCallStubManager.CacheEntryHeap))) - names.Add("CacheEntryHeap"); - } - catch (InvalidOperationException) - { - // VirtualCallStubManager type not available - } + if (vcsType.Fields.ContainsKey(nameof(Data.VirtualCallStubManager.CacheEntryHeap))) + names.Add("CacheEntryHeap"); return names; } @@ -607,21 +600,11 @@ IReadOnlyList ILoader.GetLoaderAllocatorHeaps(TargetPointer loade if (loaderAllocator.VirtualCallStubManager != TargetPointer.Null) { - try - { - Data.VirtualCallStubManager vcsMgr = _target.ProcessedData.GetOrAdd(loaderAllocator.VirtualCallStubManager); - heaps.Add(vcsMgr.IndcellHeap); + Data.VirtualCallStubManager vcsMgr = _target.ProcessedData.GetOrAdd(loaderAllocator.VirtualCallStubManager); + heaps.Add(vcsMgr.IndcellHeap); - if (vcsMgr.CacheEntryHeap is not null) - heaps.Add(vcsMgr.CacheEntryHeap.Value); - } - catch (InvalidOperationException) - { - // VirtualCallStubManager type not available - fill with nulls - IReadOnlyList names = ((ILoader)this).GetLoaderAllocatorHeapNames(); - while (heaps.Count < names.Count) - heaps.Add(TargetPointer.Null); - } + if (vcsMgr.CacheEntryHeap is not null) + heaps.Add(vcsMgr.CacheEntryHeap.Value); } else { diff --git a/src/native/managed/cdac/tests/LoaderTests.cs b/src/native/managed/cdac/tests/LoaderTests.cs index 41448352034435..fdd2623b9c7d3a 100644 --- a/src/native/managed/cdac/tests/LoaderTests.cs +++ b/src/native/managed/cdac/tests/LoaderTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using Microsoft.Diagnostics.DataContractReader.Contracts; From 4d3920d3baa928ac7788536ff9d6f49dad334500 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 21:51:36 +0000 Subject: [PATCH 06/11] Refactor ILoader to single dictionary-returning GetLoaderAllocatorHeaps API Replace separate GetLoaderAllocatorHeapNames() and GetLoaderAllocatorHeaps(TargetPointer) with a single GetLoaderAllocatorHeaps(TargetPointer) returning IReadOnlyDictionary. SOSDacImpl.GetLoaderAllocatorHeapNames now queries the global loader allocator to get names from the dictionary. GetLoaderAllocatorHeaps iterates dictionary values. Tests updated to use dictionary-based mock and verify unordered name/address sets. Co-authored-by: max-charlamb <44248479+max-charlamb@users.noreply.github.com> --- .../Contracts/ILoader.cs | 3 +- .../Contracts/Loader_1.cs | 65 ++++++------------- .../SOSDacImpl.cs | 18 +++-- src/native/managed/cdac/tests/LoaderTests.cs | 65 ++++++++----------- 4 files changed, 60 insertions(+), 91 deletions(-) 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 8052d5679ba16d..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,8 +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(); - IReadOnlyList GetLoaderAllocatorHeapNames() => throw new NotImplementedException(); - IReadOnlyList GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer) => 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.Contracts/Contracts/Loader_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs index 84fd1478dfd6f6..a96a6ff0b1afcb 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 @@ -553,65 +553,40 @@ TargetPointer ILoader.GetDynamicIL(ModuleHandle handle, uint token) return shashContract.LookupSHash(dynamicILBlobTable.HashTable, token).EntryIL; } - IReadOnlyList ILoader.GetLoaderAllocatorHeapNames() + IReadOnlyDictionary ILoader.GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer) { + Data.LoaderAllocator loaderAllocator = _target.ProcessedData.GetOrAdd(loaderAllocatorPointer); Target.TypeInfo laType = _target.GetTypeInfo(DataType.LoaderAllocator); Target.TypeInfo vcsType = _target.GetTypeInfo(DataType.VirtualCallStubManager); - List names = - [ - "LowFrequencyHeap", - "HighFrequencyHeap", - "StaticsHeap", - "StubHeap", - "ExecutableHeap", - "FixupPrecodeHeap", - "NewStubPrecodeHeap", - ]; + Dictionary heaps = new() + { + ["LowFrequencyHeap"] = loaderAllocator.LowFrequencyHeap, + ["HighFrequencyHeap"] = loaderAllocator.HighFrequencyHeap, + ["StaticsHeap"] = loaderAllocator.StaticsHeap, + ["StubHeap"] = loaderAllocator.StubHeap, + ["ExecutableHeap"] = loaderAllocator.ExecutableHeap, + ["FixupPrecodeHeap"] = loaderAllocator.FixupPrecodeHeap ?? TargetPointer.Null, + ["NewStubPrecodeHeap"] = loaderAllocator.NewStubPrecodeHeap ?? TargetPointer.Null, + }; if (laType.Fields.ContainsKey(nameof(Data.LoaderAllocator.DynamicHelpersStubHeap))) - names.Add("DynamicHelpersStubHeap"); - - names.Add("IndcellHeap"); - - if (vcsType.Fields.ContainsKey(nameof(Data.VirtualCallStubManager.CacheEntryHeap))) - names.Add("CacheEntryHeap"); - - return names; - } - - IReadOnlyList ILoader.GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer) - { - Data.LoaderAllocator loaderAllocator = _target.ProcessedData.GetOrAdd(loaderAllocatorPointer); - - List heaps = - [ - loaderAllocator.LowFrequencyHeap, - loaderAllocator.HighFrequencyHeap, - loaderAllocator.StaticsHeap, - loaderAllocator.StubHeap, - loaderAllocator.ExecutableHeap, - loaderAllocator.FixupPrecodeHeap ?? TargetPointer.Null, - loaderAllocator.NewStubPrecodeHeap ?? TargetPointer.Null, - ]; - - if (loaderAllocator.DynamicHelpersStubHeap is not null) - heaps.Add(loaderAllocator.DynamicHelpersStubHeap.Value); + heaps["DynamicHelpersStubHeap"] = loaderAllocator.DynamicHelpersStubHeap ?? TargetPointer.Null; if (loaderAllocator.VirtualCallStubManager != TargetPointer.Null) { Data.VirtualCallStubManager vcsMgr = _target.ProcessedData.GetOrAdd(loaderAllocator.VirtualCallStubManager); - heaps.Add(vcsMgr.IndcellHeap); + heaps["IndcellHeap"] = vcsMgr.IndcellHeap; - if (vcsMgr.CacheEntryHeap is not null) - heaps.Add(vcsMgr.CacheEntryHeap.Value); + if (vcsType.Fields.ContainsKey(nameof(Data.VirtualCallStubManager.CacheEntryHeap))) + heaps["CacheEntryHeap"] = vcsMgr.CacheEntryHeap ?? TargetPointer.Null; } else { - // No VirtualCallStubManager - fill remaining slots with null - IReadOnlyList names = ((ILoader)this).GetLoaderAllocatorHeapNames(); - while (heaps.Count < names.Count) - heaps.Add(TargetPointer.Null); + heaps["IndcellHeap"] = TargetPointer.Null; + + if (vcsType.Fields.ContainsKey(nameof(Data.VirtualCallStubManager.CacheEntryHeap))) + heaps["CacheEntryHeap"] = TargetPointer.Null; } return heaps; 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 a53d960a7a6098..66b30ec253b7e7 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -4265,11 +4265,13 @@ private nint[] GetOrInitializeHeapNamePtrs() return _loaderAllocatorHeapNamePtrs; Contracts.ILoader contract = _target.Contracts.Loader; - IReadOnlyList names = contract.GetLoaderAllocatorHeapNames(); - nint[] ptrs = new nint[names.Count]; - for (int i = 0; i < names.Count; i++) + 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(names[i]); + ptrs[i++] = Marshal.StringToHGlobalAnsi(name); } _loaderAllocatorHeapNamePtrs = ptrs; return ptrs; @@ -4318,7 +4320,7 @@ int ISOSDacInterface13.GetLoaderAllocatorHeaps(ClrDataAddress loaderAllocator, i try { Contracts.ILoader contract = _target.Contracts.Loader; - IReadOnlyList heaps = contract.GetLoaderAllocatorHeaps(loaderAllocator.ToTargetPointer(_target)); + IReadOnlyDictionary heaps = contract.GetLoaderAllocatorHeaps(loaderAllocator.ToTargetPointer(_target)); int loaderHeapCount = heaps.Count; if (pNeeded != null) @@ -4332,10 +4334,12 @@ int ISOSDacInterface13.GetLoaderAllocatorHeaps(ClrDataAddress loaderAllocator, i } else { - for (int i = 0; i < loaderHeapCount; i++) + int i = 0; + foreach (TargetPointer heap in heaps.Values) { - pLoaderHeaps[i] = heaps[i].ToClrDataAddress(_target); + pLoaderHeaps[i] = heap.ToClrDataAddress(_target); pKinds[i] = 0; // LoaderHeapKindNormal + i++; } } } diff --git a/src/native/managed/cdac/tests/LoaderTests.cs b/src/native/managed/cdac/tests/LoaderTests.cs index fdd2623b9c7d3a..9e987d560e0e08 100644 --- a/src/native/managed/cdac/tests/LoaderTests.cs +++ b/src/native/managed/cdac/tests/LoaderTests.cs @@ -4,6 +4,7 @@ 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; @@ -84,31 +85,18 @@ public void GetFileName(MockTarget.Architecture arch) } } - private static readonly string[] ExpectedHeapNames = - [ - "LowFrequencyHeap", - "HighFrequencyHeap", - "StaticsHeap", - "StubHeap", - "ExecutableHeap", - "FixupPrecodeHeap", - "NewStubPrecodeHeap", - "IndcellHeap", - "CacheEntryHeap", - ]; - - private static readonly TargetPointer[] MockHeapAddresses = - [ - new(0x1000), - new(0x2000), - new(0x3000), - new(0x4000), - new(0x5000), - new(0x6000), - new(0x7000), - new(0x8000), - new(0x9000), - ]; + 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) { @@ -118,8 +106,8 @@ private static SOSDacImpl CreateSOSDacImplForHeapTests(MockTarget.Architecture a var target = new TestPlaceholderTarget(arch, builder.GetMemoryContext().ReadFromTarget, loader.Types); target.SetContracts(Mock.Of( c => c.Loader == Mock.Of( - l => l.GetLoaderAllocatorHeapNames() == (IReadOnlyList)ExpectedHeapNames - && l.GetLoaderAllocatorHeaps(It.IsAny()) == (IReadOnlyList)MockHeapAddresses))); + l => l.GetLoaderAllocatorHeaps(It.IsAny()) == (IReadOnlyDictionary)MockHeapDictionary + && l.GetGlobalLoaderAllocator() == new TargetPointer(0x100)))); return new SOSDacImpl(target, null); } @@ -133,7 +121,7 @@ public void GetLoaderAllocatorHeapNames_GetCount(MockTarget.Architecture arch) int hr = impl.GetLoaderAllocatorHeapNames(0, null, &needed); Assert.Equal(HResults.S_FALSE, hr); - Assert.Equal(ExpectedHeapNames.Length, needed); + Assert.Equal(MockHeapDictionary.Count, needed); } [Theory] @@ -144,17 +132,18 @@ public void GetLoaderAllocatorHeapNames_GetNames(MockTarget.Architecture arch) int needed; int hr = impl.GetLoaderAllocatorHeapNames(0, null, &needed); - Assert.Equal(ExpectedHeapNames.Length, 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(ExpectedHeapNames.Length, needed); + 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.Equal(ExpectedHeapNames[i], actual); + Assert.Contains(actual, expectedNames); } } @@ -169,11 +158,12 @@ public void GetLoaderAllocatorHeapNames_InsufficientBuffer(MockTarget.Architectu int hr = impl.GetLoaderAllocatorHeapNames(2, names, &needed); Assert.Equal(HResults.S_FALSE, hr); - Assert.Equal(ExpectedHeapNames.Length, needed); + 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.Equal(ExpectedHeapNames[i], actual); + Assert.Contains(actual, expectedNames); } } @@ -197,7 +187,7 @@ public void GetLoaderAllocatorHeaps_GetCount(MockTarget.Architecture arch) int hr = impl.GetLoaderAllocatorHeaps(new ClrDataAddress(0x100), 0, null, null, &needed); Assert.Equal(HResults.S_OK, hr); - Assert.Equal(MockHeapAddresses.Length, needed); + Assert.Equal(MockHeapDictionary.Count, needed); } [Theory] @@ -214,10 +204,11 @@ public void GetLoaderAllocatorHeaps_GetHeaps(MockTarget.Architecture arch) int hr = impl.GetLoaderAllocatorHeaps(new ClrDataAddress(0x100), needed, heaps, kinds, &needed); Assert.Equal(HResults.S_OK, hr); - Assert.Equal(MockHeapAddresses.Length, needed); + Assert.Equal(MockHeapDictionary.Count, needed); + HashSet expectedAddresses = new(MockHeapDictionary.Values.Select(p => (ulong)p)); for (int i = 0; i < needed; i++) { - Assert.Equal((ulong)MockHeapAddresses[i], (ulong)heaps[i]); + Assert.Contains((ulong)heaps[i], expectedAddresses); Assert.Equal(0, kinds[i]); // LoaderHeapKindNormal } } @@ -234,7 +225,7 @@ public void GetLoaderAllocatorHeaps_InsufficientBuffer(MockTarget.Architecture a int hr = impl.GetLoaderAllocatorHeaps(new ClrDataAddress(0x100), 2, heaps, kinds, &needed); Assert.Equal(HResults.E_INVALIDARG, hr); - Assert.Equal(MockHeapAddresses.Length, needed); + Assert.Equal(MockHeapDictionary.Count, needed); } [Theory] From e0e9cd5d953c7fa34232102b08b693c7417cb661 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 22:05:28 +0000 Subject: [PATCH 07/11] Address review: use nameof for dict keys, exclude null heaps from dictionary - Use nameof(Data.LoaderAllocator.X) instead of hardcoded strings for all dictionary keys in GetLoaderAllocatorHeaps - Make FixupPrecodeHeap and NewStubPrecodeHeap conditional on ContainsKey (exclude from dictionary when field absent from data descriptor) - Remove else branch that padded IndcellHeap/CacheEntryHeap with null when VirtualCallStubManager is null - only include them when VcsManager exists - All 864 cDAC tests pass Co-authored-by: max-charlamb <44248479+max-charlamb@users.noreply.github.com> --- .../Contracts/Loader_1.cs | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) 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 a96a6ff0b1afcb..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 @@ -557,36 +557,34 @@ IReadOnlyDictionary ILoader.GetLoaderAllocatorHeaps(Targe { Data.LoaderAllocator loaderAllocator = _target.ProcessedData.GetOrAdd(loaderAllocatorPointer); Target.TypeInfo laType = _target.GetTypeInfo(DataType.LoaderAllocator); - Target.TypeInfo vcsType = _target.GetTypeInfo(DataType.VirtualCallStubManager); Dictionary heaps = new() { - ["LowFrequencyHeap"] = loaderAllocator.LowFrequencyHeap, - ["HighFrequencyHeap"] = loaderAllocator.HighFrequencyHeap, - ["StaticsHeap"] = loaderAllocator.StaticsHeap, - ["StubHeap"] = loaderAllocator.StubHeap, - ["ExecutableHeap"] = loaderAllocator.ExecutableHeap, - ["FixupPrecodeHeap"] = loaderAllocator.FixupPrecodeHeap ?? TargetPointer.Null, - ["NewStubPrecodeHeap"] = loaderAllocator.NewStubPrecodeHeap ?? TargetPointer.Null, + [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["DynamicHelpersStubHeap"] = loaderAllocator.DynamicHelpersStubHeap ?? TargetPointer.Null; + heaps[nameof(Data.LoaderAllocator.DynamicHelpersStubHeap)] = loaderAllocator.DynamicHelpersStubHeap!.Value; if (loaderAllocator.VirtualCallStubManager != TargetPointer.Null) { Data.VirtualCallStubManager vcsMgr = _target.ProcessedData.GetOrAdd(loaderAllocator.VirtualCallStubManager); - heaps["IndcellHeap"] = vcsMgr.IndcellHeap; + Target.TypeInfo vcsType = _target.GetTypeInfo(DataType.VirtualCallStubManager); - if (vcsType.Fields.ContainsKey(nameof(Data.VirtualCallStubManager.CacheEntryHeap))) - heaps["CacheEntryHeap"] = vcsMgr.CacheEntryHeap ?? TargetPointer.Null; - } - else - { - heaps["IndcellHeap"] = TargetPointer.Null; + heaps[nameof(Data.VirtualCallStubManager.IndcellHeap)] = vcsMgr.IndcellHeap; if (vcsType.Fields.ContainsKey(nameof(Data.VirtualCallStubManager.CacheEntryHeap))) - heaps["CacheEntryHeap"] = TargetPointer.Null; + heaps[nameof(Data.VirtualCallStubManager.CacheEntryHeap)] = vcsMgr.CacheEntryHeap!.Value; } return heaps; From 3f63f2f059685a97ea50144ab37beb5e8b01cf7a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 22:46:10 +0000 Subject: [PATCH 08/11] Fix native build break: include virtualcallstub.h in datadescriptor.h datadescriptor.h did not include virtualcallstub.h, so the cdac_data template specialization was not visible when datadescriptor.inc was compiled. This caused a build failure referencing cdac_data::IndcellHeap and cdac_data::CacheEntryHeap. Co-authored-by: max-charlamb <44248479+max-charlamb@users.noreply.github.com> --- src/coreclr/vm/datadescriptor/datadescriptor.h | 1 + 1 file changed, 1 insertion(+) 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" From ab7f065e6d6ebc77d2e4963eaaedc70c256d9e5b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 14:59:14 +0000 Subject: [PATCH 09/11] Document GetLoaderAllocatorHeaps API and data descriptors in Loader.md Add documentation for the new ILoader.GetLoaderAllocatorHeaps API, including: - New API in the contract API section - New data descriptor fields for LoaderAllocator (StaticsHeap, ExecutableHeap, FixupPrecodeHeap, NewStubPrecodeHeap, DynamicHelpersStubHeap, VirtualCallStubManager) and VirtualCallStubManager (IndcellHeap, CacheEntryHeap) in the data descriptors table - Pseudocode implementation showing feature-conditional dictionary building Co-authored-by: max-charlamb <44248479+max-charlamb@users.noreply.github.com> --- docs/design/datacontracts/Loader.md | 47 +++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) 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; From a54d098c65da66d3a9f5fefea96a353b9b838e63 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 15:03:06 +0000 Subject: [PATCH 10/11] Resolve merge conflict in DataType.cs: include both VirtualCallStubManager and PortableEntryPoint Co-authored-by: max-charlamb <44248479+max-charlamb@users.noreply.github.com> --- .../DataType.cs | 3 --- 1 file changed, 3 deletions(-) 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 eaa8763ed9d012..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,11 +123,8 @@ public enum DataType DynamicILBlobTable, EEJitManager, PatchpointInfo, -<<<<<<< copilot/implement-get-loader-allocator-heap-names VirtualCallStubManager, -======= PortableEntryPoint, ->>>>>>> main TransitionBlock, DebuggerEval, From 2c1ed8a61eb64e37e9f2b235250698f64a1ddc4c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 18:16:12 +0000 Subject: [PATCH 11/11] Add DAC/cDAC validation for GetLoaderAllocatorHeapNames and GetLoaderAllocatorHeaps Expand #if DEBUG validation blocks to compare actual name strings and heap pointer/kind values between cDAC and legacy DAC, following the pattern used by GetAssemblyList and GetMethodsWithProfilerModifiedIL. Co-authored-by: max-charlamb <44248479+max-charlamb@users.noreply.github.com> --- .../SOSDacImpl.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) 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 16e1b950b7b40d..ff2bae3e48eb92 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -4510,6 +4510,18 @@ int ISOSDacInterface13.GetLoaderAllocatorHeapNames(int count, char** ppNames, in 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; @@ -4558,6 +4570,21 @@ int ISOSDacInterface13.GetLoaderAllocatorHeaps(ClrDataAddress loaderAllocator, i 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;