From 04535d28a2a9c77a932f2e0e8d1f80eb33b68571 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 17:00:12 +0000 Subject: [PATCH 01/31] Initial plan From f5cfd1086d70dfae1e91bcd9ed22e34b33d08fc5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 17:26:36 +0000 Subject: [PATCH 02/31] Implement cDAC TraverseLoaderHeap API using GC contract Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- docs/design/datacontracts/GC.md | 44 +++++ src/coreclr/inc/loaderheap.h | 17 ++ .../vm/datadescriptor/datadescriptor.inc | 11 ++ .../Contracts/IGC.cs | 13 ++ .../DataType.cs | 2 + .../Contracts/GC/GC_1.cs | 22 +++ .../Data/LoaderHeap.cs | 19 ++ .../Data/LoaderHeapBlock.cs | 23 +++ .../SOSDacImpl.cs | 28 ++- .../managed/cdac/tests/GCLoaderHeapTests.cs | 171 ++++++++++++++++++ 10 files changed, 349 insertions(+), 1 deletion(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/LoaderHeap.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/LoaderHeapBlock.cs create mode 100644 src/native/managed/cdac/tests/GCLoaderHeapTests.cs diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index b8d59f80475369..42603d55c8cae8 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -66,6 +66,12 @@ public readonly struct GCOomData public TargetNUInt AvailablePagefileMB { get; init; } public bool LohP { get; init; } } + +public readonly struct LoaderHeapBlockData +{ + public TargetPointer VirtualAddress { get; init; } + public TargetNUInt VirtualSize { get; init; } +} ``` ```csharp @@ -116,6 +122,12 @@ public readonly struct GCOomData HandleType[] GetHandleTypes(uint[] types); // Gets the global allocation context pointer and limit void GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointer allocLimit); + // Returns the first block of a loader heap's linked list of reserved memory blocks, or TargetPointer.Null if empty + TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap); + // Returns the address and size of virtual memory for the given loader heap block + LoaderHeapBlockData GetLoaderHeapBlockData(TargetPointer block); + // Returns the next block in the loader heap linked list, or TargetPointer.Null if there are no more blocks + TargetPointer GetNextLoaderHeapBlock(TargetPointer block); ``` ## Version 1 @@ -178,6 +190,10 @@ Data descriptors used: | `GCAllocContext` | AllocBytes | VM | Number of bytes allocated on SOH by this context | | `GCAllocContext` | AllocBytesLoh | VM | Number of bytes allocated not on SOH by this context | | `EEAllocContext` | GCAllocationContext | VM | The `GCAllocContext` struct within an `EEAllocContext` | +| `LoaderHeap` | FirstBlock | VM | Pointer to the first `LoaderHeapBlock` in the linked list | +| `LoaderHeapBlock` | Next | VM | Pointer to the next `LoaderHeapBlock` in the linked list | +| `LoaderHeapBlock` | VirtualAddress | VM | Pointer to the start of the reserved virtual memory | +| `LoaderHeapBlock` | VirtualSize | VM | Size in bytes of the reserved virtual memory region | Global variables used: | Global Name | Type | Source | Purpose | @@ -740,3 +756,31 @@ void IGC.GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointe allocLimit = target.ReadPointer(globalAllocContextAddress + /* EEAllocContext::GCAllocationContext offset */ + /* GCAllocContext::Limit offset */); } ``` + +GetFirstLoaderHeapBlock +```csharp +TargetPointer IGC.GetFirstLoaderHeapBlock(TargetPointer loaderHeap) +{ + return target.ReadPointer(loaderHeap + /* LoaderHeap::FirstBlock offset */); +} +``` + +GetLoaderHeapBlockData +```csharp +LoaderHeapBlockData IGC.GetLoaderHeapBlockData(TargetPointer block) +{ + return new LoaderHeapBlockData + { + VirtualAddress = target.ReadPointer(block + /* LoaderHeapBlock::VirtualAddress offset */), + VirtualSize = target.ReadNUInt(block + /* LoaderHeapBlock::VirtualSize offset */), + }; +} +``` + +GetNextLoaderHeapBlock +```csharp +TargetPointer IGC.GetNextLoaderHeapBlock(TargetPointer block) +{ + return target.ReadPointer(block + /* LoaderHeapBlock::Next offset */); +} +``` diff --git a/src/coreclr/inc/loaderheap.h b/src/coreclr/inc/loaderheap.h index 79228c7eb5410d..ddd4f2ab3f8e0b 100644 --- a/src/coreclr/inc/loaderheap.h +++ b/src/coreclr/inc/loaderheap.h @@ -99,6 +99,8 @@ struct TaggedMemAllocPtr #define VIRTUAL_ALLOC_RESERVE_GRANULARITY (64*1024) // 0x10000 (64 KB) +template struct cdac_data; + typedef DPTR(struct LoaderHeapBlock) PTR_LoaderHeapBlock; struct LoaderHeapBlock @@ -143,6 +145,13 @@ struct LoaderHeapBlock #endif }; +template<> +struct cdac_data +{ + static constexpr size_t Next = offsetof(LoaderHeapBlock, pNext); + static constexpr size_t VirtualAddress = offsetof(LoaderHeapBlock, pVirtualAddress); + static constexpr size_t VirtualSize = offsetof(LoaderHeapBlock, dwVirtualSize); +}; struct LoaderHeapFreeBlock; @@ -199,6 +208,14 @@ typedef bool EnumPageRegionsCallback (PTR_VOID pvArgs, PTR_VOID pvAllocationBase protected: // Linked list of ClrVirtualAlloc'd pages PTR_LoaderHeapBlock m_pFirstBlock; + + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t FirstBlock = offsetof(UnlockedLoaderHeapBaseTraversable, m_pFirstBlock); }; //=============================================================================== diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 46dee01532bd27..cdc88e10589d84 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -250,6 +250,17 @@ CDAC_TYPE_FIELD(LoaderAllocator, /*pointer*/, StubHeap, cdac_data::ObjectHandle) CDAC_TYPE_END(LoaderAllocator) +CDAC_TYPE_BEGIN(LoaderHeap) +CDAC_TYPE_INDETERMINATE(LoaderHeap) +CDAC_TYPE_FIELD(LoaderHeap, /*pointer*/, FirstBlock, cdac_data::FirstBlock) +CDAC_TYPE_END(LoaderHeap) + +CDAC_TYPE_BEGIN(LoaderHeapBlock) +CDAC_TYPE_FIELD(LoaderHeapBlock, /*pointer*/, Next, cdac_data::Next) +CDAC_TYPE_FIELD(LoaderHeapBlock, /*pointer*/, VirtualAddress, cdac_data::VirtualAddress) +CDAC_TYPE_FIELD(LoaderHeapBlock, /*nuint*/, VirtualSize, cdac_data::VirtualSize) +CDAC_TYPE_END(LoaderHeapBlock) + CDAC_TYPE_BEGIN(PEAssembly) CDAC_TYPE_INDETERMINATE(PEAssembly) CDAC_TYPE_FIELD(PEAssembly, /*pointer*/, PEImage, cdac_data::PEImage) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs index 761219b245b431..96773a90318fed 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs @@ -98,6 +98,12 @@ public readonly struct GCOomData public bool LohP { get; init; } } +public readonly struct LoaderHeapBlockData +{ + public TargetPointer VirtualAddress { get; init; } + public TargetNUInt VirtualSize { get; init; } +} + public interface IGC : IContract { static string IContract.Name { get; } = nameof(GC); @@ -128,6 +134,13 @@ public interface IGC : IContract HandleType[] GetHandleTypes(uint[] types) => throw new NotImplementedException(); void GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointer allocLimit) => 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 address and size of virtual memory for the given loader heap block + LoaderHeapBlockData GetLoaderHeapBlockData(TargetPointer block) => throw new NotImplementedException(); + // Returns the next block in the loader heap linked list, or TargetPointer.Null if there are no more blocks + TargetPointer GetNextLoaderHeapBlock(TargetPointer block) => throw new NotImplementedException(); } public readonly struct GC : IGC 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 4ffb56a54be88b..0376143b8399ef 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -41,6 +41,8 @@ public enum DataType SystemDomain, Assembly, LoaderAllocator, + LoaderHeap, + LoaderHeapBlock, PEAssembly, AssemblyBinder, PEImage, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs index 935a224cebf7ce..b65185a4c1095a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs @@ -278,6 +278,28 @@ void IGC.GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointe allocLimit = eeAllocContext.GCAllocationContext.Limit; } + TargetPointer IGC.GetFirstLoaderHeapBlock(TargetPointer loaderHeap) + { + Data.LoaderHeap heap = _target.ProcessedData.GetOrAdd(loaderHeap); + return heap.FirstBlock; + } + + LoaderHeapBlockData IGC.GetLoaderHeapBlockData(TargetPointer block) + { + Data.LoaderHeapBlock blockData = _target.ProcessedData.GetOrAdd(block); + return new LoaderHeapBlockData + { + VirtualAddress = blockData.VirtualAddress, + VirtualSize = blockData.VirtualSize, + }; + } + + TargetPointer IGC.GetNextLoaderHeapBlock(TargetPointer block) + { + Data.LoaderHeapBlock blockData = _target.ProcessedData.GetOrAdd(block); + return blockData.Next; + } + private GCType GetGCType() { string[] identifiers = ((IGC)this).GetGCIdentifiers(); 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/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index c6519ec65f655a..2afd447d0fd904 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -4882,7 +4882,33 @@ int ISOSDacInterface12.GetGlobalAllocationContext(ClrDataAddress* allocPtr, ClrD #region ISOSDacInterface13 int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*LoaderHeapKind*/ int kind, /*VISITHEAP*/ delegate* unmanaged pCallback) - => _legacyImpl13 is not null ? _legacyImpl13.TraverseLoaderHeap(loaderHeapAddr, kind, pCallback) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + try + { + if (loaderHeapAddr == 0) + throw new ArgumentException(); + if (pCallback is null) + throw new ArgumentException(); + + Contracts.IGC gc = _target.Contracts.GC; + TargetPointer heapAddr = loaderHeapAddr.ToTargetPointer(_target); + TargetPointer block = gc.GetFirstLoaderHeapBlock(heapAddr); + while (block != TargetPointer.Null) + { + Contracts.LoaderHeapBlockData blockData = gc.GetLoaderHeapBlockData(block); + Interop.BOOL cont = pCallback(blockData.VirtualAddress.Value, (nuint)blockData.VirtualSize.Value); + if (cont == Interop.BOOL.FALSE) + break; + block = gc.GetNextLoaderHeapBlock(block); + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + return hr; + } int ISOSDacInterface13.GetDomainLoaderAllocator(ClrDataAddress domainAddress, ClrDataAddress* pLoaderAllocator) { int hr = HResults.S_OK; diff --git a/src/native/managed/cdac/tests/GCLoaderHeapTests.cs b/src/native/managed/cdac/tests/GCLoaderHeapTests.cs new file mode 100644 index 00000000000000..1ac8d252164b31 --- /dev/null +++ b/src/native/managed/cdac/tests/GCLoaderHeapTests.cs @@ -0,0 +1,171 @@ +// 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 Moq; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.Tests; + +public class GCLoaderHeapTests +{ + private const ulong DefaultAllocationRangeStart = 0x0005_0000; + private const ulong DefaultAllocationRangeEnd = 0x0006_0000; + + private static readonly MockDescriptors.TypeFields LoaderHeapFields = new MockDescriptors.TypeFields() + { + DataType = DataType.LoaderHeap, + Fields = + [ + new(nameof(Data.LoaderHeap.FirstBlock), DataType.pointer), + ] + }; + + private static readonly MockDescriptors.TypeFields LoaderHeapBlockFields = new MockDescriptors.TypeFields() + { + DataType = DataType.LoaderHeapBlock, + Fields = + [ + new(nameof(Data.LoaderHeapBlock.Next), DataType.pointer), + new(nameof(Data.LoaderHeapBlock.VirtualAddress), DataType.pointer), + new(nameof(Data.LoaderHeapBlock.VirtualSize), DataType.nuint), + ] + }; + + private static Dictionary GetTypes(TargetTestHelpers helpers) + { + return MockDescriptors.GetTypesForTypeFields(helpers, [LoaderHeapFields, LoaderHeapBlockFields]); + } + + private static Target CreateTarget(MockTarget.Architecture arch, Dictionary types, MockMemorySpace.Builder builder) + { + (string Name, ulong Value)[] globals = + [ + (nameof(Constants.Globals.HandlesPerBlock), 1), + (nameof(Constants.Globals.BlockInvalid), 0xFF), + (nameof(Constants.Globals.DebugDestroyedHandleValue), 0), + (nameof(Constants.Globals.HandleMaxInternalTypes), 1), + ]; + var target = new TestPlaceholderTarget(arch, builder.GetMemoryContext().ReadFromTarget, types, globals); + target.SetContracts(Mock.Of( + c => c.GC == ((IContractFactory)new GCFactory()).CreateContract(target, 1))); + return target; + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void EmptyLoaderHeap(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockMemorySpace.BumpAllocator allocator = builder.CreateAllocator(DefaultAllocationRangeStart, DefaultAllocationRangeEnd); + Dictionary types = GetTypes(helpers); + + // Allocate a loader heap with no blocks + Target.TypeInfo heapType = types[DataType.LoaderHeap]; + MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "LoaderHeap"); + // FirstBlock is zero (null) by default + builder.AddHeapFragment(heapFragment); + + Target target = CreateTarget(arch, types, builder); + IGC gc = target.Contracts.GC; + + TargetPointer firstBlock = gc.GetFirstLoaderHeapBlock(heapFragment.Address); + Assert.Equal(TargetPointer.Null, firstBlock); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SingleBlockLoaderHeap(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockMemorySpace.BumpAllocator allocator = builder.CreateAllocator(DefaultAllocationRangeStart, DefaultAllocationRangeEnd); + Dictionary types = GetTypes(helpers); + + Target.TypeInfo heapType = types[DataType.LoaderHeap]; + Target.TypeInfo blockType = types[DataType.LoaderHeapBlock]; + + // Allocate a single block + ulong virtualAddress = 0x1234_0000UL; + ulong virtualSize = 0x1000UL; + MockMemorySpace.HeapFragment blockFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(blockType), "LoaderHeapBlock"); + helpers.WritePointer(blockFragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.Next)].Offset, helpers.PointerSize), TargetPointer.Null.Value); + helpers.WritePointer(blockFragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualAddress)].Offset, helpers.PointerSize), virtualAddress); + helpers.WriteNUInt(blockFragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualSize)].Offset, helpers.PointerSize), new TargetNUInt(virtualSize)); + builder.AddHeapFragment(blockFragment); + + // Allocate the heap pointing to the single block + MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "LoaderHeap"); + helpers.WritePointer(heapFragment.Data.AsSpan().Slice(heapType.Fields[nameof(Data.LoaderHeap.FirstBlock)].Offset, helpers.PointerSize), blockFragment.Address); + builder.AddHeapFragment(heapFragment); + + Target target = CreateTarget(arch, types, builder); + IGC gc = target.Contracts.GC; + + TargetPointer firstBlock = gc.GetFirstLoaderHeapBlock(heapFragment.Address); + Assert.Equal((TargetPointer)blockFragment.Address, firstBlock); + + LoaderHeapBlockData data = gc.GetLoaderHeapBlockData(firstBlock); + Assert.Equal(virtualAddress, data.VirtualAddress.Value); + Assert.Equal(virtualSize, data.VirtualSize.Value); + + TargetPointer nextBlock = gc.GetNextLoaderHeapBlock(firstBlock); + Assert.Equal(TargetPointer.Null, nextBlock); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void MultipleBlockLoaderHeap(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockMemorySpace.BumpAllocator allocator = builder.CreateAllocator(DefaultAllocationRangeStart, DefaultAllocationRangeEnd); + Dictionary types = GetTypes(helpers); + + Target.TypeInfo heapType = types[DataType.LoaderHeap]; + Target.TypeInfo blockType = types[DataType.LoaderHeapBlock]; + + // Create two blocks + ulong[] virtualAddresses = [0x1000_0000UL, 0x2000_0000UL]; + ulong[] virtualSizes = [0x8000UL, 0x10000UL]; + + // Allocate second block (next = null) + MockMemorySpace.HeapFragment block2Fragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(blockType), "LoaderHeapBlock2"); + helpers.WritePointer(block2Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.Next)].Offset, helpers.PointerSize), TargetPointer.Null.Value); + helpers.WritePointer(block2Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualAddress)].Offset, helpers.PointerSize), virtualAddresses[1]); + helpers.WriteNUInt(block2Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualSize)].Offset, helpers.PointerSize), new TargetNUInt(virtualSizes[1])); + builder.AddHeapFragment(block2Fragment); + + // Allocate first block (next = block2) + MockMemorySpace.HeapFragment block1Fragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(blockType), "LoaderHeapBlock1"); + helpers.WritePointer(block1Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.Next)].Offset, helpers.PointerSize), block2Fragment.Address); + helpers.WritePointer(block1Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualAddress)].Offset, helpers.PointerSize), virtualAddresses[0]); + helpers.WriteNUInt(block1Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualSize)].Offset, helpers.PointerSize), new TargetNUInt(virtualSizes[0])); + builder.AddHeapFragment(block1Fragment); + + // Allocate the heap pointing to the first block + MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "LoaderHeap"); + helpers.WritePointer(heapFragment.Data.AsSpan().Slice(heapType.Fields[nameof(Data.LoaderHeap.FirstBlock)].Offset, helpers.PointerSize), block1Fragment.Address); + builder.AddHeapFragment(heapFragment); + + Target target = CreateTarget(arch, types, builder); + IGC gc = target.Contracts.GC; + + // Traverse the heap blocks + List<(ulong Address, ulong Size)> blocks = []; + TargetPointer block = gc.GetFirstLoaderHeapBlock(heapFragment.Address); + while (block != TargetPointer.Null) + { + LoaderHeapBlockData data = gc.GetLoaderHeapBlockData(block); + blocks.Add((data.VirtualAddress.Value, data.VirtualSize.Value)); + block = gc.GetNextLoaderHeapBlock(block); + } + + Assert.Equal(2, blocks.Count); + Assert.Equal((virtualAddresses[0], virtualSizes[0]), blocks[0]); + Assert.Equal((virtualAddresses[1], virtualSizes[1]), blocks[1]); + } +} From ae89c941ec257a690de8e0a7b61fcddf5f6ab8ec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 17:37:25 +0000 Subject: [PATCH 03/31] Address code review: add kind validation, debug validation block, and typo fix Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- docs/design/datacontracts/GC.md | 2 +- .../SOSDacImpl.cs | 53 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index 42603d55c8cae8..e4a0e8977e98df 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -103,7 +103,7 @@ public readonly struct LoaderHeapBlockData IEnumerable GetGCHeaps(); // The following APIs have both a workstation and serer variant. - // The workstation variant implitly operates on the global heap. + // 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/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index 2afd447d0fd904..80fe713b6673b4 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -4881,8 +4881,33 @@ int ISOSDacInterface12.GetGlobalAllocationContext(ClrDataAddress* allocPtr, ClrD #endregion ISOSDacInterface12 #region ISOSDacInterface13 +#if DEBUG + [ThreadStatic] + private static List<(ulong VirtualAddress, nuint VirtualSize)>? _debugTraverseLoaderHeapBlocks; + + [UnmanagedCallersOnly] + private static Interop.BOOL TraverseLoaderHeapDebugCallback(ulong virtualAddress, nuint virtualSize) + { + List<(ulong VirtualAddress, nuint VirtualSize)>? expected = _debugTraverseLoaderHeapBlocks; + if (expected is not null) + { + bool found = expected.Remove((virtualAddress, virtualSize)); + Debug.Assert(found, $"Unexpected loader heap block: address={virtualAddress:x}, size={virtualSize:x}"); + } + return Interop.BOOL.TRUE; + } +#endif + int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*LoaderHeapKind*/ int kind, /*VISITHEAP*/ delegate* unmanaged pCallback) { + // Both LoaderHeapKindNormal (0) and LoaderHeapKindExplicitControl (1) use + // the same FirstBlock offset via UnlockedLoaderHeapBaseTraversable. + // Unknown kind values return E_NOTIMPL to match the native DAC behavior. + const int LoaderHeapKindNormal = 0; + const int LoaderHeapKindExplicitControl = 1; + if (kind != LoaderHeapKindNormal && kind != LoaderHeapKindExplicitControl) + return HResults.E_NOTIMPL; + int hr = HResults.S_OK; try { @@ -4907,6 +4932,34 @@ int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*Loade { hr = ex.HResult; } +#if DEBUG + if (_legacyImpl13 is not null) + { + // Collect expected blocks via a second cDAC traversal (not re-invoking the original callback). + List<(ulong VirtualAddress, nuint VirtualSize)> cdacBlocks = []; + try + { + Contracts.IGC gc = _target.Contracts.GC; + TargetPointer heapAddr = loaderHeapAddr.ToTargetPointer(_target); + TargetPointer block = gc.GetFirstLoaderHeapBlock(heapAddr); + while (block != TargetPointer.Null) + { + Contracts.LoaderHeapBlockData blockData = gc.GetLoaderHeapBlockData(block); + cdacBlocks.Add((blockData.VirtualAddress.Value, (nuint)blockData.VirtualSize.Value)); + block = gc.GetNextLoaderHeapBlock(block); + } + } + catch { } + + _debugTraverseLoaderHeapBlocks = cdacBlocks; + delegate* unmanaged debugCallbackPtr = &TraverseLoaderHeapDebugCallback; + int hrLocal = _legacyImpl13.TraverseLoaderHeap(loaderHeapAddr, kind, debugCallbackPtr); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + Debug.Assert(_debugTraverseLoaderHeapBlocks!.Count == 0, + $"cDAC found {cdacBlocks.Count} blocks but DAC found {cdacBlocks.Count - _debugTraverseLoaderHeapBlocks.Count} matching blocks"); + _debugTraverseLoaderHeapBlocks = null; + } +#endif return hr; } int ISOSDacInterface13.GetDomainLoaderAllocator(ClrDataAddress domainAddress, ClrDataAddress* pLoaderAllocator) From 80a66556d3876c00fe2ea60cabd5ed7542337dae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 17:51:12 +0000 Subject: [PATCH 04/31] Move LoaderHeap APIs from GC contract to Loader contract Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- docs/design/datacontracts/GC.md | 43 ------------------- docs/design/datacontracts/Loader.md | 43 +++++++++++++++++++ .../Contracts/IGC.cs | 13 ------ .../Contracts/ILoader.cs | 13 ++++++ .../Contracts/GC/GC_1.cs | 22 ---------- .../Contracts/Loader_1.cs | 22 ++++++++++ .../SOSDacImpl.cs | 16 +++---- ...CLoaderHeapTests.cs => LoaderHeapTests.cs} | 33 ++++++-------- 8 files changed, 99 insertions(+), 106 deletions(-) rename src/native/managed/cdac/tests/{GCLoaderHeapTests.cs => LoaderHeapTests.cs} (87%) diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index e4a0e8977e98df..cba389db055f35 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -66,12 +66,6 @@ public readonly struct GCOomData public TargetNUInt AvailablePagefileMB { get; init; } public bool LohP { get; init; } } - -public readonly struct LoaderHeapBlockData -{ - public TargetPointer VirtualAddress { get; init; } - public TargetNUInt VirtualSize { get; init; } -} ``` ```csharp @@ -122,12 +116,6 @@ public readonly struct LoaderHeapBlockData HandleType[] GetHandleTypes(uint[] types); // Gets the global allocation context pointer and limit void GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointer allocLimit); - // Returns the first block of a loader heap's linked list of reserved memory blocks, or TargetPointer.Null if empty - TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap); - // Returns the address and size of virtual memory for the given loader heap block - LoaderHeapBlockData GetLoaderHeapBlockData(TargetPointer block); - // Returns the next block in the loader heap linked list, or TargetPointer.Null if there are no more blocks - TargetPointer GetNextLoaderHeapBlock(TargetPointer block); ``` ## Version 1 @@ -190,10 +178,6 @@ Data descriptors used: | `GCAllocContext` | AllocBytes | VM | Number of bytes allocated on SOH by this context | | `GCAllocContext` | AllocBytesLoh | VM | Number of bytes allocated not on SOH by this context | | `EEAllocContext` | GCAllocationContext | VM | The `GCAllocContext` struct within an `EEAllocContext` | -| `LoaderHeap` | FirstBlock | VM | Pointer to the first `LoaderHeapBlock` in the linked list | -| `LoaderHeapBlock` | Next | VM | Pointer to the next `LoaderHeapBlock` in the linked list | -| `LoaderHeapBlock` | VirtualAddress | VM | Pointer to the start of the reserved virtual memory | -| `LoaderHeapBlock` | VirtualSize | VM | Size in bytes of the reserved virtual memory region | Global variables used: | Global Name | Type | Source | Purpose | @@ -757,30 +741,3 @@ void IGC.GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointe } ``` -GetFirstLoaderHeapBlock -```csharp -TargetPointer IGC.GetFirstLoaderHeapBlock(TargetPointer loaderHeap) -{ - return target.ReadPointer(loaderHeap + /* LoaderHeap::FirstBlock offset */); -} -``` - -GetLoaderHeapBlockData -```csharp -LoaderHeapBlockData IGC.GetLoaderHeapBlockData(TargetPointer block) -{ - return new LoaderHeapBlockData - { - VirtualAddress = target.ReadPointer(block + /* LoaderHeapBlock::VirtualAddress offset */), - VirtualSize = target.ReadNUInt(block + /* LoaderHeapBlock::VirtualSize offset */), - }; -} -``` - -GetNextLoaderHeapBlock -```csharp -TargetPointer IGC.GetNextLoaderHeapBlock(TargetPointer block) -{ - return target.ReadPointer(block + /* LoaderHeapBlock::Next offset */); -} -``` diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 4fd867a1024477..d2a1ea4204e878 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -86,6 +86,20 @@ TargetPointer GetStubHeap(TargetPointer loaderAllocatorPointer); TargetPointer GetObjectHandle(TargetPointer loaderAllocatorPointer); TargetPointer GetILHeader(ModuleHandle handle, uint token); TargetPointer GetDynamicIL(ModuleHandle handle, uint token); + +// Loader heap traversal +readonly struct LoaderHeapBlockData +{ + public TargetPointer VirtualAddress { get; init; } + public TargetNUInt VirtualSize { get; init; } +} + +// 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 address and size of virtual memory for the given loader heap block +LoaderHeapBlockData GetLoaderHeapBlockData(TargetPointer block); +// Returns the next block in the loader heap linked list, or TargetPointer.Null if there are no more blocks +TargetPointer GetNextLoaderHeapBlock(TargetPointer block); ``` ## Version 1 @@ -159,6 +173,10 @@ TargetPointer GetDynamicIL(ModuleHandle handle, uint token); | `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 | @@ -761,3 +779,28 @@ class InstMethodHashTable } } ``` + +#### GetFirstLoaderHeapBlock, GetLoaderHeapBlockData, GetNextLoaderHeapBlock + +`LoaderHeap` instances use `UnlockedLoaderHeapBaseTraversable` as their primary base class. Traversal follows the `m_pFirstBlock` linked list via `LoaderHeapBlock::pNext`. + +```csharp +TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap) +{ + return target.ReadPointer(loaderHeap + /* LoaderHeap::FirstBlock offset */); +} + +LoaderHeapBlockData ILoader.GetLoaderHeapBlockData(TargetPointer block) +{ + return new LoaderHeapBlockData + { + VirtualAddress = target.ReadPointer(block + /* LoaderHeapBlock::VirtualAddress offset */), + VirtualSize = target.ReadNUInt(block + /* LoaderHeapBlock::VirtualSize offset */), + }; +} + +TargetPointer ILoader.GetNextLoaderHeapBlock(TargetPointer block) +{ + return target.ReadPointer(block + /* LoaderHeapBlock::Next offset */); +} +``` diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs index 96773a90318fed..761219b245b431 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs @@ -98,12 +98,6 @@ public readonly struct GCOomData public bool LohP { get; init; } } -public readonly struct LoaderHeapBlockData -{ - public TargetPointer VirtualAddress { get; init; } - public TargetNUInt VirtualSize { get; init; } -} - public interface IGC : IContract { static string IContract.Name { get; } = nameof(GC); @@ -134,13 +128,6 @@ public interface IGC : IContract HandleType[] GetHandleTypes(uint[] types) => throw new NotImplementedException(); void GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointer allocLimit) => 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 address and size of virtual memory for the given loader heap block - LoaderHeapBlockData GetLoaderHeapBlockData(TargetPointer block) => throw new NotImplementedException(); - // Returns the next block in the loader heap linked list, or TargetPointer.Null if there are no more blocks - TargetPointer GetNextLoaderHeapBlock(TargetPointer block) => throw new NotImplementedException(); } public readonly struct GC : IGC 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..fb392810c1db57 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 @@ -16,6 +16,12 @@ public ModuleHandle(TargetPointer address) public TargetPointer Address { get; } } +public readonly struct LoaderHeapBlockData +{ + public TargetPointer VirtualAddress { get; init; } + public TargetNUInt VirtualSize { get; init; } +} + [Flags] public enum ModuleFlags { @@ -94,6 +100,13 @@ 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 address and size of virtual memory for the given loader heap block + LoaderHeapBlockData GetLoaderHeapBlockData(TargetPointer block) => throw new NotImplementedException(); + // Returns the next block in the loader heap linked list, or TargetPointer.Null if there are no more blocks + TargetPointer GetNextLoaderHeapBlock(TargetPointer block) => throw new NotImplementedException(); } public readonly struct Loader : ILoader diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs index b65185a4c1095a..935a224cebf7ce 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs @@ -278,28 +278,6 @@ void IGC.GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointe allocLimit = eeAllocContext.GCAllocationContext.Limit; } - TargetPointer IGC.GetFirstLoaderHeapBlock(TargetPointer loaderHeap) - { - Data.LoaderHeap heap = _target.ProcessedData.GetOrAdd(loaderHeap); - return heap.FirstBlock; - } - - LoaderHeapBlockData IGC.GetLoaderHeapBlockData(TargetPointer block) - { - Data.LoaderHeapBlock blockData = _target.ProcessedData.GetOrAdd(block); - return new LoaderHeapBlockData - { - VirtualAddress = blockData.VirtualAddress, - VirtualSize = blockData.VirtualSize, - }; - } - - TargetPointer IGC.GetNextLoaderHeapBlock(TargetPointer block) - { - Data.LoaderHeapBlock blockData = _target.ProcessedData.GetOrAdd(block); - return blockData.Next; - } - private GCType GetGCType() { string[] identifiers = ((IGC)this).GetGCIdentifiers(); 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..254099a3f32d9e 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,26 @@ TargetPointer ILoader.GetDynamicIL(ModuleHandle handle, uint token) ISHash shashContract = _target.Contracts.SHash; return shashContract.LookupSHash(dynamicILBlobTable.HashTable, token).EntryIL; } + + TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap) + { + Data.LoaderHeap heap = _target.ProcessedData.GetOrAdd(loaderHeap); + return heap.FirstBlock; + } + + LoaderHeapBlockData ILoader.GetLoaderHeapBlockData(TargetPointer block) + { + Data.LoaderHeapBlock blockData = _target.ProcessedData.GetOrAdd(block); + return new LoaderHeapBlockData + { + VirtualAddress = blockData.VirtualAddress, + VirtualSize = blockData.VirtualSize, + }; + } + + TargetPointer ILoader.GetNextLoaderHeapBlock(TargetPointer block) + { + Data.LoaderHeapBlock blockData = _target.ProcessedData.GetOrAdd(block); + return blockData.Next; + } } 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 80fe713b6673b4..abbd86e1d60586 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -4916,16 +4916,16 @@ int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*Loade if (pCallback is null) throw new ArgumentException(); - Contracts.IGC gc = _target.Contracts.GC; + Contracts.ILoader loader = _target.Contracts.Loader; TargetPointer heapAddr = loaderHeapAddr.ToTargetPointer(_target); - TargetPointer block = gc.GetFirstLoaderHeapBlock(heapAddr); + TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr); while (block != TargetPointer.Null) { - Contracts.LoaderHeapBlockData blockData = gc.GetLoaderHeapBlockData(block); + Contracts.LoaderHeapBlockData blockData = loader.GetLoaderHeapBlockData(block); Interop.BOOL cont = pCallback(blockData.VirtualAddress.Value, (nuint)blockData.VirtualSize.Value); if (cont == Interop.BOOL.FALSE) break; - block = gc.GetNextLoaderHeapBlock(block); + block = loader.GetNextLoaderHeapBlock(block); } } catch (System.Exception ex) @@ -4939,14 +4939,14 @@ int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*Loade List<(ulong VirtualAddress, nuint VirtualSize)> cdacBlocks = []; try { - Contracts.IGC gc = _target.Contracts.GC; + Contracts.ILoader loader = _target.Contracts.Loader; TargetPointer heapAddr = loaderHeapAddr.ToTargetPointer(_target); - TargetPointer block = gc.GetFirstLoaderHeapBlock(heapAddr); + TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr); while (block != TargetPointer.Null) { - Contracts.LoaderHeapBlockData blockData = gc.GetLoaderHeapBlockData(block); + Contracts.LoaderHeapBlockData blockData = loader.GetLoaderHeapBlockData(block); cdacBlocks.Add((blockData.VirtualAddress.Value, (nuint)blockData.VirtualSize.Value)); - block = gc.GetNextLoaderHeapBlock(block); + block = loader.GetNextLoaderHeapBlock(block); } } catch { } diff --git a/src/native/managed/cdac/tests/GCLoaderHeapTests.cs b/src/native/managed/cdac/tests/LoaderHeapTests.cs similarity index 87% rename from src/native/managed/cdac/tests/GCLoaderHeapTests.cs rename to src/native/managed/cdac/tests/LoaderHeapTests.cs index 1ac8d252164b31..5bbc678a8b0196 100644 --- a/src/native/managed/cdac/tests/GCLoaderHeapTests.cs +++ b/src/native/managed/cdac/tests/LoaderHeapTests.cs @@ -9,7 +9,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Tests; -public class GCLoaderHeapTests +public class LoaderHeapTests { private const ulong DefaultAllocationRangeStart = 0x0005_0000; private const ulong DefaultAllocationRangeEnd = 0x0006_0000; @@ -41,16 +41,9 @@ public class GCLoaderHeapTests private static Target CreateTarget(MockTarget.Architecture arch, Dictionary types, MockMemorySpace.Builder builder) { - (string Name, ulong Value)[] globals = - [ - (nameof(Constants.Globals.HandlesPerBlock), 1), - (nameof(Constants.Globals.BlockInvalid), 0xFF), - (nameof(Constants.Globals.DebugDestroyedHandleValue), 0), - (nameof(Constants.Globals.HandleMaxInternalTypes), 1), - ]; - var target = new TestPlaceholderTarget(arch, builder.GetMemoryContext().ReadFromTarget, types, globals); + var target = new TestPlaceholderTarget(arch, builder.GetMemoryContext().ReadFromTarget, types); target.SetContracts(Mock.Of( - c => c.GC == ((IContractFactory)new GCFactory()).CreateContract(target, 1))); + c => c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1))); return target; } @@ -70,9 +63,9 @@ public void EmptyLoaderHeap(MockTarget.Architecture arch) builder.AddHeapFragment(heapFragment); Target target = CreateTarget(arch, types, builder); - IGC gc = target.Contracts.GC; + ILoader loader = target.Contracts.Loader; - TargetPointer firstBlock = gc.GetFirstLoaderHeapBlock(heapFragment.Address); + TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address); Assert.Equal(TargetPointer.Null, firstBlock); } @@ -103,16 +96,16 @@ public void SingleBlockLoaderHeap(MockTarget.Architecture arch) builder.AddHeapFragment(heapFragment); Target target = CreateTarget(arch, types, builder); - IGC gc = target.Contracts.GC; + ILoader loader = target.Contracts.Loader; - TargetPointer firstBlock = gc.GetFirstLoaderHeapBlock(heapFragment.Address); + TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address); Assert.Equal((TargetPointer)blockFragment.Address, firstBlock); - LoaderHeapBlockData data = gc.GetLoaderHeapBlockData(firstBlock); + LoaderHeapBlockData data = loader.GetLoaderHeapBlockData(firstBlock); Assert.Equal(virtualAddress, data.VirtualAddress.Value); Assert.Equal(virtualSize, data.VirtualSize.Value); - TargetPointer nextBlock = gc.GetNextLoaderHeapBlock(firstBlock); + TargetPointer nextBlock = loader.GetNextLoaderHeapBlock(firstBlock); Assert.Equal(TargetPointer.Null, nextBlock); } @@ -152,16 +145,16 @@ public void MultipleBlockLoaderHeap(MockTarget.Architecture arch) builder.AddHeapFragment(heapFragment); Target target = CreateTarget(arch, types, builder); - IGC gc = target.Contracts.GC; + ILoader loader = target.Contracts.Loader; // Traverse the heap blocks List<(ulong Address, ulong Size)> blocks = []; - TargetPointer block = gc.GetFirstLoaderHeapBlock(heapFragment.Address); + TargetPointer block = loader.GetFirstLoaderHeapBlock(heapFragment.Address); while (block != TargetPointer.Null) { - LoaderHeapBlockData data = gc.GetLoaderHeapBlockData(block); + LoaderHeapBlockData data = loader.GetLoaderHeapBlockData(block); blocks.Add((data.VirtualAddress.Value, data.VirtualSize.Value)); - block = gc.GetNextLoaderHeapBlock(block); + block = loader.GetNextLoaderHeapBlock(block); } Assert.Equal(2, blocks.Count); From 6c76e30dbbaa0e1ba7d016af7529a9b139693157 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 18:23:04 +0000 Subject: [PATCH 05/31] Use offsetof() directly in datadescriptor.inc instead of cdac_data structs for LoaderHeap types Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- src/coreclr/inc/loaderheap.h | 16 ---------------- src/coreclr/vm/datadescriptor/datadescriptor.inc | 8 ++++---- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/src/coreclr/inc/loaderheap.h b/src/coreclr/inc/loaderheap.h index ddd4f2ab3f8e0b..2015e1f6a4a78f 100644 --- a/src/coreclr/inc/loaderheap.h +++ b/src/coreclr/inc/loaderheap.h @@ -145,14 +145,6 @@ struct LoaderHeapBlock #endif }; -template<> -struct cdac_data -{ - static constexpr size_t Next = offsetof(LoaderHeapBlock, pNext); - static constexpr size_t VirtualAddress = offsetof(LoaderHeapBlock, pVirtualAddress); - static constexpr size_t VirtualSize = offsetof(LoaderHeapBlock, dwVirtualSize); -}; - struct LoaderHeapFreeBlock; // Collection of methods for helping in debugging heap corruptions @@ -208,14 +200,6 @@ typedef bool EnumPageRegionsCallback (PTR_VOID pvArgs, PTR_VOID pvAllocationBase protected: // Linked list of ClrVirtualAlloc'd pages PTR_LoaderHeapBlock m_pFirstBlock; - - friend struct ::cdac_data; -}; - -template<> -struct cdac_data -{ - static constexpr size_t FirstBlock = offsetof(UnlockedLoaderHeapBaseTraversable, m_pFirstBlock); }; //=============================================================================== diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index cdc88e10589d84..ff5e9b84c84927 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -252,13 +252,13 @@ CDAC_TYPE_END(LoaderAllocator) CDAC_TYPE_BEGIN(LoaderHeap) CDAC_TYPE_INDETERMINATE(LoaderHeap) -CDAC_TYPE_FIELD(LoaderHeap, /*pointer*/, FirstBlock, cdac_data::FirstBlock) +CDAC_TYPE_FIELD(LoaderHeap, /*pointer*/, FirstBlock, offsetof(UnlockedLoaderHeapBaseTraversable, m_pFirstBlock)) CDAC_TYPE_END(LoaderHeap) CDAC_TYPE_BEGIN(LoaderHeapBlock) -CDAC_TYPE_FIELD(LoaderHeapBlock, /*pointer*/, Next, cdac_data::Next) -CDAC_TYPE_FIELD(LoaderHeapBlock, /*pointer*/, VirtualAddress, cdac_data::VirtualAddress) -CDAC_TYPE_FIELD(LoaderHeapBlock, /*nuint*/, VirtualSize, cdac_data::VirtualSize) +CDAC_TYPE_FIELD(LoaderHeapBlock, /*pointer*/, Next, offsetof(LoaderHeapBlock, pNext)) +CDAC_TYPE_FIELD(LoaderHeapBlock, /*pointer*/, VirtualAddress, offsetof(LoaderHeapBlock, pVirtualAddress)) +CDAC_TYPE_FIELD(LoaderHeapBlock, /*nuint*/, VirtualSize, offsetof(LoaderHeapBlock, dwVirtualSize)) CDAC_TYPE_END(LoaderHeapBlock) CDAC_TYPE_BEGIN(PEAssembly) From c6ebd5a4a44fade8911222f61dd481948523ad8c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 18:34:11 +0000 Subject: [PATCH 06/31] Revert stray template cdac_data forward declaration from loaderheap.h Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- src/coreclr/inc/loaderheap.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/coreclr/inc/loaderheap.h b/src/coreclr/inc/loaderheap.h index 2015e1f6a4a78f..79228c7eb5410d 100644 --- a/src/coreclr/inc/loaderheap.h +++ b/src/coreclr/inc/loaderheap.h @@ -99,8 +99,6 @@ struct TaggedMemAllocPtr #define VIRTUAL_ALLOC_RESERVE_GRANULARITY (64*1024) // 0x10000 (64 KB) -template struct cdac_data; - typedef DPTR(struct LoaderHeapBlock) PTR_LoaderHeapBlock; struct LoaderHeapBlock @@ -145,6 +143,7 @@ struct LoaderHeapBlock #endif }; + struct LoaderHeapFreeBlock; // Collection of methods for helping in debugging heap corruptions From 2073ae134a251ad1bdfc074dd22f763c49584a30 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 19:36:01 +0000 Subject: [PATCH 07/31] Add LoaderHeapKind enum and separate ExplicitControlLoaderHeap cDAC type; embed kind check in GetFirstLoaderHeapBlock Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- docs/design/datacontracts/Loader.md | 30 +++- .../vm/datadescriptor/datadescriptor.inc | 5 + .../Contracts/ILoader.cs | 11 +- .../DataType.cs | 1 + .../Contracts/Loader_1.cs | 11 +- .../Data/ExplicitControlLoaderHeap.cs | 19 +++ .../SOSDacImpl.cs | 12 +- .../managed/cdac/tests/LoaderHeapTests.cs | 158 ++++++++++++++++-- 8 files changed, 211 insertions(+), 36 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExplicitControlLoaderHeap.cs diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index d2a1ea4204e878..2cf4bd196b4354 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -94,8 +94,15 @@ readonly struct LoaderHeapBlockData public TargetNUInt VirtualSize { get; init; } } -// Returns the first block of the loader heap linked list, or TargetPointer.Null if the heap has no blocks -TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap); +enum LoaderHeapKind +{ + Normal = 0, // UnlockedLoaderHeap / LoaderHeap + ExplicitControl = 1, // ExplicitControlLoaderHeap +} + +// Returns the first block of the loader heap linked list, or TargetPointer.Null if the heap has no blocks. +// Throws NotImplementedException for unknown kind values. +TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap, LoaderHeapKind kind); // Returns the address and size of virtual memory for the given loader heap block LoaderHeapBlockData GetLoaderHeapBlockData(TargetPointer block); // Returns the next block in the loader heap linked list, or TargetPointer.Null if there are no more blocks @@ -173,7 +180,8 @@ TargetPointer GetNextLoaderHeapBlock(TargetPointer block); | `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 | +| `LoaderHeap` | `FirstBlock` | Pointer to the first `LoaderHeapBlock` in the linked list (normal heaps: `UnlockedLoaderHeap`/`LoaderHeap`) | +| `ExplicitControlLoaderHeap` | `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 | @@ -782,12 +790,22 @@ class InstMethodHashTable #### GetFirstLoaderHeapBlock, GetLoaderHeapBlockData, GetNextLoaderHeapBlock -`LoaderHeap` instances use `UnlockedLoaderHeapBaseTraversable` as their primary base class. Traversal follows the `m_pFirstBlock` linked list via `LoaderHeapBlock::pNext`. +Both `UnlockedLoaderHeap`/`LoaderHeap` (normal) and `ExplicitControlLoaderHeap` (explicit-control) inherit from +`UnlockedLoaderHeapBaseTraversable`, which holds `m_pFirstBlock`. Each kind maps to a separate cDAC type +(`LoaderHeap` and `ExplicitControlLoaderHeap` respectively) so callers are explicit about which type they are +traversing. The `LoaderHeapKind` enum encodes the choice, and an unknown kind throws `NotImplementedException`. ```csharp -TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap) +TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap, LoaderHeapKind kind) { - return target.ReadPointer(loaderHeap + /* LoaderHeap::FirstBlock offset */); + return kind switch + { + LoaderHeapKind.Normal => + target.ReadPointer(loaderHeap + /* LoaderHeap::FirstBlock offset */), + LoaderHeapKind.ExplicitControl => + target.ReadPointer(loaderHeap + /* ExplicitControlLoaderHeap::FirstBlock offset */), + _ => throw new NotImplementedException($"Unknown loader heap kind: {kind}"), + }; } LoaderHeapBlockData ILoader.GetLoaderHeapBlockData(TargetPointer block) diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index ff5e9b84c84927..16289c2e8983b4 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -255,6 +255,11 @@ CDAC_TYPE_INDETERMINATE(LoaderHeap) CDAC_TYPE_FIELD(LoaderHeap, /*pointer*/, FirstBlock, offsetof(UnlockedLoaderHeapBaseTraversable, m_pFirstBlock)) CDAC_TYPE_END(LoaderHeap) +CDAC_TYPE_BEGIN(ExplicitControlLoaderHeap) +CDAC_TYPE_INDETERMINATE(ExplicitControlLoaderHeap) +CDAC_TYPE_FIELD(ExplicitControlLoaderHeap, /*pointer*/, FirstBlock, offsetof(UnlockedLoaderHeapBaseTraversable, m_pFirstBlock)) +CDAC_TYPE_END(ExplicitControlLoaderHeap) + CDAC_TYPE_BEGIN(LoaderHeapBlock) CDAC_TYPE_FIELD(LoaderHeapBlock, /*pointer*/, Next, offsetof(LoaderHeapBlock, pNext)) CDAC_TYPE_FIELD(LoaderHeapBlock, /*pointer*/, VirtualAddress, offsetof(LoaderHeapBlock, pVirtualAddress)) 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 fb392810c1db57..5df2f8b14c0df4 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 @@ -22,6 +22,12 @@ public readonly struct LoaderHeapBlockData public TargetNUInt VirtualSize { get; init; } } +public enum LoaderHeapKind +{ + Normal = 0, // UnlockedLoaderHeap / LoaderHeap + ExplicitControl = 1, // ExplicitControlLoaderHeap +} + [Flags] public enum ModuleFlags { @@ -101,8 +107,9 @@ public interface ILoader : IContract 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 first block of the loader heap linked list, or TargetPointer.Null if the heap has no blocks. + // Throws NotImplementedException for unknown kind values. + TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap, LoaderHeapKind kind) => throw new NotImplementedException(); // Returns the address and size of virtual memory for the given loader heap block LoaderHeapBlockData GetLoaderHeapBlockData(TargetPointer block) => throw new NotImplementedException(); // Returns the next block in the loader heap linked list, or TargetPointer.Null if there are no more blocks 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 0376143b8399ef..a2219019259205 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -42,6 +42,7 @@ public enum DataType Assembly, LoaderAllocator, LoaderHeap, + ExplicitControlLoaderHeap, LoaderHeapBlock, PEAssembly, AssemblyBinder, 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 254099a3f32d9e..388456cdd26338 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,10 +553,15 @@ TargetPointer ILoader.GetDynamicIL(ModuleHandle handle, uint token) return shashContract.LookupSHash(dynamicILBlobTable.HashTable, token).EntryIL; } - TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap) + TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap, LoaderHeapKind kind) { - Data.LoaderHeap heap = _target.ProcessedData.GetOrAdd(loaderHeap); - return heap.FirstBlock; + return kind switch + { + LoaderHeapKind.Normal => _target.ProcessedData.GetOrAdd(loaderHeap).FirstBlock, + LoaderHeapKind.ExplicitControl => _target.ProcessedData.GetOrAdd(loaderHeap).FirstBlock, + // NotImplementedException maps to E_NOTIMPL, matching native DAC behavior for unknown heap kinds. + _ => throw new NotImplementedException($"Unknown loader heap kind: {kind}"), + }; } LoaderHeapBlockData ILoader.GetLoaderHeapBlockData(TargetPointer block) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExplicitControlLoaderHeap.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExplicitControlLoaderHeap.cs new file mode 100644 index 00000000000000..dd93d88ffc5bf5 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExplicitControlLoaderHeap.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 ExplicitControlLoaderHeap : IData +{ + static ExplicitControlLoaderHeap IData.Create(Target target, TargetPointer address) + => new ExplicitControlLoaderHeap(target, address); + + public ExplicitControlLoaderHeap(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.ExplicitControlLoaderHeap); + + 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.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index abbd86e1d60586..fbe236789146bf 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -4900,14 +4900,6 @@ private static Interop.BOOL TraverseLoaderHeapDebugCallback(ulong virtualAddress int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*LoaderHeapKind*/ int kind, /*VISITHEAP*/ delegate* unmanaged pCallback) { - // Both LoaderHeapKindNormal (0) and LoaderHeapKindExplicitControl (1) use - // the same FirstBlock offset via UnlockedLoaderHeapBaseTraversable. - // Unknown kind values return E_NOTIMPL to match the native DAC behavior. - const int LoaderHeapKindNormal = 0; - const int LoaderHeapKindExplicitControl = 1; - if (kind != LoaderHeapKindNormal && kind != LoaderHeapKindExplicitControl) - return HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -4918,7 +4910,7 @@ int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*Loade Contracts.ILoader loader = _target.Contracts.Loader; TargetPointer heapAddr = loaderHeapAddr.ToTargetPointer(_target); - TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr); + TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr, (Contracts.LoaderHeapKind)kind); while (block != TargetPointer.Null) { Contracts.LoaderHeapBlockData blockData = loader.GetLoaderHeapBlockData(block); @@ -4941,7 +4933,7 @@ int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*Loade { Contracts.ILoader loader = _target.Contracts.Loader; TargetPointer heapAddr = loaderHeapAddr.ToTargetPointer(_target); - TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr); + TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr, (Contracts.LoaderHeapKind)kind); while (block != TargetPointer.Null) { Contracts.LoaderHeapBlockData blockData = loader.GetLoaderHeapBlockData(block); diff --git a/src/native/managed/cdac/tests/LoaderHeapTests.cs b/src/native/managed/cdac/tests/LoaderHeapTests.cs index 5bbc678a8b0196..1a15fcd9140bf9 100644 --- a/src/native/managed/cdac/tests/LoaderHeapTests.cs +++ b/src/native/managed/cdac/tests/LoaderHeapTests.cs @@ -23,6 +23,15 @@ public class LoaderHeapTests ] }; + private static readonly MockDescriptors.TypeFields ExplicitControlLoaderHeapFields = new MockDescriptors.TypeFields() + { + DataType = DataType.ExplicitControlLoaderHeap, + Fields = + [ + new(nameof(Data.ExplicitControlLoaderHeap.FirstBlock), DataType.pointer), + ] + }; + private static readonly MockDescriptors.TypeFields LoaderHeapBlockFields = new MockDescriptors.TypeFields() { DataType = DataType.LoaderHeapBlock, @@ -36,7 +45,7 @@ public class LoaderHeapTests private static Dictionary GetTypes(TargetTestHelpers helpers) { - return MockDescriptors.GetTypesForTypeFields(helpers, [LoaderHeapFields, LoaderHeapBlockFields]); + return MockDescriptors.GetTypesForTypeFields(helpers, [LoaderHeapFields, ExplicitControlLoaderHeapFields, LoaderHeapBlockFields]); } private static Target CreateTarget(MockTarget.Architecture arch, Dictionary types, MockMemorySpace.Builder builder) @@ -49,14 +58,13 @@ private static Target CreateTarget(MockTarget.Architecture arch, Dictionary types = GetTypes(helpers); - // Allocate a loader heap with no blocks Target.TypeInfo heapType = types[DataType.LoaderHeap]; MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "LoaderHeap"); // FirstBlock is zero (null) by default @@ -65,13 +73,53 @@ public void EmptyLoaderHeap(MockTarget.Architecture arch) Target target = CreateTarget(arch, types, builder); ILoader loader = target.Contracts.Loader; - TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address); + TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.Normal); + Assert.Equal(TargetPointer.Null, firstBlock); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void EmptyLoaderHeap_ExplicitControl(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockMemorySpace.BumpAllocator allocator = builder.CreateAllocator(DefaultAllocationRangeStart, DefaultAllocationRangeEnd); + Dictionary types = GetTypes(helpers); + + Target.TypeInfo heapType = types[DataType.ExplicitControlLoaderHeap]; + MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "ExplicitControlLoaderHeap"); + // FirstBlock is zero (null) by default + builder.AddHeapFragment(heapFragment); + + Target target = CreateTarget(arch, types, builder); + ILoader loader = target.Contracts.Loader; + + TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.ExplicitControl); Assert.Equal(TargetPointer.Null, firstBlock); } [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void SingleBlockLoaderHeap(MockTarget.Architecture arch) + public void UnknownKind_ThrowsNotImplemented(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockMemorySpace.BumpAllocator allocator = builder.CreateAllocator(DefaultAllocationRangeStart, DefaultAllocationRangeEnd); + Dictionary types = GetTypes(helpers); + + Target.TypeInfo heapType = types[DataType.LoaderHeap]; + MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "LoaderHeap"); + builder.AddHeapFragment(heapFragment); + + Target target = CreateTarget(arch, types, builder); + ILoader loader = target.Contracts.Loader; + + Assert.Throws(() => loader.GetFirstLoaderHeapBlock(heapFragment.Address, (LoaderHeapKind)99)); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SingleBlockLoaderHeap_Normal(MockTarget.Architecture arch) { TargetTestHelpers helpers = new(arch); MockMemorySpace.Builder builder = new(helpers); @@ -81,7 +129,6 @@ public void SingleBlockLoaderHeap(MockTarget.Architecture arch) Target.TypeInfo heapType = types[DataType.LoaderHeap]; Target.TypeInfo blockType = types[DataType.LoaderHeapBlock]; - // Allocate a single block ulong virtualAddress = 0x1234_0000UL; ulong virtualSize = 0x1000UL; MockMemorySpace.HeapFragment blockFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(blockType), "LoaderHeapBlock"); @@ -90,7 +137,6 @@ public void SingleBlockLoaderHeap(MockTarget.Architecture arch) helpers.WriteNUInt(blockFragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualSize)].Offset, helpers.PointerSize), new TargetNUInt(virtualSize)); builder.AddHeapFragment(blockFragment); - // Allocate the heap pointing to the single block MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "LoaderHeap"); helpers.WritePointer(heapFragment.Data.AsSpan().Slice(heapType.Fields[nameof(Data.LoaderHeap.FirstBlock)].Offset, helpers.PointerSize), blockFragment.Address); builder.AddHeapFragment(heapFragment); @@ -98,7 +144,7 @@ public void SingleBlockLoaderHeap(MockTarget.Architecture arch) Target target = CreateTarget(arch, types, builder); ILoader loader = target.Contracts.Loader; - TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address); + TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.Normal); Assert.Equal((TargetPointer)blockFragment.Address, firstBlock); LoaderHeapBlockData data = loader.GetLoaderHeapBlockData(firstBlock); @@ -111,7 +157,45 @@ public void SingleBlockLoaderHeap(MockTarget.Architecture arch) [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void MultipleBlockLoaderHeap(MockTarget.Architecture arch) + public void SingleBlockLoaderHeap_ExplicitControl(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockMemorySpace.BumpAllocator allocator = builder.CreateAllocator(DefaultAllocationRangeStart, DefaultAllocationRangeEnd); + Dictionary types = GetTypes(helpers); + + Target.TypeInfo heapType = types[DataType.ExplicitControlLoaderHeap]; + Target.TypeInfo blockType = types[DataType.LoaderHeapBlock]; + + ulong virtualAddress = 0x5678_0000UL; + ulong virtualSize = 0x2000UL; + MockMemorySpace.HeapFragment blockFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(blockType), "LoaderHeapBlock"); + helpers.WritePointer(blockFragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.Next)].Offset, helpers.PointerSize), TargetPointer.Null.Value); + helpers.WritePointer(blockFragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualAddress)].Offset, helpers.PointerSize), virtualAddress); + helpers.WriteNUInt(blockFragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualSize)].Offset, helpers.PointerSize), new TargetNUInt(virtualSize)); + builder.AddHeapFragment(blockFragment); + + MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "ExplicitControlLoaderHeap"); + helpers.WritePointer(heapFragment.Data.AsSpan().Slice(heapType.Fields[nameof(Data.ExplicitControlLoaderHeap.FirstBlock)].Offset, helpers.PointerSize), blockFragment.Address); + builder.AddHeapFragment(heapFragment); + + Target target = CreateTarget(arch, types, builder); + ILoader loader = target.Contracts.Loader; + + TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.ExplicitControl); + Assert.Equal((TargetPointer)blockFragment.Address, firstBlock); + + LoaderHeapBlockData data = loader.GetLoaderHeapBlockData(firstBlock); + Assert.Equal(virtualAddress, data.VirtualAddress.Value); + Assert.Equal(virtualSize, data.VirtualSize.Value); + + TargetPointer nextBlock = loader.GetNextLoaderHeapBlock(firstBlock); + Assert.Equal(TargetPointer.Null, nextBlock); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void MultipleBlockLoaderHeap_Normal(MockTarget.Architecture arch) { TargetTestHelpers helpers = new(arch); MockMemorySpace.Builder builder = new(helpers); @@ -121,25 +205,21 @@ public void MultipleBlockLoaderHeap(MockTarget.Architecture arch) Target.TypeInfo heapType = types[DataType.LoaderHeap]; Target.TypeInfo blockType = types[DataType.LoaderHeapBlock]; - // Create two blocks ulong[] virtualAddresses = [0x1000_0000UL, 0x2000_0000UL]; ulong[] virtualSizes = [0x8000UL, 0x10000UL]; - // Allocate second block (next = null) MockMemorySpace.HeapFragment block2Fragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(blockType), "LoaderHeapBlock2"); helpers.WritePointer(block2Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.Next)].Offset, helpers.PointerSize), TargetPointer.Null.Value); helpers.WritePointer(block2Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualAddress)].Offset, helpers.PointerSize), virtualAddresses[1]); helpers.WriteNUInt(block2Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualSize)].Offset, helpers.PointerSize), new TargetNUInt(virtualSizes[1])); builder.AddHeapFragment(block2Fragment); - // Allocate first block (next = block2) MockMemorySpace.HeapFragment block1Fragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(blockType), "LoaderHeapBlock1"); helpers.WritePointer(block1Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.Next)].Offset, helpers.PointerSize), block2Fragment.Address); helpers.WritePointer(block1Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualAddress)].Offset, helpers.PointerSize), virtualAddresses[0]); helpers.WriteNUInt(block1Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualSize)].Offset, helpers.PointerSize), new TargetNUInt(virtualSizes[0])); builder.AddHeapFragment(block1Fragment); - // Allocate the heap pointing to the first block MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "LoaderHeap"); helpers.WritePointer(heapFragment.Data.AsSpan().Slice(heapType.Fields[nameof(Data.LoaderHeap.FirstBlock)].Offset, helpers.PointerSize), block1Fragment.Address); builder.AddHeapFragment(heapFragment); @@ -147,9 +227,56 @@ public void MultipleBlockLoaderHeap(MockTarget.Architecture arch) Target target = CreateTarget(arch, types, builder); ILoader loader = target.Contracts.Loader; - // Traverse the heap blocks List<(ulong Address, ulong Size)> blocks = []; - TargetPointer block = loader.GetFirstLoaderHeapBlock(heapFragment.Address); + TargetPointer block = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.Normal); + while (block != TargetPointer.Null) + { + LoaderHeapBlockData data = loader.GetLoaderHeapBlockData(block); + blocks.Add((data.VirtualAddress.Value, data.VirtualSize.Value)); + block = loader.GetNextLoaderHeapBlock(block); + } + + Assert.Equal(2, blocks.Count); + Assert.Equal((virtualAddresses[0], virtualSizes[0]), blocks[0]); + Assert.Equal((virtualAddresses[1], virtualSizes[1]), blocks[1]); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void MultipleBlockLoaderHeap_ExplicitControl(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockMemorySpace.BumpAllocator allocator = builder.CreateAllocator(DefaultAllocationRangeStart, DefaultAllocationRangeEnd); + Dictionary types = GetTypes(helpers); + + Target.TypeInfo heapType = types[DataType.ExplicitControlLoaderHeap]; + Target.TypeInfo blockType = types[DataType.LoaderHeapBlock]; + + ulong[] virtualAddresses = [0x3000_0000UL, 0x4000_0000UL]; + ulong[] virtualSizes = [0x4000UL, 0x8000UL]; + + MockMemorySpace.HeapFragment block2Fragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(blockType), "ExplicitBlock2"); + helpers.WritePointer(block2Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.Next)].Offset, helpers.PointerSize), TargetPointer.Null.Value); + helpers.WritePointer(block2Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualAddress)].Offset, helpers.PointerSize), virtualAddresses[1]); + helpers.WriteNUInt(block2Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualSize)].Offset, helpers.PointerSize), new TargetNUInt(virtualSizes[1])); + builder.AddHeapFragment(block2Fragment); + + MockMemorySpace.HeapFragment block1Fragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(blockType), "ExplicitBlock1"); + helpers.WritePointer(block1Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.Next)].Offset, helpers.PointerSize), block2Fragment.Address); + helpers.WritePointer(block1Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualAddress)].Offset, helpers.PointerSize), virtualAddresses[0]); + helpers.WriteNUInt(block1Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualSize)].Offset, helpers.PointerSize), new TargetNUInt(virtualSizes[0])); + builder.AddHeapFragment(block1Fragment); + + MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "ExplicitControlLoaderHeap"); + helpers.WritePointer(heapFragment.Data.AsSpan().Slice(heapType.Fields[nameof(Data.ExplicitControlLoaderHeap.FirstBlock)].Offset, helpers.PointerSize), block1Fragment.Address); + builder.AddHeapFragment(heapFragment); + + Target target = CreateTarget(arch, types, builder); + ILoader loader = target.Contracts.Loader; + + List<(ulong Address, ulong Size)> blocks = []; + TargetPointer block = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.ExplicitControl); while (block != TargetPointer.Null) { LoaderHeapBlockData data = loader.GetLoaderHeapBlockData(block); @@ -162,3 +289,4 @@ public void MultipleBlockLoaderHeap(MockTarget.Architecture arch) Assert.Equal((virtualAddresses[1], virtualSizes[1]), blocks[1]); } } + From a04f5c879bd4c911a91c203ecc7e1f5252846e34 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 22:04:04 +0000 Subject: [PATCH 08/31] Fix build break: add cdac_data specialization and friend declaration to loaderheap.h Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- src/coreclr/inc/loaderheap.h | 10 ++++++++++ src/coreclr/vm/datadescriptor/datadescriptor.inc | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/coreclr/inc/loaderheap.h b/src/coreclr/inc/loaderheap.h index 79228c7eb5410d..d5a63efd1ad124 100644 --- a/src/coreclr/inc/loaderheap.h +++ b/src/coreclr/inc/loaderheap.h @@ -18,6 +18,9 @@ #include "ex.h" #include "executableallocator.h" +// Forward declaration for cDAC data exposure +template struct cdac_data; + //============================================================================== // Interface used to back out loader heap allocations. //============================================================================== @@ -176,6 +179,7 @@ enum class LoaderHeapImplementationKind class UnlockedLoaderHeapBaseTraversable { + friend struct cdac_data; protected: #ifdef DACCESS_COMPILE UnlockedLoaderHeapBaseTraversable() {} @@ -201,6 +205,12 @@ 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 diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 16289c2e8983b4..9c47824eebc06c 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -252,12 +252,12 @@ CDAC_TYPE_END(LoaderAllocator) CDAC_TYPE_BEGIN(LoaderHeap) CDAC_TYPE_INDETERMINATE(LoaderHeap) -CDAC_TYPE_FIELD(LoaderHeap, /*pointer*/, FirstBlock, offsetof(UnlockedLoaderHeapBaseTraversable, m_pFirstBlock)) +CDAC_TYPE_FIELD(LoaderHeap, /*pointer*/, FirstBlock, cdac_data::FirstBlock) CDAC_TYPE_END(LoaderHeap) CDAC_TYPE_BEGIN(ExplicitControlLoaderHeap) CDAC_TYPE_INDETERMINATE(ExplicitControlLoaderHeap) -CDAC_TYPE_FIELD(ExplicitControlLoaderHeap, /*pointer*/, FirstBlock, offsetof(UnlockedLoaderHeapBaseTraversable, m_pFirstBlock)) +CDAC_TYPE_FIELD(ExplicitControlLoaderHeap, /*pointer*/, FirstBlock, cdac_data::FirstBlock) CDAC_TYPE_END(ExplicitControlLoaderHeap) CDAC_TYPE_BEGIN(LoaderHeapBlock) From 668fc1cb9cc26500e16fbcc59d4a2ac9147d1c9c Mon Sep 17 00:00:00 2001 From: rcj1 Date: Thu, 5 Mar 2026 16:45:16 -0800 Subject: [PATCH 09/31] fix --- docs/design/datacontracts/Loader.md | 27 ++-- src/coreclr/inc/loaderheap.h | 25 +-- .../vm/datadescriptor/datadescriptor.inc | 4 +- .../Contracts/ILoader.cs | 10 +- .../Contracts/Loader_1.cs | 14 +- .../ISOSDacInterface.cs | 4 +- .../SOSDacImpl.cs | 149 ++++++++++++------ .../managed/cdac/tests/LoaderHeapTests.cs | 16 +- 8 files changed, 152 insertions(+), 97 deletions(-) diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 2cf4bd196b4354..df7ecb654a6785 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -88,12 +88,6 @@ TargetPointer GetILHeader(ModuleHandle handle, uint token); TargetPointer GetDynamicIL(ModuleHandle handle, uint token); // Loader heap traversal -readonly struct LoaderHeapBlockData -{ - public TargetPointer VirtualAddress { get; init; } - public TargetNUInt VirtualSize { get; init; } -} - enum LoaderHeapKind { Normal = 0, // UnlockedLoaderHeap / LoaderHeap @@ -103,8 +97,10 @@ enum LoaderHeapKind // Returns the first block of the loader heap linked list, or TargetPointer.Null if the heap has no blocks. // Throws NotImplementedException for unknown kind values. TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap, LoaderHeapKind kind); -// Returns the address and size of virtual memory for the given loader heap block -LoaderHeapBlockData GetLoaderHeapBlockData(TargetPointer block); +// Returns the size of the reserved virtual memory region for the given loader heap block +TargetNUInt GetLoaderHeapBlockSize(TargetPointer block); +// Returns the start address of the reserved virtual memory for the given loader heap block +TargetPointer GetLoaderHeapBlockAddress(TargetPointer block); // Returns the next block in the loader heap linked list, or TargetPointer.Null if there are no more blocks TargetPointer GetNextLoaderHeapBlock(TargetPointer block); ``` @@ -788,7 +784,7 @@ class InstMethodHashTable } ``` -#### GetFirstLoaderHeapBlock, GetLoaderHeapBlockData, GetNextLoaderHeapBlock +#### GetFirstLoaderHeapBlock, GetLoaderHeapBlockAddress, GetLoaderHeapBlockSize, GetNextLoaderHeapBlock Both `UnlockedLoaderHeap`/`LoaderHeap` (normal) and `ExplicitControlLoaderHeap` (explicit-control) inherit from `UnlockedLoaderHeapBaseTraversable`, which holds `m_pFirstBlock`. Each kind maps to a separate cDAC type @@ -808,13 +804,14 @@ TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap, LoaderHe }; } -LoaderHeapBlockData ILoader.GetLoaderHeapBlockData(TargetPointer block) +TargetPointer ILoader.GetLoaderHeapBlockAddress(TargetPointer block) { - return new LoaderHeapBlockData - { - VirtualAddress = target.ReadPointer(block + /* LoaderHeapBlock::VirtualAddress offset */), - VirtualSize = target.ReadNUInt(block + /* LoaderHeapBlock::VirtualSize offset */), - }; + return target.ReadPointer(block + /* LoaderHeapBlock::VirtualAddress offset */); +} + +TargetNUInt ILoader.GetLoaderHeapBlockSize(TargetPointer block) +{ + return target.ReadNUInt(block + /* LoaderHeapBlock::VirtualSize offset */); } TargetPointer ILoader.GetNextLoaderHeapBlock(TargetPointer block) diff --git a/src/coreclr/inc/loaderheap.h b/src/coreclr/inc/loaderheap.h index d5a63efd1ad124..fc1d80ce634024 100644 --- a/src/coreclr/inc/loaderheap.h +++ b/src/coreclr/inc/loaderheap.h @@ -17,9 +17,7 @@ #include "utilcode.h" #include "ex.h" #include "executableallocator.h" - -// Forward declaration for cDAC data exposure -template struct cdac_data; +#include "../vm/cdacdata.h" //============================================================================== // Interface used to back out loader heap allocations. @@ -179,7 +177,6 @@ enum class LoaderHeapImplementationKind class UnlockedLoaderHeapBaseTraversable { - friend struct cdac_data; protected: #ifdef DACCESS_COMPILE UnlockedLoaderHeapBaseTraversable() {} @@ -205,12 +202,6 @@ 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 @@ -219,6 +210,7 @@ struct cdac_data typedef DPTR(class UnlockedLoaderHeapBase) PTR_UnlockedLoaderHeapBase; class UnlockedLoaderHeapBase : public UnlockedLoaderHeapBaseTraversable, public ILoaderHeapBackout { + friend struct cdac_data; #ifdef _DEBUG friend class LoaderHeapSniffer; #endif @@ -291,6 +283,12 @@ class UnlockedLoaderHeapBase : public UnlockedLoaderHeapBaseTraversable, public #endif }; +template<> +struct cdac_data +{ + static constexpr size_t FirstBlock = offsetof(UnlockedLoaderHeapBase, m_pFirstBlock); +}; + //=============================================================================== // This is the base class for LoaderHeap It's used as a simple // allocator that's semantically (but not perfwise!) equivalent to a blackbox @@ -609,6 +607,7 @@ class UnlockedInterleavedLoaderHeap : public UnlockedLoaderHeapBase typedef DPTR(class ExplicitControlLoaderHeap) PTR_ExplicitControlLoaderHeap; class ExplicitControlLoaderHeap : public UnlockedLoaderHeapBaseTraversable { + friend struct cdac_data; #ifdef DACCESS_COMPILE friend class ClrDataAccess; #endif @@ -703,6 +702,12 @@ class ExplicitControlLoaderHeap : public UnlockedLoaderHeapBaseTraversable void SetReservedRegion(BYTE* dwReservedRegionAddress, SIZE_T dwReservedRegionSize, BOOL fReleaseMemory); }; +template<> +struct cdac_data +{ + static constexpr size_t FirstBlock = offsetof(ExplicitControlLoaderHeap, m_pFirstBlock); +}; + //=============================================================================== // Create the LoaderHeap lock. It's the same lock for several different Heaps. //=============================================================================== diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 9c47824eebc06c..6c828e67a3114b 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -252,12 +252,12 @@ CDAC_TYPE_END(LoaderAllocator) CDAC_TYPE_BEGIN(LoaderHeap) CDAC_TYPE_INDETERMINATE(LoaderHeap) -CDAC_TYPE_FIELD(LoaderHeap, /*pointer*/, FirstBlock, cdac_data::FirstBlock) +CDAC_TYPE_FIELD(LoaderHeap, /*pointer*/, FirstBlock, cdac_data::FirstBlock) CDAC_TYPE_END(LoaderHeap) CDAC_TYPE_BEGIN(ExplicitControlLoaderHeap) CDAC_TYPE_INDETERMINATE(ExplicitControlLoaderHeap) -CDAC_TYPE_FIELD(ExplicitControlLoaderHeap, /*pointer*/, FirstBlock, cdac_data::FirstBlock) +CDAC_TYPE_FIELD(ExplicitControlLoaderHeap, /*pointer*/, FirstBlock, cdac_data::FirstBlock) CDAC_TYPE_END(ExplicitControlLoaderHeap) CDAC_TYPE_BEGIN(LoaderHeapBlock) 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 5df2f8b14c0df4..c1a88055e21e39 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 @@ -16,12 +16,6 @@ public ModuleHandle(TargetPointer address) public TargetPointer Address { get; } } -public readonly struct LoaderHeapBlockData -{ - public TargetPointer VirtualAddress { get; init; } - public TargetNUInt VirtualSize { get; init; } -} - public enum LoaderHeapKind { Normal = 0, // UnlockedLoaderHeap / LoaderHeap @@ -110,8 +104,8 @@ public interface ILoader : IContract // Returns the first block of the loader heap linked list, or TargetPointer.Null if the heap has no blocks. // Throws NotImplementedException for unknown kind values. TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap, LoaderHeapKind kind) => throw new NotImplementedException(); - // Returns the address and size of virtual memory for the given loader heap block - LoaderHeapBlockData GetLoaderHeapBlockData(TargetPointer block) => throw new NotImplementedException(); + TargetNUInt GetLoaderHeapBlockSize(TargetPointer block) => throw new NotImplementedException(); + TargetPointer GetLoaderHeapBlockAddress(TargetPointer block) => throw new NotImplementedException(); // Returns the next block in the loader heap linked list, or TargetPointer.Null if there are no more blocks TargetPointer GetNextLoaderHeapBlock(TargetPointer block) => throw new NotImplementedException(); } 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 388456cdd26338..f7abfbc8d7b33b 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 @@ -564,14 +564,16 @@ TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap, LoaderHe }; } - LoaderHeapBlockData ILoader.GetLoaderHeapBlockData(TargetPointer block) + TargetNUInt ILoader.GetLoaderHeapBlockSize(TargetPointer block) { Data.LoaderHeapBlock blockData = _target.ProcessedData.GetOrAdd(block); - return new LoaderHeapBlockData - { - VirtualAddress = blockData.VirtualAddress, - VirtualSize = blockData.VirtualSize, - }; + return blockData.VirtualSize; + } + + TargetPointer ILoader.GetLoaderHeapBlockAddress(TargetPointer block) + { + Data.LoaderHeapBlock blockData = _target.ProcessedData.GetOrAdd(block); + return blockData.VirtualAddress; } TargetPointer ILoader.GetNextLoaderHeapBlock(TargetPointer block) 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 9b150f2de981d5..d25128d8a14cb0 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs @@ -663,7 +663,7 @@ public unsafe partial interface ISOSDacInterface // Heaps [PreserveSig] - int TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*VISITHEAP*/ void* pCallback); + int TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*VISITHEAP*/ delegate* unmanaged[Stdcall] pCallback); [PreserveSig] int GetCodeHeapList(ClrDataAddress jitManager, uint count, /*struct DacpJitCodeHeapInfo*/ void* codeHeaps, uint* pNeeded); [PreserveSig] @@ -943,7 +943,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[Stdcall]< /*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 fbe236789146bf..346ab89d0b2ccd 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -3868,8 +3868,67 @@ int ISOSDacInterface.GetWorkRequestData(ClrDataAddress addrWorkRequest, void* da } int ISOSDacInterface.TraverseEHInfo(ClrDataAddress ip, void* pCallback, void* token) => _legacyImpl is not null ? _legacyImpl.TraverseEHInfo(ip, pCallback, token) : HResults.E_NOTIMPL; - int ISOSDacInterface.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, void* pCallback) - => _legacyImpl is not null ? _legacyImpl.TraverseLoaderHeap(loaderHeapAddr, pCallback) : HResults.E_NOTIMPL; + int ISOSDacInterface.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, delegate* unmanaged[Stdcall] 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, Contracts.LoaderHeapKind.Normal); + TargetPointer firstBlock = block; + int i = 0; + while (block != TargetPointer.Null && i++ < iterationMax) + { + TargetPointer blockAddress; + TargetNUInt blockSize; + try + { + blockAddress = loader.GetLoaderHeapBlockAddress(block); + blockSize = loader.GetLoaderHeapBlockSize(block); + } + catch (VirtualReadException) + { + throw new NullReferenceException(); + } + pCallback(blockAddress.Value, (nuint)blockSize.Value, block == firstBlock ? Interop.BOOL.TRUE : Interop.BOOL.FALSE); +#if DEBUG + _debugTraverseLoaderHeapBlocks.Add((blockAddress.Value, (nuint)blockSize.Value)); +#endif + block = loader.GetNextLoaderHeapBlock(block); + if (block == firstBlock) + throw new NullReferenceException(); + } + if (i >= iterationMax) + hr = HResults.S_FALSE; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacyImpl is not null) + { + int cdacCount = _debugTraverseLoaderHeapBlocks.Count; + delegate* unmanaged[Stdcall] debugCallbackPtr = &TraverseLoaderHeapDebugCallback; + int hrLocal = _legacyImpl.TraverseLoaderHeap(loaderHeapAddr, debugCallbackPtr); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + 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(CallConvs = new[] { typeof(CallConvStdcall) })] @@ -4881,44 +4940,61 @@ int ISOSDacInterface12.GetGlobalAllocationContext(ClrDataAddress* allocPtr, ClrD #endregion ISOSDacInterface12 #region ISOSDacInterface13 + #if DEBUG - [ThreadStatic] - private static List<(ulong VirtualAddress, nuint VirtualSize)>? _debugTraverseLoaderHeapBlocks; + private static List<(ulong VirtualAddress, nuint VirtualSize)> _debugTraverseLoaderHeapBlocks = new(); + private static uint _debugTraverseLoaderDebugCount; - [UnmanagedCallersOnly] - private static Interop.BOOL TraverseLoaderHeapDebugCallback(ulong virtualAddress, nuint virtualSize) + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })] + private static void TraverseLoaderHeapDebugCallback(ulong virtualAddress, nuint virtualSize, Interop.BOOL _) { - List<(ulong VirtualAddress, nuint VirtualSize)>? expected = _debugTraverseLoaderHeapBlocks; - if (expected is not null) - { - bool found = expected.Remove((virtualAddress, virtualSize)); - Debug.Assert(found, $"Unexpected loader heap block: address={virtualAddress:x}, size={virtualSize:x}"); - } - return Interop.BOOL.TRUE; + 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 - - int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*LoaderHeapKind*/ int kind, /*VISITHEAP*/ delegate* unmanaged pCallback) + int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*LoaderHeapKind*/ int kind, /*VISITHEAP*/ delegate* unmanaged[Stdcall] pCallback) { int hr = HResults.S_OK; +#if DEBUG + _debugTraverseLoaderHeapBlocks.Clear(); + _debugTraverseLoaderDebugCount = 0; +#endif try { - if (loaderHeapAddr == 0) - throw new ArgumentException(); - if (pCallback is null) + 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, (Contracts.LoaderHeapKind)kind); - while (block != TargetPointer.Null) + TargetPointer firstBlock = block; + int i = 0; + while (block != TargetPointer.Null && i++ < iterationMax) { - Contracts.LoaderHeapBlockData blockData = loader.GetLoaderHeapBlockData(block); - Interop.BOOL cont = pCallback(blockData.VirtualAddress.Value, (nuint)blockData.VirtualSize.Value); - if (cont == Interop.BOOL.FALSE) - break; + TargetPointer blockAddress; + TargetNUInt blockSize; + try + { + blockAddress = loader.GetLoaderHeapBlockAddress(block); + blockSize = loader.GetLoaderHeapBlockSize(block); + } + catch (VirtualReadException) + { + throw new NullReferenceException(); + } + pCallback(blockAddress.Value, (nuint)blockSize.Value, block == firstBlock ? Interop.BOOL.TRUE : Interop.BOOL.FALSE); +#if DEBUG + _debugTraverseLoaderHeapBlocks.Add((blockAddress.Value, (nuint)blockSize.Value)); +#endif block = loader.GetNextLoaderHeapBlock(block); + if (block == firstBlock) + throw new NullReferenceException(); } + if (i >= iterationMax) + hr = HResults.S_FALSE; } catch (System.Exception ex) { @@ -4927,29 +5003,14 @@ int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*Loade #if DEBUG if (_legacyImpl13 is not null) { - // Collect expected blocks via a second cDAC traversal (not re-invoking the original callback). - List<(ulong VirtualAddress, nuint VirtualSize)> cdacBlocks = []; - try - { - Contracts.ILoader loader = _target.Contracts.Loader; - TargetPointer heapAddr = loaderHeapAddr.ToTargetPointer(_target); - TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr, (Contracts.LoaderHeapKind)kind); - while (block != TargetPointer.Null) - { - Contracts.LoaderHeapBlockData blockData = loader.GetLoaderHeapBlockData(block); - cdacBlocks.Add((blockData.VirtualAddress.Value, (nuint)blockData.VirtualSize.Value)); - block = loader.GetNextLoaderHeapBlock(block); - } - } - catch { } - - _debugTraverseLoaderHeapBlocks = cdacBlocks; - delegate* unmanaged debugCallbackPtr = &TraverseLoaderHeapDebugCallback; + int cdacCount = _debugTraverseLoaderHeapBlocks.Count; + delegate* unmanaged[Stdcall] debugCallbackPtr = &TraverseLoaderHeapDebugCallback; int hrLocal = _legacyImpl13.TraverseLoaderHeap(loaderHeapAddr, kind, debugCallbackPtr); Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); - Debug.Assert(_debugTraverseLoaderHeapBlocks!.Count == 0, - $"cDAC found {cdacBlocks.Count} blocks but DAC found {cdacBlocks.Count - _debugTraverseLoaderHeapBlocks.Count} matching blocks"); - _debugTraverseLoaderHeapBlocks = null; + 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; diff --git a/src/native/managed/cdac/tests/LoaderHeapTests.cs b/src/native/managed/cdac/tests/LoaderHeapTests.cs index 1a15fcd9140bf9..631b6f8800fa00 100644 --- a/src/native/managed/cdac/tests/LoaderHeapTests.cs +++ b/src/native/managed/cdac/tests/LoaderHeapTests.cs @@ -147,9 +147,8 @@ public void SingleBlockLoaderHeap_Normal(MockTarget.Architecture arch) TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.Normal); Assert.Equal((TargetPointer)blockFragment.Address, firstBlock); - LoaderHeapBlockData data = loader.GetLoaderHeapBlockData(firstBlock); - Assert.Equal(virtualAddress, data.VirtualAddress.Value); - Assert.Equal(virtualSize, data.VirtualSize.Value); + Assert.Equal(virtualAddress, loader.GetLoaderHeapBlockAddress(firstBlock).Value); + Assert.Equal(virtualSize, loader.GetLoaderHeapBlockSize(firstBlock).Value); TargetPointer nextBlock = loader.GetNextLoaderHeapBlock(firstBlock); Assert.Equal(TargetPointer.Null, nextBlock); @@ -185,9 +184,8 @@ public void SingleBlockLoaderHeap_ExplicitControl(MockTarget.Architecture arch) TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.ExplicitControl); Assert.Equal((TargetPointer)blockFragment.Address, firstBlock); - LoaderHeapBlockData data = loader.GetLoaderHeapBlockData(firstBlock); - Assert.Equal(virtualAddress, data.VirtualAddress.Value); - Assert.Equal(virtualSize, data.VirtualSize.Value); + Assert.Equal(virtualAddress, loader.GetLoaderHeapBlockAddress(firstBlock).Value); + Assert.Equal(virtualSize, loader.GetLoaderHeapBlockSize(firstBlock).Value); TargetPointer nextBlock = loader.GetNextLoaderHeapBlock(firstBlock); Assert.Equal(TargetPointer.Null, nextBlock); @@ -231,8 +229,7 @@ public void MultipleBlockLoaderHeap_Normal(MockTarget.Architecture arch) TargetPointer block = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.Normal); while (block != TargetPointer.Null) { - LoaderHeapBlockData data = loader.GetLoaderHeapBlockData(block); - blocks.Add((data.VirtualAddress.Value, data.VirtualSize.Value)); + blocks.Add((loader.GetLoaderHeapBlockAddress(block).Value, loader.GetLoaderHeapBlockSize(block).Value)); block = loader.GetNextLoaderHeapBlock(block); } @@ -279,8 +276,7 @@ public void MultipleBlockLoaderHeap_ExplicitControl(MockTarget.Architecture arch TargetPointer block = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.ExplicitControl); while (block != TargetPointer.Null) { - LoaderHeapBlockData data = loader.GetLoaderHeapBlockData(block); - blocks.Add((data.VirtualAddress.Value, data.VirtualSize.Value)); + blocks.Add((loader.GetLoaderHeapBlockAddress(block).Value, loader.GetLoaderHeapBlockSize(block).Value)); block = loader.GetNextLoaderHeapBlock(block); } From 0b34106600587fdfa02e6a77827bcda0a44f3d78 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Fri, 6 Mar 2026 09:21:43 -0800 Subject: [PATCH 10/31] simplify loader heap --- docs/design/datacontracts/Loader.md | 28 +--- src/coreclr/debug/daccess/request.cpp | 21 +-- src/coreclr/inc/loaderheap.h | 26 ++-- .../vm/datadescriptor/datadescriptor.inc | 7 +- .../Contracts/ILoader.cs | 2 +- .../DataType.cs | 1 - .../Contracts/Loader_1.cs | 10 +- .../Data/ExplicitControlLoaderHeap.cs | 19 --- .../SOSDacImpl.cs | 6 +- .../managed/cdac/tests/LoaderHeapTests.cs | 147 +----------------- 10 files changed, 37 insertions(+), 230 deletions(-) delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExplicitControlLoaderHeap.cs diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index df7ecb654a6785..4509d244d9a30e 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -90,13 +90,12 @@ TargetPointer GetDynamicIL(ModuleHandle handle, uint token); // Loader heap traversal enum LoaderHeapKind { - Normal = 0, // UnlockedLoaderHeap / LoaderHeap - ExplicitControl = 1, // ExplicitControlLoaderHeap + Normal = 0, + ExplicitControl = 1, } // Returns the first block of the loader heap linked list, or TargetPointer.Null if the heap has no blocks. -// Throws NotImplementedException for unknown kind values. -TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap, LoaderHeapKind kind); +TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap); // Returns the size of the reserved virtual memory region for the given loader heap block TargetNUInt GetLoaderHeapBlockSize(TargetPointer block); // Returns the start address of the reserved virtual memory for the given loader heap block @@ -176,8 +175,7 @@ TargetPointer GetNextLoaderHeapBlock(TargetPointer block); | `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 (normal heaps: `UnlockedLoaderHeap`/`LoaderHeap`) | -| `ExplicitControlLoaderHeap` | `FirstBlock` | Pointer to the first `LoaderHeapBlock` in the linked list | +| `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 | @@ -784,24 +782,12 @@ class InstMethodHashTable } ``` -#### GetFirstLoaderHeapBlock, GetLoaderHeapBlockAddress, GetLoaderHeapBlockSize, GetNextLoaderHeapBlock - -Both `UnlockedLoaderHeap`/`LoaderHeap` (normal) and `ExplicitControlLoaderHeap` (explicit-control) inherit from -`UnlockedLoaderHeapBaseTraversable`, which holds `m_pFirstBlock`. Each kind maps to a separate cDAC type -(`LoaderHeap` and `ExplicitControlLoaderHeap` respectively) so callers are explicit about which type they are -traversing. The `LoaderHeapKind` enum encodes the choice, and an unknown kind throws `NotImplementedException`. +#### LoaderHeapBlock ```csharp -TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap, LoaderHeapKind kind) +TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap) { - return kind switch - { - LoaderHeapKind.Normal => - target.ReadPointer(loaderHeap + /* LoaderHeap::FirstBlock offset */), - LoaderHeapKind.ExplicitControl => - target.ReadPointer(loaderHeap + /* ExplicitControlLoaderHeap::FirstBlock offset */), - _ => throw new NotImplementedException($"Unknown loader heap kind: {kind}"), - }; + return target.ReadPointer(loaderHeap + /* LoaderHeap::FirstBlock offset */); } TargetPointer ILoader.GetLoaderHeapBlockAddress(TargetPointer block) diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 310873f6e6232a..b17ce9d3e0a486 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -3577,7 +3577,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,22 +3591,11 @@ ClrDataAccess::TraverseLoaderHeap(CLRDATA_ADDRESS loaderHeapAddr, LoaderHeapKind if (loaderHeapAddr == 0 || pCallback == 0) return E_INVALIDARG; - 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; + if (kind != LoaderHeapKindNormal && kind != LoaderHeapKindExplicitControl) + return E_NOTIMPL; - default: - hr = E_NOTIMPL; - break; - } + SOSDacEnter(); + 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 fc1d80ce634024..155fb7c8a8be3b 100644 --- a/src/coreclr/inc/loaderheap.h +++ b/src/coreclr/inc/loaderheap.h @@ -175,10 +175,12 @@ enum class LoaderHeapImplementationKind Interleaved }; +typedef DPTR(class UnlockedLoaderHeapBaseTraversable) PTR_UnlockedLoaderHeapBaseTraversable; class UnlockedLoaderHeapBaseTraversable { protected: #ifdef DACCESS_COMPILE + friend class ClrDataAccess; UnlockedLoaderHeapBaseTraversable() {} #else UnlockedLoaderHeapBaseTraversable() : @@ -196,21 +198,26 @@ class UnlockedLoaderHeapBaseTraversable typedef bool EnumPageRegionsCallback (PTR_VOID pvArgs, PTR_VOID pvAllocationBase, SIZE_T cbReserved); void EnumPageRegions (EnumPageRegionsCallback *pCallback, PTR_VOID pvArgs); #endif - + virtual ~UnlockedLoaderHeapBaseTraversable() {} + friend struct cdac_data; protected: // Linked list of ClrVirtualAlloc'd pages 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 { - friend struct cdac_data; #ifdef _DEBUG friend class LoaderHeapSniffer; #endif @@ -283,12 +290,6 @@ class UnlockedLoaderHeapBase : public UnlockedLoaderHeapBaseTraversable, public #endif }; -template<> -struct cdac_data -{ - static constexpr size_t FirstBlock = offsetof(UnlockedLoaderHeapBase, m_pFirstBlock); -}; - //=============================================================================== // This is the base class for LoaderHeap It's used as a simple // allocator that's semantically (but not perfwise!) equivalent to a blackbox @@ -607,7 +608,6 @@ class UnlockedInterleavedLoaderHeap : public UnlockedLoaderHeapBase typedef DPTR(class ExplicitControlLoaderHeap) PTR_ExplicitControlLoaderHeap; class ExplicitControlLoaderHeap : public UnlockedLoaderHeapBaseTraversable { - friend struct cdac_data; #ifdef DACCESS_COMPILE friend class ClrDataAccess; #endif @@ -702,12 +702,6 @@ class ExplicitControlLoaderHeap : public UnlockedLoaderHeapBaseTraversable void SetReservedRegion(BYTE* dwReservedRegionAddress, SIZE_T dwReservedRegionSize, BOOL fReleaseMemory); }; -template<> -struct cdac_data -{ - static constexpr size_t FirstBlock = offsetof(ExplicitControlLoaderHeap, m_pFirstBlock); -}; - //=============================================================================== // Create the LoaderHeap lock. It's the same lock for several different Heaps. //=============================================================================== diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 6c828e67a3114b..bf89cf1399ad40 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -252,14 +252,9 @@ CDAC_TYPE_END(LoaderAllocator) CDAC_TYPE_BEGIN(LoaderHeap) CDAC_TYPE_INDETERMINATE(LoaderHeap) -CDAC_TYPE_FIELD(LoaderHeap, /*pointer*/, FirstBlock, cdac_data::FirstBlock) +CDAC_TYPE_FIELD(LoaderHeap, /*pointer*/, FirstBlock, cdac_data::FirstBlock) CDAC_TYPE_END(LoaderHeap) -CDAC_TYPE_BEGIN(ExplicitControlLoaderHeap) -CDAC_TYPE_INDETERMINATE(ExplicitControlLoaderHeap) -CDAC_TYPE_FIELD(ExplicitControlLoaderHeap, /*pointer*/, FirstBlock, cdac_data::FirstBlock) -CDAC_TYPE_END(ExplicitControlLoaderHeap) - CDAC_TYPE_BEGIN(LoaderHeapBlock) CDAC_TYPE_FIELD(LoaderHeapBlock, /*pointer*/, Next, offsetof(LoaderHeapBlock, pNext)) CDAC_TYPE_FIELD(LoaderHeapBlock, /*pointer*/, VirtualAddress, offsetof(LoaderHeapBlock, pVirtualAddress)) 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 c1a88055e21e39..b981c0b75db5c3 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 @@ -103,7 +103,7 @@ public interface ILoader : IContract // Returns the first block of the loader heap linked list, or TargetPointer.Null if the heap has no blocks. // Throws NotImplementedException for unknown kind values. - TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap, LoaderHeapKind kind) => throw new NotImplementedException(); + TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap) => throw new NotImplementedException(); TargetNUInt GetLoaderHeapBlockSize(TargetPointer block) => throw new NotImplementedException(); TargetPointer GetLoaderHeapBlockAddress(TargetPointer block) => throw new NotImplementedException(); // Returns the next block in the loader heap linked list, or TargetPointer.Null if there are no more blocks 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 a2219019259205..0376143b8399ef 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -42,7 +42,6 @@ public enum DataType Assembly, LoaderAllocator, LoaderHeap, - ExplicitControlLoaderHeap, LoaderHeapBlock, PEAssembly, AssemblyBinder, 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 f7abfbc8d7b33b..75b50999e4aee9 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,15 +553,9 @@ TargetPointer ILoader.GetDynamicIL(ModuleHandle handle, uint token) return shashContract.LookupSHash(dynamicILBlobTable.HashTable, token).EntryIL; } - TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap, LoaderHeapKind kind) + TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap) { - return kind switch - { - LoaderHeapKind.Normal => _target.ProcessedData.GetOrAdd(loaderHeap).FirstBlock, - LoaderHeapKind.ExplicitControl => _target.ProcessedData.GetOrAdd(loaderHeap).FirstBlock, - // NotImplementedException maps to E_NOTIMPL, matching native DAC behavior for unknown heap kinds. - _ => throw new NotImplementedException($"Unknown loader heap kind: {kind}"), - }; + return _target.ProcessedData.GetOrAdd(loaderHeap).FirstBlock; } TargetNUInt ILoader.GetLoaderHeapBlockSize(TargetPointer block) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExplicitControlLoaderHeap.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExplicitControlLoaderHeap.cs deleted file mode 100644 index dd93d88ffc5bf5..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExplicitControlLoaderHeap.cs +++ /dev/null @@ -1,19 +0,0 @@ -// 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 ExplicitControlLoaderHeap : IData -{ - static ExplicitControlLoaderHeap IData.Create(Target target, TargetPointer address) - => new ExplicitControlLoaderHeap(target, address); - - public ExplicitControlLoaderHeap(Target target, TargetPointer address) - { - Target.TypeInfo type = target.GetTypeInfo(DataType.ExplicitControlLoaderHeap); - - 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.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index 346ab89d0b2ccd..37d7d2557cad96 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -3883,7 +3883,7 @@ int ISOSDacInterface.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, delegate* Contracts.ILoader loader = _target.Contracts.Loader; TargetPointer heapAddr = loaderHeapAddr.ToTargetPointer(_target); - TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr, Contracts.LoaderHeapKind.Normal); + TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr); TargetPointer firstBlock = block; int i = 0; while (block != TargetPointer.Null && i++ < iterationMax) @@ -4965,11 +4965,13 @@ int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*Loade { if (loaderHeapAddr == 0 || pCallback is null) throw new ArgumentException(); + if (kind != (int)Contracts.LoaderHeapKind.Normal && kind != (int)Contracts.LoaderHeapKind.ExplicitControl) + throw new NotImplementedException(); int iterationMax = 8192; Contracts.ILoader loader = _target.Contracts.Loader; TargetPointer heapAddr = loaderHeapAddr.ToTargetPointer(_target); - TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr, (Contracts.LoaderHeapKind)kind); + TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr); TargetPointer firstBlock = block; int i = 0; while (block != TargetPointer.Null && i++ < iterationMax) diff --git a/src/native/managed/cdac/tests/LoaderHeapTests.cs b/src/native/managed/cdac/tests/LoaderHeapTests.cs index 631b6f8800fa00..6170894fd16c14 100644 --- a/src/native/managed/cdac/tests/LoaderHeapTests.cs +++ b/src/native/managed/cdac/tests/LoaderHeapTests.cs @@ -23,15 +23,6 @@ public class LoaderHeapTests ] }; - private static readonly MockDescriptors.TypeFields ExplicitControlLoaderHeapFields = new MockDescriptors.TypeFields() - { - DataType = DataType.ExplicitControlLoaderHeap, - Fields = - [ - new(nameof(Data.ExplicitControlLoaderHeap.FirstBlock), DataType.pointer), - ] - }; - private static readonly MockDescriptors.TypeFields LoaderHeapBlockFields = new MockDescriptors.TypeFields() { DataType = DataType.LoaderHeapBlock, @@ -45,7 +36,7 @@ public class LoaderHeapTests private static Dictionary GetTypes(TargetTestHelpers helpers) { - return MockDescriptors.GetTypesForTypeFields(helpers, [LoaderHeapFields, ExplicitControlLoaderHeapFields, LoaderHeapBlockFields]); + return MockDescriptors.GetTypesForTypeFields(helpers, [LoaderHeapFields, LoaderHeapBlockFields]); } private static Target CreateTarget(MockTarget.Architecture arch, Dictionary types, MockMemorySpace.Builder builder) @@ -58,7 +49,7 @@ private static Target CreateTarget(MockTarget.Architecture arch, Dictionary types = GetTypes(helpers); - - Target.TypeInfo heapType = types[DataType.ExplicitControlLoaderHeap]; - MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "ExplicitControlLoaderHeap"); - // FirstBlock is zero (null) by default - builder.AddHeapFragment(heapFragment); - - Target target = CreateTarget(arch, types, builder); - ILoader loader = target.Contracts.Loader; - - TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.ExplicitControl); - Assert.Equal(TargetPointer.Null, firstBlock); - } - - [Theory] - [ClassData(typeof(MockTarget.StdArch))] - public void UnknownKind_ThrowsNotImplemented(MockTarget.Architecture arch) - { - TargetTestHelpers helpers = new(arch); - MockMemorySpace.Builder builder = new(helpers); - MockMemorySpace.BumpAllocator allocator = builder.CreateAllocator(DefaultAllocationRangeStart, DefaultAllocationRangeEnd); - Dictionary types = GetTypes(helpers); - - Target.TypeInfo heapType = types[DataType.LoaderHeap]; - MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "LoaderHeap"); - builder.AddHeapFragment(heapFragment); - - Target target = CreateTarget(arch, types, builder); - ILoader loader = target.Contracts.Loader; - - Assert.Throws(() => loader.GetFirstLoaderHeapBlock(heapFragment.Address, (LoaderHeapKind)99)); - } - - [Theory] - [ClassData(typeof(MockTarget.StdArch))] - public void SingleBlockLoaderHeap_Normal(MockTarget.Architecture arch) + public void SingleBlockLoaderHeap(MockTarget.Architecture arch) { TargetTestHelpers helpers = new(arch); MockMemorySpace.Builder builder = new(helpers); @@ -144,44 +95,7 @@ public void SingleBlockLoaderHeap_Normal(MockTarget.Architecture arch) Target target = CreateTarget(arch, types, builder); ILoader loader = target.Contracts.Loader; - TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.Normal); - Assert.Equal((TargetPointer)blockFragment.Address, firstBlock); - - Assert.Equal(virtualAddress, loader.GetLoaderHeapBlockAddress(firstBlock).Value); - Assert.Equal(virtualSize, loader.GetLoaderHeapBlockSize(firstBlock).Value); - - TargetPointer nextBlock = loader.GetNextLoaderHeapBlock(firstBlock); - Assert.Equal(TargetPointer.Null, nextBlock); - } - - [Theory] - [ClassData(typeof(MockTarget.StdArch))] - public void SingleBlockLoaderHeap_ExplicitControl(MockTarget.Architecture arch) - { - TargetTestHelpers helpers = new(arch); - MockMemorySpace.Builder builder = new(helpers); - MockMemorySpace.BumpAllocator allocator = builder.CreateAllocator(DefaultAllocationRangeStart, DefaultAllocationRangeEnd); - Dictionary types = GetTypes(helpers); - - Target.TypeInfo heapType = types[DataType.ExplicitControlLoaderHeap]; - Target.TypeInfo blockType = types[DataType.LoaderHeapBlock]; - - ulong virtualAddress = 0x5678_0000UL; - ulong virtualSize = 0x2000UL; - MockMemorySpace.HeapFragment blockFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(blockType), "LoaderHeapBlock"); - helpers.WritePointer(blockFragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.Next)].Offset, helpers.PointerSize), TargetPointer.Null.Value); - helpers.WritePointer(blockFragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualAddress)].Offset, helpers.PointerSize), virtualAddress); - helpers.WriteNUInt(blockFragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualSize)].Offset, helpers.PointerSize), new TargetNUInt(virtualSize)); - builder.AddHeapFragment(blockFragment); - - MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "ExplicitControlLoaderHeap"); - helpers.WritePointer(heapFragment.Data.AsSpan().Slice(heapType.Fields[nameof(Data.ExplicitControlLoaderHeap.FirstBlock)].Offset, helpers.PointerSize), blockFragment.Address); - builder.AddHeapFragment(heapFragment); - - Target target = CreateTarget(arch, types, builder); - ILoader loader = target.Contracts.Loader; - - TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.ExplicitControl); + TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address); Assert.Equal((TargetPointer)blockFragment.Address, firstBlock); Assert.Equal(virtualAddress, loader.GetLoaderHeapBlockAddress(firstBlock).Value); @@ -193,7 +107,7 @@ public void SingleBlockLoaderHeap_ExplicitControl(MockTarget.Architecture arch) [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void MultipleBlockLoaderHeap_Normal(MockTarget.Architecture arch) + public void MultipleBlockLoaderHeap(MockTarget.Architecture arch) { TargetTestHelpers helpers = new(arch); MockMemorySpace.Builder builder = new(helpers); @@ -226,54 +140,7 @@ public void MultipleBlockLoaderHeap_Normal(MockTarget.Architecture arch) ILoader loader = target.Contracts.Loader; List<(ulong Address, ulong Size)> blocks = []; - TargetPointer block = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.Normal); - while (block != TargetPointer.Null) - { - blocks.Add((loader.GetLoaderHeapBlockAddress(block).Value, loader.GetLoaderHeapBlockSize(block).Value)); - block = loader.GetNextLoaderHeapBlock(block); - } - - Assert.Equal(2, blocks.Count); - Assert.Equal((virtualAddresses[0], virtualSizes[0]), blocks[0]); - Assert.Equal((virtualAddresses[1], virtualSizes[1]), blocks[1]); - } - - [Theory] - [ClassData(typeof(MockTarget.StdArch))] - public void MultipleBlockLoaderHeap_ExplicitControl(MockTarget.Architecture arch) - { - TargetTestHelpers helpers = new(arch); - MockMemorySpace.Builder builder = new(helpers); - MockMemorySpace.BumpAllocator allocator = builder.CreateAllocator(DefaultAllocationRangeStart, DefaultAllocationRangeEnd); - Dictionary types = GetTypes(helpers); - - Target.TypeInfo heapType = types[DataType.ExplicitControlLoaderHeap]; - Target.TypeInfo blockType = types[DataType.LoaderHeapBlock]; - - ulong[] virtualAddresses = [0x3000_0000UL, 0x4000_0000UL]; - ulong[] virtualSizes = [0x4000UL, 0x8000UL]; - - MockMemorySpace.HeapFragment block2Fragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(blockType), "ExplicitBlock2"); - helpers.WritePointer(block2Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.Next)].Offset, helpers.PointerSize), TargetPointer.Null.Value); - helpers.WritePointer(block2Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualAddress)].Offset, helpers.PointerSize), virtualAddresses[1]); - helpers.WriteNUInt(block2Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualSize)].Offset, helpers.PointerSize), new TargetNUInt(virtualSizes[1])); - builder.AddHeapFragment(block2Fragment); - - MockMemorySpace.HeapFragment block1Fragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(blockType), "ExplicitBlock1"); - helpers.WritePointer(block1Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.Next)].Offset, helpers.PointerSize), block2Fragment.Address); - helpers.WritePointer(block1Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualAddress)].Offset, helpers.PointerSize), virtualAddresses[0]); - helpers.WriteNUInt(block1Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualSize)].Offset, helpers.PointerSize), new TargetNUInt(virtualSizes[0])); - builder.AddHeapFragment(block1Fragment); - - MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "ExplicitControlLoaderHeap"); - helpers.WritePointer(heapFragment.Data.AsSpan().Slice(heapType.Fields[nameof(Data.ExplicitControlLoaderHeap.FirstBlock)].Offset, helpers.PointerSize), block1Fragment.Address); - builder.AddHeapFragment(heapFragment); - - Target target = CreateTarget(arch, types, builder); - ILoader loader = target.Contracts.Loader; - - List<(ulong Address, ulong Size)> blocks = []; - TargetPointer block = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.ExplicitControl); + TargetPointer block = loader.GetFirstLoaderHeapBlock(heapFragment.Address); while (block != TargetPointer.Null) { blocks.Add((loader.GetLoaderHeapBlockAddress(block).Value, loader.GetLoaderHeapBlockSize(block).Value)); From 54a628cdaa64d937437b1fbdbeb6144d54b58a05 Mon Sep 17 00:00:00 2001 From: Rachel Date: Fri, 6 Mar 2026 09:22:24 -0800 Subject: [PATCH 11/31] Update docs/design/datacontracts/GC.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/design/datacontracts/GC.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index cba389db055f35..871b7ac4b8ac92 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -96,7 +96,7 @@ public readonly struct GCOomData // Returns pointers to all GC heaps IEnumerable GetGCHeaps(); - // The following APIs have both a workstation and serer variant. + // 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. From b15b9ce29fb7586d2c9e720c7ce3511f6ef2e47f Mon Sep 17 00:00:00 2001 From: rcj1 Date: Fri, 6 Mar 2026 10:00:46 -0800 Subject: [PATCH 12/31] moving debug helpers --- .../SOSDacImpl.cs | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) 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 37d7d2557cad96..5e83aea5dec813 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -3868,6 +3868,20 @@ int ISOSDacInterface.GetWorkRequestData(ClrDataAddress addrWorkRequest, void* da } int ISOSDacInterface.TraverseEHInfo(ClrDataAddress ip, void* pCallback, void* token) => _legacyImpl is not null ? _legacyImpl.TraverseEHInfo(ip, pCallback, token) : HResults.E_NOTIMPL; + +#if DEBUG + private static List<(ulong VirtualAddress, nuint VirtualSize)> _debugTraverseLoaderHeapBlocks = new(); + private static uint _debugTraverseLoaderDebugCount; + + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })] + 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 int ISOSDacInterface.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, delegate* unmanaged[Stdcall] pCallback) { int hr = HResults.S_OK; @@ -4941,19 +4955,6 @@ int ISOSDacInterface12.GetGlobalAllocationContext(ClrDataAddress* allocPtr, ClrD #region ISOSDacInterface13 -#if DEBUG - private static List<(ulong VirtualAddress, nuint VirtualSize)> _debugTraverseLoaderHeapBlocks = new(); - private static uint _debugTraverseLoaderDebugCount; - - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })] - 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 int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*LoaderHeapKind*/ int kind, /*VISITHEAP*/ delegate* unmanaged[Stdcall] pCallback) { int hr = HResults.S_OK; From 221ef850934365e02a8ee1560f4414015ea7ad1d Mon Sep 17 00:00:00 2001 From: rcj1 Date: Fri, 6 Mar 2026 10:02:54 -0800 Subject: [PATCH 13/31] comments --- .../Contracts/ILoader.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 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 b981c0b75db5c3..0fdc094b889874 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 @@ -18,8 +18,8 @@ public ModuleHandle(TargetPointer address) public enum LoaderHeapKind { - Normal = 0, // UnlockedLoaderHeap / LoaderHeap - ExplicitControl = 1, // ExplicitControlLoaderHeap + Normal = 0, + ExplicitControl = 1, } [Flags] @@ -102,7 +102,6 @@ public interface ILoader : IContract 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. - // Throws NotImplementedException for unknown kind values. TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap) => throw new NotImplementedException(); TargetNUInt GetLoaderHeapBlockSize(TargetPointer block) => throw new NotImplementedException(); TargetPointer GetLoaderHeapBlockAddress(TargetPointer block) => throw new NotImplementedException(); From f21396f3e35cf0d99e98d744192edd79f8293d14 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Fri, 6 Mar 2026 18:43:58 -0800 Subject: [PATCH 14/31] Revert "simplify loader heap" This reverts commit 0b34106600587fdfa02e6a77827bcda0a44f3d78. --- docs/design/datacontracts/Loader.md | 28 +++- src/coreclr/debug/daccess/request.cpp | 21 ++- src/coreclr/inc/loaderheap.h | 26 ++-- .../vm/datadescriptor/datadescriptor.inc | 7 +- .../Contracts/ILoader.cs | 3 +- .../DataType.cs | 1 + .../Contracts/Loader_1.cs | 10 +- .../Data/ExplicitControlLoaderHeap.cs | 19 +++ .../SOSDacImpl.cs | 6 +- .../managed/cdac/tests/LoaderHeapTests.cs | 147 +++++++++++++++++- 10 files changed, 231 insertions(+), 37 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExplicitControlLoaderHeap.cs diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 4509d244d9a30e..df7ecb654a6785 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -90,12 +90,13 @@ TargetPointer GetDynamicIL(ModuleHandle handle, uint token); // Loader heap traversal enum LoaderHeapKind { - Normal = 0, - ExplicitControl = 1, + Normal = 0, // UnlockedLoaderHeap / LoaderHeap + ExplicitControl = 1, // ExplicitControlLoaderHeap } // Returns the first block of the loader heap linked list, or TargetPointer.Null if the heap has no blocks. -TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap); +// Throws NotImplementedException for unknown kind values. +TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap, LoaderHeapKind kind); // Returns the size of the reserved virtual memory region for the given loader heap block TargetNUInt GetLoaderHeapBlockSize(TargetPointer block); // Returns the start address of the reserved virtual memory for the given loader heap block @@ -175,7 +176,8 @@ TargetPointer GetNextLoaderHeapBlock(TargetPointer block); | `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 | +| `LoaderHeap` | `FirstBlock` | Pointer to the first `LoaderHeapBlock` in the linked list (normal heaps: `UnlockedLoaderHeap`/`LoaderHeap`) | +| `ExplicitControlLoaderHeap` | `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 | @@ -782,12 +784,24 @@ class InstMethodHashTable } ``` -#### LoaderHeapBlock +#### GetFirstLoaderHeapBlock, GetLoaderHeapBlockAddress, GetLoaderHeapBlockSize, GetNextLoaderHeapBlock + +Both `UnlockedLoaderHeap`/`LoaderHeap` (normal) and `ExplicitControlLoaderHeap` (explicit-control) inherit from +`UnlockedLoaderHeapBaseTraversable`, which holds `m_pFirstBlock`. Each kind maps to a separate cDAC type +(`LoaderHeap` and `ExplicitControlLoaderHeap` respectively) so callers are explicit about which type they are +traversing. The `LoaderHeapKind` enum encodes the choice, and an unknown kind throws `NotImplementedException`. ```csharp -TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap) +TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap, LoaderHeapKind kind) { - return target.ReadPointer(loaderHeap + /* LoaderHeap::FirstBlock offset */); + return kind switch + { + LoaderHeapKind.Normal => + target.ReadPointer(loaderHeap + /* LoaderHeap::FirstBlock offset */), + LoaderHeapKind.ExplicitControl => + target.ReadPointer(loaderHeap + /* ExplicitControlLoaderHeap::FirstBlock offset */), + _ => throw new NotImplementedException($"Unknown loader heap kind: {kind}"), + }; } TargetPointer ILoader.GetLoaderHeapBlockAddress(TargetPointer block) diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index b17ce9d3e0a486..310873f6e6232a 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -3577,7 +3577,7 @@ ClrDataAccess::TraverseLoaderHeap(CLRDATA_ADDRESS loaderHeapAddr, VISITHEAP pFun SOSDacEnter(); - hr = TraverseLoaderHeapBlock(PTR_UnlockedLoaderHeapBaseTraversable(TO_TADDR(loaderHeapAddr))->m_pFirstBlock, pFunc); + hr = TraverseLoaderHeapBlock(PTR_UnlockedLoaderHeapBase(TO_TADDR(loaderHeapAddr))->m_pFirstBlock, pFunc); SOSDacLeave(); return hr; @@ -3591,11 +3591,22 @@ ClrDataAccess::TraverseLoaderHeap(CLRDATA_ADDRESS loaderHeapAddr, LoaderHeapKind if (loaderHeapAddr == 0 || pCallback == 0) return E_INVALIDARG; - if (kind != LoaderHeapKindNormal && kind != LoaderHeapKindExplicitControl) - return E_NOTIMPL; - SOSDacEnter(); - hr = TraverseLoaderHeapBlock(PTR_UnlockedLoaderHeapBaseTraversable(TO_TADDR(loaderHeapAddr))->m_pFirstBlock, pCallback); + + 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; + } SOSDacLeave(); return hr; diff --git a/src/coreclr/inc/loaderheap.h b/src/coreclr/inc/loaderheap.h index 155fb7c8a8be3b..fc1d80ce634024 100644 --- a/src/coreclr/inc/loaderheap.h +++ b/src/coreclr/inc/loaderheap.h @@ -175,12 +175,10 @@ enum class LoaderHeapImplementationKind Interleaved }; -typedef DPTR(class UnlockedLoaderHeapBaseTraversable) PTR_UnlockedLoaderHeapBaseTraversable; class UnlockedLoaderHeapBaseTraversable { protected: #ifdef DACCESS_COMPILE - friend class ClrDataAccess; UnlockedLoaderHeapBaseTraversable() {} #else UnlockedLoaderHeapBaseTraversable() : @@ -198,26 +196,21 @@ class UnlockedLoaderHeapBaseTraversable typedef bool EnumPageRegionsCallback (PTR_VOID pvArgs, PTR_VOID pvAllocationBase, SIZE_T cbReserved); void EnumPageRegions (EnumPageRegionsCallback *pCallback, PTR_VOID pvArgs); #endif - virtual ~UnlockedLoaderHeapBaseTraversable() {} - friend struct cdac_data; + protected: // Linked list of ClrVirtualAlloc'd pages 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 { + friend struct cdac_data; #ifdef _DEBUG friend class LoaderHeapSniffer; #endif @@ -290,6 +283,12 @@ class UnlockedLoaderHeapBase : public UnlockedLoaderHeapBaseTraversable, public #endif }; +template<> +struct cdac_data +{ + static constexpr size_t FirstBlock = offsetof(UnlockedLoaderHeapBase, m_pFirstBlock); +}; + //=============================================================================== // This is the base class for LoaderHeap It's used as a simple // allocator that's semantically (but not perfwise!) equivalent to a blackbox @@ -608,6 +607,7 @@ class UnlockedInterleavedLoaderHeap : public UnlockedLoaderHeapBase typedef DPTR(class ExplicitControlLoaderHeap) PTR_ExplicitControlLoaderHeap; class ExplicitControlLoaderHeap : public UnlockedLoaderHeapBaseTraversable { + friend struct cdac_data; #ifdef DACCESS_COMPILE friend class ClrDataAccess; #endif @@ -702,6 +702,12 @@ class ExplicitControlLoaderHeap : public UnlockedLoaderHeapBaseTraversable void SetReservedRegion(BYTE* dwReservedRegionAddress, SIZE_T dwReservedRegionSize, BOOL fReleaseMemory); }; +template<> +struct cdac_data +{ + static constexpr size_t FirstBlock = offsetof(ExplicitControlLoaderHeap, m_pFirstBlock); +}; + //=============================================================================== // Create the LoaderHeap lock. It's the same lock for several different Heaps. //=============================================================================== diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index bf89cf1399ad40..6c828e67a3114b 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -252,9 +252,14 @@ CDAC_TYPE_END(LoaderAllocator) CDAC_TYPE_BEGIN(LoaderHeap) CDAC_TYPE_INDETERMINATE(LoaderHeap) -CDAC_TYPE_FIELD(LoaderHeap, /*pointer*/, FirstBlock, cdac_data::FirstBlock) +CDAC_TYPE_FIELD(LoaderHeap, /*pointer*/, FirstBlock, cdac_data::FirstBlock) CDAC_TYPE_END(LoaderHeap) +CDAC_TYPE_BEGIN(ExplicitControlLoaderHeap) +CDAC_TYPE_INDETERMINATE(ExplicitControlLoaderHeap) +CDAC_TYPE_FIELD(ExplicitControlLoaderHeap, /*pointer*/, FirstBlock, cdac_data::FirstBlock) +CDAC_TYPE_END(ExplicitControlLoaderHeap) + CDAC_TYPE_BEGIN(LoaderHeapBlock) CDAC_TYPE_FIELD(LoaderHeapBlock, /*pointer*/, Next, offsetof(LoaderHeapBlock, pNext)) CDAC_TYPE_FIELD(LoaderHeapBlock, /*pointer*/, VirtualAddress, offsetof(LoaderHeapBlock, pVirtualAddress)) 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 0fdc094b889874..a3ce9324b0de71 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 @@ -102,7 +102,8 @@ public interface ILoader : IContract 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(); + // Throws NotImplementedException for unknown kind values. + TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap, LoaderHeapKind kind) => throw new NotImplementedException(); TargetNUInt GetLoaderHeapBlockSize(TargetPointer block) => throw new NotImplementedException(); TargetPointer GetLoaderHeapBlockAddress(TargetPointer block) => throw new NotImplementedException(); // Returns the next block in the loader heap linked list, or TargetPointer.Null if there are no more blocks 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 0376143b8399ef..a2219019259205 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -42,6 +42,7 @@ public enum DataType Assembly, LoaderAllocator, LoaderHeap, + ExplicitControlLoaderHeap, LoaderHeapBlock, PEAssembly, AssemblyBinder, 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 75b50999e4aee9..f7abfbc8d7b33b 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,9 +553,15 @@ TargetPointer ILoader.GetDynamicIL(ModuleHandle handle, uint token) return shashContract.LookupSHash(dynamicILBlobTable.HashTable, token).EntryIL; } - TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap) + TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap, LoaderHeapKind kind) { - return _target.ProcessedData.GetOrAdd(loaderHeap).FirstBlock; + return kind switch + { + LoaderHeapKind.Normal => _target.ProcessedData.GetOrAdd(loaderHeap).FirstBlock, + LoaderHeapKind.ExplicitControl => _target.ProcessedData.GetOrAdd(loaderHeap).FirstBlock, + // NotImplementedException maps to E_NOTIMPL, matching native DAC behavior for unknown heap kinds. + _ => throw new NotImplementedException($"Unknown loader heap kind: {kind}"), + }; } TargetNUInt ILoader.GetLoaderHeapBlockSize(TargetPointer block) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExplicitControlLoaderHeap.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExplicitControlLoaderHeap.cs new file mode 100644 index 00000000000000..dd93d88ffc5bf5 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExplicitControlLoaderHeap.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 ExplicitControlLoaderHeap : IData +{ + static ExplicitControlLoaderHeap IData.Create(Target target, TargetPointer address) + => new ExplicitControlLoaderHeap(target, address); + + public ExplicitControlLoaderHeap(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.ExplicitControlLoaderHeap); + + 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.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index 5e83aea5dec813..91fe1a838e6c9b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -3897,7 +3897,7 @@ int ISOSDacInterface.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, delegate* Contracts.ILoader loader = _target.Contracts.Loader; TargetPointer heapAddr = loaderHeapAddr.ToTargetPointer(_target); - TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr); + TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr, Contracts.LoaderHeapKind.Normal); TargetPointer firstBlock = block; int i = 0; while (block != TargetPointer.Null && i++ < iterationMax) @@ -4966,13 +4966,11 @@ int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*Loade { if (loaderHeapAddr == 0 || pCallback is null) throw new ArgumentException(); - if (kind != (int)Contracts.LoaderHeapKind.Normal && kind != (int)Contracts.LoaderHeapKind.ExplicitControl) - throw new NotImplementedException(); int iterationMax = 8192; Contracts.ILoader loader = _target.Contracts.Loader; TargetPointer heapAddr = loaderHeapAddr.ToTargetPointer(_target); - TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr); + TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr, (Contracts.LoaderHeapKind)kind); TargetPointer firstBlock = block; int i = 0; while (block != TargetPointer.Null && i++ < iterationMax) diff --git a/src/native/managed/cdac/tests/LoaderHeapTests.cs b/src/native/managed/cdac/tests/LoaderHeapTests.cs index 6170894fd16c14..631b6f8800fa00 100644 --- a/src/native/managed/cdac/tests/LoaderHeapTests.cs +++ b/src/native/managed/cdac/tests/LoaderHeapTests.cs @@ -23,6 +23,15 @@ public class LoaderHeapTests ] }; + private static readonly MockDescriptors.TypeFields ExplicitControlLoaderHeapFields = new MockDescriptors.TypeFields() + { + DataType = DataType.ExplicitControlLoaderHeap, + Fields = + [ + new(nameof(Data.ExplicitControlLoaderHeap.FirstBlock), DataType.pointer), + ] + }; + private static readonly MockDescriptors.TypeFields LoaderHeapBlockFields = new MockDescriptors.TypeFields() { DataType = DataType.LoaderHeapBlock, @@ -36,7 +45,7 @@ public class LoaderHeapTests private static Dictionary GetTypes(TargetTestHelpers helpers) { - return MockDescriptors.GetTypesForTypeFields(helpers, [LoaderHeapFields, LoaderHeapBlockFields]); + return MockDescriptors.GetTypesForTypeFields(helpers, [LoaderHeapFields, ExplicitControlLoaderHeapFields, LoaderHeapBlockFields]); } private static Target CreateTarget(MockTarget.Architecture arch, Dictionary types, MockMemorySpace.Builder builder) @@ -49,7 +58,7 @@ private static Target CreateTarget(MockTarget.Architecture arch, Dictionary types = GetTypes(helpers); + + Target.TypeInfo heapType = types[DataType.ExplicitControlLoaderHeap]; + MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "ExplicitControlLoaderHeap"); + // FirstBlock is zero (null) by default + builder.AddHeapFragment(heapFragment); + + Target target = CreateTarget(arch, types, builder); + ILoader loader = target.Contracts.Loader; + + TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.ExplicitControl); + Assert.Equal(TargetPointer.Null, firstBlock); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void UnknownKind_ThrowsNotImplemented(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockMemorySpace.BumpAllocator allocator = builder.CreateAllocator(DefaultAllocationRangeStart, DefaultAllocationRangeEnd); + Dictionary types = GetTypes(helpers); + + Target.TypeInfo heapType = types[DataType.LoaderHeap]; + MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "LoaderHeap"); + builder.AddHeapFragment(heapFragment); + + Target target = CreateTarget(arch, types, builder); + ILoader loader = target.Contracts.Loader; + + Assert.Throws(() => loader.GetFirstLoaderHeapBlock(heapFragment.Address, (LoaderHeapKind)99)); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SingleBlockLoaderHeap_Normal(MockTarget.Architecture arch) { TargetTestHelpers helpers = new(arch); MockMemorySpace.Builder builder = new(helpers); @@ -95,7 +144,44 @@ public void SingleBlockLoaderHeap(MockTarget.Architecture arch) Target target = CreateTarget(arch, types, builder); ILoader loader = target.Contracts.Loader; - TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address); + TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.Normal); + Assert.Equal((TargetPointer)blockFragment.Address, firstBlock); + + Assert.Equal(virtualAddress, loader.GetLoaderHeapBlockAddress(firstBlock).Value); + Assert.Equal(virtualSize, loader.GetLoaderHeapBlockSize(firstBlock).Value); + + TargetPointer nextBlock = loader.GetNextLoaderHeapBlock(firstBlock); + Assert.Equal(TargetPointer.Null, nextBlock); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SingleBlockLoaderHeap_ExplicitControl(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockMemorySpace.BumpAllocator allocator = builder.CreateAllocator(DefaultAllocationRangeStart, DefaultAllocationRangeEnd); + Dictionary types = GetTypes(helpers); + + Target.TypeInfo heapType = types[DataType.ExplicitControlLoaderHeap]; + Target.TypeInfo blockType = types[DataType.LoaderHeapBlock]; + + ulong virtualAddress = 0x5678_0000UL; + ulong virtualSize = 0x2000UL; + MockMemorySpace.HeapFragment blockFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(blockType), "LoaderHeapBlock"); + helpers.WritePointer(blockFragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.Next)].Offset, helpers.PointerSize), TargetPointer.Null.Value); + helpers.WritePointer(blockFragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualAddress)].Offset, helpers.PointerSize), virtualAddress); + helpers.WriteNUInt(blockFragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualSize)].Offset, helpers.PointerSize), new TargetNUInt(virtualSize)); + builder.AddHeapFragment(blockFragment); + + MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "ExplicitControlLoaderHeap"); + helpers.WritePointer(heapFragment.Data.AsSpan().Slice(heapType.Fields[nameof(Data.ExplicitControlLoaderHeap.FirstBlock)].Offset, helpers.PointerSize), blockFragment.Address); + builder.AddHeapFragment(heapFragment); + + Target target = CreateTarget(arch, types, builder); + ILoader loader = target.Contracts.Loader; + + TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.ExplicitControl); Assert.Equal((TargetPointer)blockFragment.Address, firstBlock); Assert.Equal(virtualAddress, loader.GetLoaderHeapBlockAddress(firstBlock).Value); @@ -107,7 +193,7 @@ public void SingleBlockLoaderHeap(MockTarget.Architecture arch) [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void MultipleBlockLoaderHeap(MockTarget.Architecture arch) + public void MultipleBlockLoaderHeap_Normal(MockTarget.Architecture arch) { TargetTestHelpers helpers = new(arch); MockMemorySpace.Builder builder = new(helpers); @@ -140,7 +226,54 @@ public void MultipleBlockLoaderHeap(MockTarget.Architecture arch) ILoader loader = target.Contracts.Loader; List<(ulong Address, ulong Size)> blocks = []; - TargetPointer block = loader.GetFirstLoaderHeapBlock(heapFragment.Address); + TargetPointer block = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.Normal); + while (block != TargetPointer.Null) + { + blocks.Add((loader.GetLoaderHeapBlockAddress(block).Value, loader.GetLoaderHeapBlockSize(block).Value)); + block = loader.GetNextLoaderHeapBlock(block); + } + + Assert.Equal(2, blocks.Count); + Assert.Equal((virtualAddresses[0], virtualSizes[0]), blocks[0]); + Assert.Equal((virtualAddresses[1], virtualSizes[1]), blocks[1]); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void MultipleBlockLoaderHeap_ExplicitControl(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockMemorySpace.BumpAllocator allocator = builder.CreateAllocator(DefaultAllocationRangeStart, DefaultAllocationRangeEnd); + Dictionary types = GetTypes(helpers); + + Target.TypeInfo heapType = types[DataType.ExplicitControlLoaderHeap]; + Target.TypeInfo blockType = types[DataType.LoaderHeapBlock]; + + ulong[] virtualAddresses = [0x3000_0000UL, 0x4000_0000UL]; + ulong[] virtualSizes = [0x4000UL, 0x8000UL]; + + MockMemorySpace.HeapFragment block2Fragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(blockType), "ExplicitBlock2"); + helpers.WritePointer(block2Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.Next)].Offset, helpers.PointerSize), TargetPointer.Null.Value); + helpers.WritePointer(block2Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualAddress)].Offset, helpers.PointerSize), virtualAddresses[1]); + helpers.WriteNUInt(block2Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualSize)].Offset, helpers.PointerSize), new TargetNUInt(virtualSizes[1])); + builder.AddHeapFragment(block2Fragment); + + MockMemorySpace.HeapFragment block1Fragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(blockType), "ExplicitBlock1"); + helpers.WritePointer(block1Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.Next)].Offset, helpers.PointerSize), block2Fragment.Address); + helpers.WritePointer(block1Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualAddress)].Offset, helpers.PointerSize), virtualAddresses[0]); + helpers.WriteNUInt(block1Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualSize)].Offset, helpers.PointerSize), new TargetNUInt(virtualSizes[0])); + builder.AddHeapFragment(block1Fragment); + + MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "ExplicitControlLoaderHeap"); + helpers.WritePointer(heapFragment.Data.AsSpan().Slice(heapType.Fields[nameof(Data.ExplicitControlLoaderHeap.FirstBlock)].Offset, helpers.PointerSize), block1Fragment.Address); + builder.AddHeapFragment(heapFragment); + + Target target = CreateTarget(arch, types, builder); + ILoader loader = target.Contracts.Loader; + + List<(ulong Address, ulong Size)> blocks = []; + TargetPointer block = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.ExplicitControl); while (block != TargetPointer.Null) { blocks.Add((loader.GetLoaderHeapBlockAddress(block).Value, loader.GetLoaderHeapBlockSize(block).Value)); From 60e0be18ab2e75d1cfc8f2bba46405a7fd4bb915 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Sat, 7 Mar 2026 09:19:25 -0800 Subject: [PATCH 15/31] code review --- .../ISOSDacInterface.cs | 4 +- .../SOSDacImpl.cs | 39 +++++++++++-------- 2 files changed, 24 insertions(+), 19 deletions(-) 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 d25128d8a14cb0..ed601327e4a9eb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs @@ -663,7 +663,7 @@ public unsafe partial interface ISOSDacInterface // Heaps [PreserveSig] - int TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*VISITHEAP*/ delegate* unmanaged[Stdcall] pCallback); + int TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*VISITHEAP*/ delegate* unmanaged pCallback); [PreserveSig] int GetCodeHeapList(ClrDataAddress jitManager, uint count, /*struct DacpJitCodeHeapInfo*/ void* codeHeaps, uint* pNeeded); [PreserveSig] @@ -943,7 +943,7 @@ public unsafe partial interface ISOSDacInterface12 public unsafe partial interface ISOSDacInterface13 { [PreserveSig] - int TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*LoaderHeapKind*/ int kind, /*VISITHEAP*/ delegate* unmanaged[Stdcall]< /*ClrDataAddress*/ ulong, nuint, Interop.BOOL, void> 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 91fe1a838e6c9b..ad045e2c7f9104 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -3870,23 +3870,28 @@ int ISOSDacInterface.TraverseEHInfo(ClrDataAddress ip, void* pCallback, void* to => _legacyImpl is not null ? _legacyImpl.TraverseEHInfo(ip, pCallback, token) : HResults.E_NOTIMPL; #if DEBUG - private static List<(ulong VirtualAddress, nuint VirtualSize)> _debugTraverseLoaderHeapBlocks = new(); + [ThreadStatic] + private static List<(ulong VirtualAddress, nuint VirtualSize)>? _debugTraverseLoaderHeapBlocks; + [ThreadStatic] private static uint _debugTraverseLoaderDebugCount; - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })] + 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; + 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 - int ISOSDacInterface.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, delegate* unmanaged[Stdcall] pCallback) + int ISOSDacInterface.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, delegate* unmanaged pCallback) { int hr = HResults.S_OK; #if DEBUG - _debugTraverseLoaderHeapBlocks.Clear(); + DebugTraverseLoaderHeapBlocks.Clear(); _debugTraverseLoaderDebugCount = 0; #endif try @@ -3915,7 +3920,7 @@ int ISOSDacInterface.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, delegate* } pCallback(blockAddress.Value, (nuint)blockSize.Value, block == firstBlock ? Interop.BOOL.TRUE : Interop.BOOL.FALSE); #if DEBUG - _debugTraverseLoaderHeapBlocks.Add((blockAddress.Value, (nuint)blockSize.Value)); + DebugTraverseLoaderHeapBlocks.Add((blockAddress.Value, (nuint)blockSize.Value)); #endif block = loader.GetNextLoaderHeapBlock(block); if (block == firstBlock) @@ -3931,12 +3936,12 @@ int ISOSDacInterface.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, delegate* #if DEBUG if (_legacyImpl is not null) { - int cdacCount = _debugTraverseLoaderHeapBlocks.Count; - delegate* unmanaged[Stdcall] debugCallbackPtr = &TraverseLoaderHeapDebugCallback; + int cdacCount = DebugTraverseLoaderHeapBlocks.Count; + delegate* unmanaged debugCallbackPtr = &TraverseLoaderHeapDebugCallback; int hrLocal = _legacyImpl.TraverseLoaderHeap(loaderHeapAddr, debugCallbackPtr); Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); - Debug.Assert(_debugTraverseLoaderHeapBlocks.Count == 0, - $"cDAC found {cdacCount} blocks, DAC matched {_debugTraverseLoaderDebugCount}, {_debugTraverseLoaderHeapBlocks.Count} unmatched"); + 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"); } @@ -4955,11 +4960,11 @@ int ISOSDacInterface12.GetGlobalAllocationContext(ClrDataAddress* allocPtr, ClrD #region ISOSDacInterface13 - int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*LoaderHeapKind*/ int kind, /*VISITHEAP*/ delegate* unmanaged[Stdcall] pCallback) + int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*LoaderHeapKind*/ int kind, /*VISITHEAP*/ delegate* unmanaged pCallback) { int hr = HResults.S_OK; #if DEBUG - _debugTraverseLoaderHeapBlocks.Clear(); + DebugTraverseLoaderHeapBlocks.Clear(); _debugTraverseLoaderDebugCount = 0; #endif try @@ -4988,7 +4993,7 @@ int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*Loade } pCallback(blockAddress.Value, (nuint)blockSize.Value, block == firstBlock ? Interop.BOOL.TRUE : Interop.BOOL.FALSE); #if DEBUG - _debugTraverseLoaderHeapBlocks.Add((blockAddress.Value, (nuint)blockSize.Value)); + DebugTraverseLoaderHeapBlocks.Add((blockAddress.Value, (nuint)blockSize.Value)); #endif block = loader.GetNextLoaderHeapBlock(block); if (block == firstBlock) @@ -5004,12 +5009,12 @@ int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*Loade #if DEBUG if (_legacyImpl13 is not null) { - int cdacCount = _debugTraverseLoaderHeapBlocks.Count; - delegate* unmanaged[Stdcall] debugCallbackPtr = &TraverseLoaderHeapDebugCallback; + int cdacCount = DebugTraverseLoaderHeapBlocks.Count; + delegate* unmanaged debugCallbackPtr = &TraverseLoaderHeapDebugCallback; int hrLocal = _legacyImpl13.TraverseLoaderHeap(loaderHeapAddr, kind, debugCallbackPtr); Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); - Debug.Assert(_debugTraverseLoaderHeapBlocks.Count == 0, - $"cDAC found {cdacCount} blocks, DAC matched {_debugTraverseLoaderDebugCount}, {_debugTraverseLoaderHeapBlocks.Count} unmatched"); + 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"); } From d8c4a3fb4968b3fc24352b9c7abf82834bd304a3 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Mon, 9 Mar 2026 09:15:26 -0700 Subject: [PATCH 16/31] fix callconv --- .../ISOSDacInterface.cs | 4 ++-- .../SOSDacImpl.cs | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) 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 ed601327e4a9eb..02e9da4406c62e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs @@ -521,7 +521,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] @@ -689,7 +689,7 @@ public unsafe partial interface ISOSDacInterface [PreserveSig] int GetCCWInterfaces(ClrDataAddress ccw, uint count, /*struct DacpCOMInterfacePointerData*/ void* 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 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 ad045e2c7f9104..ee9a7e74e0cc50 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -3950,7 +3950,7 @@ int ISOSDacInterface.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, delegate* } #if DEBUG - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })] + [UnmanagedCallersOnly] private static void TraverseModuleMapCallback(uint index, ulong moduleAddr, void* expectedElements) { var expectedElementsDict = (Dictionary)GCHandle.FromIntPtr((nint)expectedElements).Target!; @@ -3964,7 +3964,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)>(); @@ -4004,7 +4004,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.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); @@ -4015,7 +4015,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!; @@ -4030,7 +4030,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(); @@ -4064,7 +4064,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.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); From 36f229e9f1391e2d547528e944d640b153d03bb5 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Mon, 23 Mar 2026 17:09:38 -0700 Subject: [PATCH 17/31] incorporating refactor --- docs/design/datacontracts/Loader.md | 26 +--- src/coreclr/debug/daccess/request.cpp | 17 +- src/coreclr/inc/loaderheap.h | 29 ++-- .../vm/datadescriptor/datadescriptor.inc | 7 +- .../Contracts/ILoader.cs | 7 +- .../DataType.cs | 1 - .../Contracts/Loader_1.cs | 10 +- .../Data/ExplicitControlLoaderHeap.cs | 19 --- .../SOSDacImpl.cs | 17 +- .../managed/cdac/tests/LoaderHeapTests.cs | 147 +----------------- 10 files changed, 39 insertions(+), 241 deletions(-) delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExplicitControlLoaderHeap.cs diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 415b14872ebf46..25fa2b9a26e280 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -86,17 +86,9 @@ TargetPointer GetStubHeap(TargetPointer loaderAllocatorPointer); TargetPointer GetObjectHandle(TargetPointer loaderAllocatorPointer); TargetPointer GetILHeader(ModuleHandle handle, uint token); TargetPointer GetDynamicIL(ModuleHandle handle, uint token); - -// Loader heap traversal -enum LoaderHeapKind -{ - Normal = 0, // UnlockedLoaderHeap / LoaderHeap - ExplicitControl = 1, // ExplicitControlLoaderHeap -} - // Returns the first block of the loader heap linked list, or TargetPointer.Null if the heap has no blocks. // Throws NotImplementedException for unknown kind values. -TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap, LoaderHeapKind kind); +TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap); // Returns the size of the reserved virtual memory region for the given loader heap block TargetNUInt GetLoaderHeapBlockSize(TargetPointer block); // Returns the start address of the reserved virtual memory for the given loader heap block @@ -893,22 +885,10 @@ class InstMethodHashTable #### GetFirstLoaderHeapBlock, GetLoaderHeapBlockAddress, GetLoaderHeapBlockSize, GetNextLoaderHeapBlock -Both `UnlockedLoaderHeap`/`LoaderHeap` (normal) and `ExplicitControlLoaderHeap` (explicit-control) inherit from -`UnlockedLoaderHeapBaseTraversable`, which holds `m_pFirstBlock`. Each kind maps to a separate cDAC type -(`LoaderHeap` and `ExplicitControlLoaderHeap` respectively) so callers are explicit about which type they are -traversing. The `LoaderHeapKind` enum encodes the choice, and an unknown kind throws `NotImplementedException`. - ```csharp -TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap, LoaderHeapKind kind) +TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap) { - return kind switch - { - LoaderHeapKind.Normal => - target.ReadPointer(loaderHeap + /* LoaderHeap::FirstBlock offset */), - LoaderHeapKind.ExplicitControl => - target.ReadPointer(loaderHeap + /* ExplicitControlLoaderHeap::FirstBlock offset */), - _ => throw new NotImplementedException($"Unknown loader heap kind: {kind}"), - }; + return target.ReadPointer(loaderHeap + /* ExplicitControlLoaderHeap::FirstBlock offset */); } TargetPointer ILoader.GetLoaderHeapBlockAddress(TargetPointer block) diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index b644768a3e90c1..9420e6e74cb414 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -3577,7 +3577,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; @@ -3593,20 +3593,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 fc1d80ce634024..5c5b2b2b9b3cd5 100644 --- a/src/coreclr/inc/loaderheap.h +++ b/src/coreclr/inc/loaderheap.h @@ -175,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() {} @@ -189,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); @@ -202,6 +209,12 @@ 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 @@ -210,7 +223,6 @@ typedef bool EnumPageRegionsCallback (PTR_VOID pvArgs, PTR_VOID pvAllocationBase typedef DPTR(class UnlockedLoaderHeapBase) PTR_UnlockedLoaderHeapBase; class UnlockedLoaderHeapBase : public UnlockedLoaderHeapBaseTraversable, public ILoaderHeapBackout { - friend struct cdac_data; #ifdef _DEBUG friend class LoaderHeapSniffer; #endif @@ -283,12 +295,6 @@ class UnlockedLoaderHeapBase : public UnlockedLoaderHeapBaseTraversable, public #endif }; -template<> -struct cdac_data -{ - static constexpr size_t FirstBlock = offsetof(UnlockedLoaderHeapBase, m_pFirstBlock); -}; - //=============================================================================== // This is the base class for LoaderHeap It's used as a simple // allocator that's semantically (but not perfwise!) equivalent to a blackbox @@ -607,10 +613,6 @@ class UnlockedInterleavedLoaderHeap : public UnlockedLoaderHeapBase typedef DPTR(class ExplicitControlLoaderHeap) PTR_ExplicitControlLoaderHeap; class ExplicitControlLoaderHeap : public UnlockedLoaderHeapBaseTraversable { - friend struct cdac_data; -#ifdef DACCESS_COMPILE - friend class ClrDataAccess; -#endif private: // Allocation pointer in current block @@ -702,11 +704,6 @@ class ExplicitControlLoaderHeap : public UnlockedLoaderHeapBaseTraversable void SetReservedRegion(BYTE* dwReservedRegionAddress, SIZE_T dwReservedRegionSize, BOOL fReleaseMemory); }; -template<> -struct cdac_data -{ - static constexpr size_t FirstBlock = offsetof(ExplicitControlLoaderHeap, m_pFirstBlock); -}; //=============================================================================== // Create the LoaderHeap lock. It's the same lock for several different Heaps. diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 275b2c8b0b4a78..2ebd344e1dd3a5 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -278,14 +278,9 @@ CDAC_TYPE_END(LoaderAllocator) CDAC_TYPE_BEGIN(LoaderHeap) CDAC_TYPE_INDETERMINATE(LoaderHeap) -CDAC_TYPE_FIELD(LoaderHeap, /*pointer*/, FirstBlock, cdac_data::FirstBlock) +CDAC_TYPE_FIELD(LoaderHeap, /*pointer*/, FirstBlock, cdac_data::FirstBlock) CDAC_TYPE_END(LoaderHeap) -CDAC_TYPE_BEGIN(ExplicitControlLoaderHeap) -CDAC_TYPE_INDETERMINATE(ExplicitControlLoaderHeap) -CDAC_TYPE_FIELD(ExplicitControlLoaderHeap, /*pointer*/, FirstBlock, cdac_data::FirstBlock) -CDAC_TYPE_END(ExplicitControlLoaderHeap) - CDAC_TYPE_BEGIN(LoaderHeapBlock) CDAC_TYPE_FIELD(LoaderHeapBlock, /*pointer*/, Next, offsetof(LoaderHeapBlock, pNext)) CDAC_TYPE_FIELD(LoaderHeapBlock, /*pointer*/, VirtualAddress, offsetof(LoaderHeapBlock, pVirtualAddress)) 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 dcc5dea74e902e..aa3be87f9131d1 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 @@ -16,11 +16,6 @@ public ModuleHandle(TargetPointer address) public TargetPointer Address { get; } } -public enum LoaderHeapKind -{ - Normal = 0, - ExplicitControl = 1, -} [Flags] public enum ModuleFlags @@ -103,7 +98,7 @@ public interface ILoader : IContract // Returns the first block of the loader heap linked list, or TargetPointer.Null if the heap has no blocks. // Throws NotImplementedException for unknown kind values. - TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap, LoaderHeapKind kind) => throw new NotImplementedException(); + TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap) => throw new NotImplementedException(); TargetNUInt GetLoaderHeapBlockSize(TargetPointer block) => throw new NotImplementedException(); TargetPointer GetLoaderHeapBlockAddress(TargetPointer block) => throw new NotImplementedException(); // Returns the next block in the loader heap linked list, or TargetPointer.Null if there are no more blocks 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 b3bc70b816450f..a41b9e9178bf16 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -46,7 +46,6 @@ public enum DataType Assembly, LoaderAllocator, LoaderHeap, - ExplicitControlLoaderHeap, LoaderHeapBlock, PEAssembly, AssemblyBinder, 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 284f1b85be287c..b00465c6670c09 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 @@ -601,15 +601,9 @@ TargetPointer ILoader.GetDynamicIL(ModuleHandle handle, uint token) return shashContract.LookupSHash(dynamicILBlobTable.HashTable, token).EntryIL; } - TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap, LoaderHeapKind kind) + TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap) { - return kind switch - { - LoaderHeapKind.Normal => _target.ProcessedData.GetOrAdd(loaderHeap).FirstBlock, - LoaderHeapKind.ExplicitControl => _target.ProcessedData.GetOrAdd(loaderHeap).FirstBlock, - // NotImplementedException maps to E_NOTIMPL, matching native DAC behavior for unknown heap kinds. - _ => throw new NotImplementedException($"Unknown loader heap kind: {kind}"), - }; + return _target.ProcessedData.GetOrAdd(loaderHeap).FirstBlock; } TargetNUInt ILoader.GetLoaderHeapBlockSize(TargetPointer block) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExplicitControlLoaderHeap.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExplicitControlLoaderHeap.cs deleted file mode 100644 index dd93d88ffc5bf5..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExplicitControlLoaderHeap.cs +++ /dev/null @@ -1,19 +0,0 @@ -// 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 ExplicitControlLoaderHeap : IData -{ - static ExplicitControlLoaderHeap IData.Create(Target target, TargetPointer address) - => new ExplicitControlLoaderHeap(target, address); - - public ExplicitControlLoaderHeap(Target target, TargetPointer address) - { - Target.TypeInfo type = target.GetTypeInfo(DataType.ExplicitControlLoaderHeap); - - 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.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index da5772c46c67d1..54a61884c23d61 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -4335,7 +4335,7 @@ int ISOSDacInterface.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, delegate* Contracts.ILoader loader = _target.Contracts.Loader; TargetPointer heapAddr = loaderHeapAddr.ToTargetPointer(_target); - TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr, Contracts.LoaderHeapKind.Normal); + TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr); TargetPointer firstBlock = block; int i = 0; while (block != TargetPointer.Null && i++ < iterationMax) @@ -4372,11 +4372,14 @@ int ISOSDacInterface.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, delegate* int cdacCount = DebugTraverseLoaderHeapBlocks.Count; delegate* unmanaged debugCallbackPtr = &TraverseLoaderHeapDebugCallback; int hrLocal = _legacyImpl.TraverseLoaderHeap(loaderHeapAddr, debugCallbackPtr); - Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); - 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"); + 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; @@ -5662,7 +5665,7 @@ int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*Loade Contracts.ILoader loader = _target.Contracts.Loader; TargetPointer heapAddr = loaderHeapAddr.ToTargetPointer(_target); - TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr, (Contracts.LoaderHeapKind)kind); + TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr); TargetPointer firstBlock = block; int i = 0; while (block != TargetPointer.Null && i++ < iterationMax) diff --git a/src/native/managed/cdac/tests/LoaderHeapTests.cs b/src/native/managed/cdac/tests/LoaderHeapTests.cs index 631b6f8800fa00..6170894fd16c14 100644 --- a/src/native/managed/cdac/tests/LoaderHeapTests.cs +++ b/src/native/managed/cdac/tests/LoaderHeapTests.cs @@ -23,15 +23,6 @@ public class LoaderHeapTests ] }; - private static readonly MockDescriptors.TypeFields ExplicitControlLoaderHeapFields = new MockDescriptors.TypeFields() - { - DataType = DataType.ExplicitControlLoaderHeap, - Fields = - [ - new(nameof(Data.ExplicitControlLoaderHeap.FirstBlock), DataType.pointer), - ] - }; - private static readonly MockDescriptors.TypeFields LoaderHeapBlockFields = new MockDescriptors.TypeFields() { DataType = DataType.LoaderHeapBlock, @@ -45,7 +36,7 @@ public class LoaderHeapTests private static Dictionary GetTypes(TargetTestHelpers helpers) { - return MockDescriptors.GetTypesForTypeFields(helpers, [LoaderHeapFields, ExplicitControlLoaderHeapFields, LoaderHeapBlockFields]); + return MockDescriptors.GetTypesForTypeFields(helpers, [LoaderHeapFields, LoaderHeapBlockFields]); } private static Target CreateTarget(MockTarget.Architecture arch, Dictionary types, MockMemorySpace.Builder builder) @@ -58,7 +49,7 @@ private static Target CreateTarget(MockTarget.Architecture arch, Dictionary types = GetTypes(helpers); - - Target.TypeInfo heapType = types[DataType.ExplicitControlLoaderHeap]; - MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "ExplicitControlLoaderHeap"); - // FirstBlock is zero (null) by default - builder.AddHeapFragment(heapFragment); - - Target target = CreateTarget(arch, types, builder); - ILoader loader = target.Contracts.Loader; - - TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.ExplicitControl); - Assert.Equal(TargetPointer.Null, firstBlock); - } - - [Theory] - [ClassData(typeof(MockTarget.StdArch))] - public void UnknownKind_ThrowsNotImplemented(MockTarget.Architecture arch) - { - TargetTestHelpers helpers = new(arch); - MockMemorySpace.Builder builder = new(helpers); - MockMemorySpace.BumpAllocator allocator = builder.CreateAllocator(DefaultAllocationRangeStart, DefaultAllocationRangeEnd); - Dictionary types = GetTypes(helpers); - - Target.TypeInfo heapType = types[DataType.LoaderHeap]; - MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "LoaderHeap"); - builder.AddHeapFragment(heapFragment); - - Target target = CreateTarget(arch, types, builder); - ILoader loader = target.Contracts.Loader; - - Assert.Throws(() => loader.GetFirstLoaderHeapBlock(heapFragment.Address, (LoaderHeapKind)99)); - } - - [Theory] - [ClassData(typeof(MockTarget.StdArch))] - public void SingleBlockLoaderHeap_Normal(MockTarget.Architecture arch) + public void SingleBlockLoaderHeap(MockTarget.Architecture arch) { TargetTestHelpers helpers = new(arch); MockMemorySpace.Builder builder = new(helpers); @@ -144,44 +95,7 @@ public void SingleBlockLoaderHeap_Normal(MockTarget.Architecture arch) Target target = CreateTarget(arch, types, builder); ILoader loader = target.Contracts.Loader; - TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.Normal); - Assert.Equal((TargetPointer)blockFragment.Address, firstBlock); - - Assert.Equal(virtualAddress, loader.GetLoaderHeapBlockAddress(firstBlock).Value); - Assert.Equal(virtualSize, loader.GetLoaderHeapBlockSize(firstBlock).Value); - - TargetPointer nextBlock = loader.GetNextLoaderHeapBlock(firstBlock); - Assert.Equal(TargetPointer.Null, nextBlock); - } - - [Theory] - [ClassData(typeof(MockTarget.StdArch))] - public void SingleBlockLoaderHeap_ExplicitControl(MockTarget.Architecture arch) - { - TargetTestHelpers helpers = new(arch); - MockMemorySpace.Builder builder = new(helpers); - MockMemorySpace.BumpAllocator allocator = builder.CreateAllocator(DefaultAllocationRangeStart, DefaultAllocationRangeEnd); - Dictionary types = GetTypes(helpers); - - Target.TypeInfo heapType = types[DataType.ExplicitControlLoaderHeap]; - Target.TypeInfo blockType = types[DataType.LoaderHeapBlock]; - - ulong virtualAddress = 0x5678_0000UL; - ulong virtualSize = 0x2000UL; - MockMemorySpace.HeapFragment blockFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(blockType), "LoaderHeapBlock"); - helpers.WritePointer(blockFragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.Next)].Offset, helpers.PointerSize), TargetPointer.Null.Value); - helpers.WritePointer(blockFragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualAddress)].Offset, helpers.PointerSize), virtualAddress); - helpers.WriteNUInt(blockFragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualSize)].Offset, helpers.PointerSize), new TargetNUInt(virtualSize)); - builder.AddHeapFragment(blockFragment); - - MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "ExplicitControlLoaderHeap"); - helpers.WritePointer(heapFragment.Data.AsSpan().Slice(heapType.Fields[nameof(Data.ExplicitControlLoaderHeap.FirstBlock)].Offset, helpers.PointerSize), blockFragment.Address); - builder.AddHeapFragment(heapFragment); - - Target target = CreateTarget(arch, types, builder); - ILoader loader = target.Contracts.Loader; - - TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.ExplicitControl); + TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address); Assert.Equal((TargetPointer)blockFragment.Address, firstBlock); Assert.Equal(virtualAddress, loader.GetLoaderHeapBlockAddress(firstBlock).Value); @@ -193,7 +107,7 @@ public void SingleBlockLoaderHeap_ExplicitControl(MockTarget.Architecture arch) [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void MultipleBlockLoaderHeap_Normal(MockTarget.Architecture arch) + public void MultipleBlockLoaderHeap(MockTarget.Architecture arch) { TargetTestHelpers helpers = new(arch); MockMemorySpace.Builder builder = new(helpers); @@ -226,54 +140,7 @@ public void MultipleBlockLoaderHeap_Normal(MockTarget.Architecture arch) ILoader loader = target.Contracts.Loader; List<(ulong Address, ulong Size)> blocks = []; - TargetPointer block = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.Normal); - while (block != TargetPointer.Null) - { - blocks.Add((loader.GetLoaderHeapBlockAddress(block).Value, loader.GetLoaderHeapBlockSize(block).Value)); - block = loader.GetNextLoaderHeapBlock(block); - } - - Assert.Equal(2, blocks.Count); - Assert.Equal((virtualAddresses[0], virtualSizes[0]), blocks[0]); - Assert.Equal((virtualAddresses[1], virtualSizes[1]), blocks[1]); - } - - [Theory] - [ClassData(typeof(MockTarget.StdArch))] - public void MultipleBlockLoaderHeap_ExplicitControl(MockTarget.Architecture arch) - { - TargetTestHelpers helpers = new(arch); - MockMemorySpace.Builder builder = new(helpers); - MockMemorySpace.BumpAllocator allocator = builder.CreateAllocator(DefaultAllocationRangeStart, DefaultAllocationRangeEnd); - Dictionary types = GetTypes(helpers); - - Target.TypeInfo heapType = types[DataType.ExplicitControlLoaderHeap]; - Target.TypeInfo blockType = types[DataType.LoaderHeapBlock]; - - ulong[] virtualAddresses = [0x3000_0000UL, 0x4000_0000UL]; - ulong[] virtualSizes = [0x4000UL, 0x8000UL]; - - MockMemorySpace.HeapFragment block2Fragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(blockType), "ExplicitBlock2"); - helpers.WritePointer(block2Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.Next)].Offset, helpers.PointerSize), TargetPointer.Null.Value); - helpers.WritePointer(block2Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualAddress)].Offset, helpers.PointerSize), virtualAddresses[1]); - helpers.WriteNUInt(block2Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualSize)].Offset, helpers.PointerSize), new TargetNUInt(virtualSizes[1])); - builder.AddHeapFragment(block2Fragment); - - MockMemorySpace.HeapFragment block1Fragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(blockType), "ExplicitBlock1"); - helpers.WritePointer(block1Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.Next)].Offset, helpers.PointerSize), block2Fragment.Address); - helpers.WritePointer(block1Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualAddress)].Offset, helpers.PointerSize), virtualAddresses[0]); - helpers.WriteNUInt(block1Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualSize)].Offset, helpers.PointerSize), new TargetNUInt(virtualSizes[0])); - builder.AddHeapFragment(block1Fragment); - - MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "ExplicitControlLoaderHeap"); - helpers.WritePointer(heapFragment.Data.AsSpan().Slice(heapType.Fields[nameof(Data.ExplicitControlLoaderHeap.FirstBlock)].Offset, helpers.PointerSize), block1Fragment.Address); - builder.AddHeapFragment(heapFragment); - - Target target = CreateTarget(arch, types, builder); - ILoader loader = target.Contracts.Loader; - - List<(ulong Address, ulong Size)> blocks = []; - TargetPointer block = loader.GetFirstLoaderHeapBlock(heapFragment.Address, LoaderHeapKind.ExplicitControl); + TargetPointer block = loader.GetFirstLoaderHeapBlock(heapFragment.Address); while (block != TargetPointer.Null) { blocks.Add((loader.GetLoaderHeapBlockAddress(block).Value, loader.GetLoaderHeapBlockSize(block).Value)); From 4f540317b5148235aacffdb59b265b459c593b89 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 24 Mar 2026 10:12:10 -0700 Subject: [PATCH 18/31] nits --- docs/design/datacontracts/Loader.md | 5 ++--- src/coreclr/inc/loaderheap.h | 2 -- .../Contracts/ILoader.cs | 2 -- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 25fa2b9a26e280..e28c43998c1920 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -178,8 +178,7 @@ IReadOnlyDictionary GetLoaderAllocatorHeaps(TargetPointer | `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 (normal heaps: `UnlockedLoaderHeap`/`LoaderHeap`) | -| `ExplicitControlLoaderHeap` | `FirstBlock` | Pointer to the first `LoaderHeapBlock` in the linked list | +| `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 | @@ -888,7 +887,7 @@ class InstMethodHashTable ```csharp TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap) { - return target.ReadPointer(loaderHeap + /* ExplicitControlLoaderHeap::FirstBlock offset */); + return target.ReadPointer(loaderHeap + /* LoaderHeap::FirstBlock offset */); } TargetPointer ILoader.GetLoaderHeapBlockAddress(TargetPointer block) diff --git a/src/coreclr/inc/loaderheap.h b/src/coreclr/inc/loaderheap.h index 5c5b2b2b9b3cd5..3f04cff5bef438 100644 --- a/src/coreclr/inc/loaderheap.h +++ b/src/coreclr/inc/loaderheap.h @@ -220,7 +220,6 @@ struct cdac_data // 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 @@ -704,7 +703,6 @@ class ExplicitControlLoaderHeap : public UnlockedLoaderHeapBaseTraversable void SetReservedRegion(BYTE* dwReservedRegionAddress, SIZE_T dwReservedRegionSize, BOOL fReleaseMemory); }; - //=============================================================================== // Create the LoaderHeap lock. It's the same lock for several different Heaps. //=============================================================================== 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 aa3be87f9131d1..684a578ba5f9f8 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 @@ -16,7 +16,6 @@ public ModuleHandle(TargetPointer address) public TargetPointer Address { get; } } - [Flags] public enum ModuleFlags { @@ -97,7 +96,6 @@ public interface ILoader : IContract 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. - // Throws NotImplementedException for unknown kind values. TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap) => throw new NotImplementedException(); TargetNUInt GetLoaderHeapBlockSize(TargetPointer block) => throw new NotImplementedException(); TargetPointer GetLoaderHeapBlockAddress(TargetPointer block) => throw new NotImplementedException(); From 5c557d9b1031d48eb0d514e5e1793683812d28c3 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 24 Mar 2026 11:38:00 -0700 Subject: [PATCH 19/31] validation --- .../SOSDacImpl.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) 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 54a61884c23d61..ec2cf6ecccb2ac 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -5702,11 +5702,14 @@ int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*Loade int cdacCount = DebugTraverseLoaderHeapBlocks.Count; delegate* unmanaged debugCallbackPtr = &TraverseLoaderHeapDebugCallback; int hrLocal = _legacyImpl13.TraverseLoaderHeap(loaderHeapAddr, kind, debugCallbackPtr); - Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); - 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"); + 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; From 8c8e7608c0b876d8c2719121e0e5429ade34a3a1 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Fri, 17 Apr 2026 10:44:13 -0700 Subject: [PATCH 20/31] updating datadescriptor types --- src/coreclr/vm/datadescriptor/datadescriptor.inc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 64048c8d345fa2..32ce81251d234e 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -309,13 +309,13 @@ CDAC_TYPE_END(LoaderAllocator) CDAC_TYPE_BEGIN(LoaderHeap) CDAC_TYPE_INDETERMINATE(LoaderHeap) -CDAC_TYPE_FIELD(LoaderHeap, /*pointer*/, FirstBlock, cdac_data::FirstBlock) +CDAC_TYPE_FIELD(LoaderHeap, T_POINTER, FirstBlock, cdac_data::FirstBlock) CDAC_TYPE_END(LoaderHeap) CDAC_TYPE_BEGIN(LoaderHeapBlock) -CDAC_TYPE_FIELD(LoaderHeapBlock, /*pointer*/, Next, offsetof(LoaderHeapBlock, pNext)) -CDAC_TYPE_FIELD(LoaderHeapBlock, /*pointer*/, VirtualAddress, offsetof(LoaderHeapBlock, pVirtualAddress)) -CDAC_TYPE_FIELD(LoaderHeapBlock, /*nuint*/, VirtualSize, offsetof(LoaderHeapBlock, dwVirtualSize)) +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) From dde973668d04908e878a874847fe53001a913693 Mon Sep 17 00:00:00 2001 From: Rachel Date: Sat, 18 Apr 2026 06:59:04 -0700 Subject: [PATCH 21/31] Update docs/design/datacontracts/Loader.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/design/datacontracts/Loader.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index f38b2f400b5754..71fb14e533ed52 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -93,7 +93,6 @@ 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. -// Throws NotImplementedException for unknown kind values. TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap); // Returns the size of the reserved virtual memory region for the given loader heap block TargetNUInt GetLoaderHeapBlockSize(TargetPointer block); From c1181d67d8755f7c79c2f6c325d674238e4d8028 Mon Sep 17 00:00:00 2001 From: Rachel Date: Sat, 18 Apr 2026 06:59:42 -0700 Subject: [PATCH 22/31] Update src/coreclr/inc/loaderheap.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/coreclr/inc/loaderheap.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/inc/loaderheap.h b/src/coreclr/inc/loaderheap.h index 3f04cff5bef438..56b494701d6898 100644 --- a/src/coreclr/inc/loaderheap.h +++ b/src/coreclr/inc/loaderheap.h @@ -17,7 +17,7 @@ #include "utilcode.h" #include "ex.h" #include "executableallocator.h" -#include "../vm/cdacdata.h" +#include "cdacdata.h" //============================================================================== // Interface used to back out loader heap allocations. From eccd8b1bba4d8ffdfa28b67f541dedb9e95e1818 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Apr 2026 13:59:56 +0000 Subject: [PATCH 23/31] Update LoaderHeapTests to use new TypedView/Layout test infrastructure Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/ae0b9e3f-cd45-462f-8325-f4e168b17339 Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- .../managed/cdac/tests/LoaderHeapTests.cs | 143 ++++++------------ .../MockDescriptors/MockDescriptors.Loader.cs | 68 +++++++++ 2 files changed, 116 insertions(+), 95 deletions(-) diff --git a/src/native/managed/cdac/tests/LoaderHeapTests.cs b/src/native/managed/cdac/tests/LoaderHeapTests.cs index 6170894fd16c14..0057fa4350bf58 100644 --- a/src/native/managed/cdac/tests/LoaderHeapTests.cs +++ b/src/native/managed/cdac/tests/LoaderHeapTests.cs @@ -4,67 +4,49 @@ using System; using System.Collections.Generic; using Microsoft.Diagnostics.DataContractReader.Contracts; -using Moq; using Xunit; namespace Microsoft.Diagnostics.DataContractReader.Tests; public class LoaderHeapTests { - private const ulong DefaultAllocationRangeStart = 0x0005_0000; - private const ulong DefaultAllocationRangeEnd = 0x0006_0000; - - private static readonly MockDescriptors.TypeFields LoaderHeapFields = new MockDescriptors.TypeFields() - { - DataType = DataType.LoaderHeap, - Fields = - [ - new(nameof(Data.LoaderHeap.FirstBlock), DataType.pointer), - ] - }; - - private static readonly MockDescriptors.TypeFields LoaderHeapBlockFields = new MockDescriptors.TypeFields() - { - DataType = DataType.LoaderHeapBlock, - Fields = - [ - new(nameof(Data.LoaderHeapBlock.Next), DataType.pointer), - new(nameof(Data.LoaderHeapBlock.VirtualAddress), DataType.pointer), - new(nameof(Data.LoaderHeapBlock.VirtualSize), DataType.nuint), - ] - }; - - private static Dictionary GetTypes(TargetTestHelpers helpers) + 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) { - return MockDescriptors.GetTypesForTypeFields(helpers, [LoaderHeapFields, LoaderHeapBlockFields]); - } + TestPlaceholderTarget.Builder targetBuilder = new(arch); + MockLoaderBuilder loader = new(targetBuilder.MemoryBuilder); - private static Target CreateTarget(MockTarget.Architecture arch, Dictionary types, MockMemorySpace.Builder builder) - { - var target = new TestPlaceholderTarget(arch, builder.GetMemoryContext().ReadFromTarget, types); - target.SetContracts(Mock.Of( - c => c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1))); - return target; + configure(loader); + + TestPlaceholderTarget target = targetBuilder + .AddTypes(CreateContractTypes(loader)) + .AddContract(version: 1) + .Build(); + return target.Contracts.Loader; } [Theory] [ClassData(typeof(MockTarget.StdArch))] public void EmptyLoaderHeap(MockTarget.Architecture arch) { - TargetTestHelpers helpers = new(arch); - MockMemorySpace.Builder builder = new(helpers); - MockMemorySpace.BumpAllocator allocator = builder.CreateAllocator(DefaultAllocationRangeStart, DefaultAllocationRangeEnd); - Dictionary types = GetTypes(helpers); - - Target.TypeInfo heapType = types[DataType.LoaderHeap]; - MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "LoaderHeap"); - // FirstBlock is zero (null) by default - builder.AddHeapFragment(heapFragment); + TargetPointer heapAddr = TargetPointer.Null; - Target target = CreateTarget(arch, types, builder); - ILoader loader = target.Contracts.Loader; + ILoader loader = CreateLoaderContract(arch, loaderBuilder => + { + MockLoaderHeap heap = loaderBuilder.AddLoaderHeap(firstBlockAddress: 0); + heapAddr = new TargetPointer(heap.Address); + }); - TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapFragment.Address); + TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapAddr); Assert.Equal(TargetPointer.Null, firstBlock); } @@ -72,31 +54,19 @@ public void EmptyLoaderHeap(MockTarget.Architecture arch) [ClassData(typeof(MockTarget.StdArch))] public void SingleBlockLoaderHeap(MockTarget.Architecture arch) { - TargetTestHelpers helpers = new(arch); - MockMemorySpace.Builder builder = new(helpers); - MockMemorySpace.BumpAllocator allocator = builder.CreateAllocator(DefaultAllocationRangeStart, DefaultAllocationRangeEnd); - Dictionary types = GetTypes(helpers); - - Target.TypeInfo heapType = types[DataType.LoaderHeap]; - Target.TypeInfo blockType = types[DataType.LoaderHeapBlock]; + TargetPointer heapAddr = TargetPointer.Null; + const ulong virtualAddress = 0x1234_0000UL; + const ulong virtualSize = 0x1000UL; - ulong virtualAddress = 0x1234_0000UL; - ulong virtualSize = 0x1000UL; - MockMemorySpace.HeapFragment blockFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(blockType), "LoaderHeapBlock"); - helpers.WritePointer(blockFragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.Next)].Offset, helpers.PointerSize), TargetPointer.Null.Value); - helpers.WritePointer(blockFragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualAddress)].Offset, helpers.PointerSize), virtualAddress); - helpers.WriteNUInt(blockFragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualSize)].Offset, helpers.PointerSize), new TargetNUInt(virtualSize)); - builder.AddHeapFragment(blockFragment); - - MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "LoaderHeap"); - helpers.WritePointer(heapFragment.Data.AsSpan().Slice(heapType.Fields[nameof(Data.LoaderHeap.FirstBlock)].Offset, helpers.PointerSize), blockFragment.Address); - builder.AddHeapFragment(heapFragment); - - Target target = CreateTarget(arch, types, builder); - ILoader loader = target.Contracts.Loader; + 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(heapFragment.Address); - Assert.Equal((TargetPointer)blockFragment.Address, firstBlock); + TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapAddr); + Assert.NotEqual(TargetPointer.Null, firstBlock); Assert.Equal(virtualAddress, loader.GetLoaderHeapBlockAddress(firstBlock).Value); Assert.Equal(virtualSize, loader.GetLoaderHeapBlockSize(firstBlock).Value); @@ -109,38 +79,21 @@ public void SingleBlockLoaderHeap(MockTarget.Architecture arch) [ClassData(typeof(MockTarget.StdArch))] public void MultipleBlockLoaderHeap(MockTarget.Architecture arch) { - TargetTestHelpers helpers = new(arch); - MockMemorySpace.Builder builder = new(helpers); - MockMemorySpace.BumpAllocator allocator = builder.CreateAllocator(DefaultAllocationRangeStart, DefaultAllocationRangeEnd); - Dictionary types = GetTypes(helpers); - - Target.TypeInfo heapType = types[DataType.LoaderHeap]; - Target.TypeInfo blockType = types[DataType.LoaderHeapBlock]; - + TargetPointer heapAddr = TargetPointer.Null; ulong[] virtualAddresses = [0x1000_0000UL, 0x2000_0000UL]; ulong[] virtualSizes = [0x8000UL, 0x10000UL]; - MockMemorySpace.HeapFragment block2Fragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(blockType), "LoaderHeapBlock2"); - helpers.WritePointer(block2Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.Next)].Offset, helpers.PointerSize), TargetPointer.Null.Value); - helpers.WritePointer(block2Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualAddress)].Offset, helpers.PointerSize), virtualAddresses[1]); - helpers.WriteNUInt(block2Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualSize)].Offset, helpers.PointerSize), new TargetNUInt(virtualSizes[1])); - builder.AddHeapFragment(block2Fragment); - - MockMemorySpace.HeapFragment block1Fragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(blockType), "LoaderHeapBlock1"); - helpers.WritePointer(block1Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.Next)].Offset, helpers.PointerSize), block2Fragment.Address); - helpers.WritePointer(block1Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualAddress)].Offset, helpers.PointerSize), virtualAddresses[0]); - helpers.WriteNUInt(block1Fragment.Data.AsSpan().Slice(blockType.Fields[nameof(Data.LoaderHeapBlock.VirtualSize)].Offset, helpers.PointerSize), new TargetNUInt(virtualSizes[0])); - builder.AddHeapFragment(block1Fragment); - - MockMemorySpace.HeapFragment heapFragment = allocator.Allocate((ulong)helpers.SizeOfTypeInfo(heapType), "LoaderHeap"); - helpers.WritePointer(heapFragment.Data.AsSpan().Slice(heapType.Fields[nameof(Data.LoaderHeap.FirstBlock)].Offset, helpers.PointerSize), block1Fragment.Address); - builder.AddHeapFragment(heapFragment); - - Target target = CreateTarget(arch, types, builder); - ILoader loader = target.Contracts.Loader; + 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(heapFragment.Address); + TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr); while (block != TargetPointer.Null) { blocks.Add((loader.GetLoaderHeapBlockAddress(block).Value, loader.GetLoaderHeapBlockSize(block).Value)); 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( From 52d60d20a696070067503ff4ea850fcd3dc2f654 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Apr 2026 14:19:56 +0000 Subject: [PATCH 24/31] Remove duplicate GetGlobalAllocationContext section from GC.md Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/70602aac-600c-465f-ba10-bd2c1f88c00d Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- docs/design/datacontracts/GC.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index 5924457cc1bca9..50e3a954ba6f4e 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -969,13 +969,3 @@ TargetNUInt IGC.GetHandleExtraInfo(TargetPointer handle) return target.ReadNUInt(extraInfoAddr); } ``` - -GetGlobalAllocationContext -```csharp -void IGC.GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointer allocLimit) -{ - TargetPointer globalAllocContextAddress = target.ReadGlobalPointer("GlobalAllocContext"); - allocPtr = target.ReadPointer(globalAllocContextAddress + /* EEAllocContext::GCAllocationContext offset */ + /* GCAllocContext::Pointer offset */); - allocLimit = target.ReadPointer(globalAllocContextAddress + /* EEAllocContext::GCAllocationContext offset */ + /* GCAllocContext::Limit offset */); -} -``` From e3e63eca840bd6f6013055f56e2f2685886725e7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Apr 2026 14:26:58 +0000 Subject: [PATCH 25/31] Reorder pseudocode blocks in GC.md to match declarations order Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/28ec038b-38bd-497a-b624-274a255794da Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- docs/design/datacontracts/GC.md | 140 ++++++++++++++++---------------- 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index 50e3a954ba6f4e..2e3a7117d644c4 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -404,46 +404,6 @@ IEnumerable IGC.GetGCHeaps() } ``` -GetOomData -```csharp -GCOomData IGC.GetOomData() -{ - string[] gcIdentifiers = GetGCIdentifiers(); - if (!gcType.Contains("workstation")) - throw new InvalidOperationException(); - - TargetPointer oomHistory = target.ReadGlobalPointer("GCHeapOomData"); - return GetGCOomData(oomHistoryData); -} - -GCOomData IGC.GetOomData(TargetPointer heapAddress) -{ - string[] gcIdentifiers = GetGCIdentifiers(); - if (!gcType.Contains("server")) - throw new InvalidOperationException(); - - TargetPointer oomHistory = target.ReadPointer(heapAddress + /* GCHeap::OomData offset */); - return GetGCOomData(oomHistory); -} - -private GCOomData GetGCOomData(TargetPointer oomHistory) -{ - GCOomData data = default; - - data.Reason = target.Read(oomHistory + /* OomHistory::Reason offset */); - data.AllocSize = target.ReadNUInt(oomHistory + /* OomHistory::AllocSize offset */); - data.Reserved = target.ReadPointer(oomHistory + /* OomHistory::Reserved offset */); - data.Allocated = target.ReadPointer(oomHistory + /* OomHistory::Allocated offset */); - data.GcIndex = target.ReadNUInt(oomHistory + /* OomHistory::GcIndex offset */); - data.Fgm = target.Read(oomHistory + /* OomHistory::Fgm offset */); - data.Size = target.ReadNUInt(oomHistory + /* OomHistory::Size offset */); - data.AvailablePagefileMb = target.ReadNUInt(oomHistory + /* OomHistory::AvailablePagefileMb offset */); - data.LohP = target.Read(oomHistory + /* OomHistory::LohP offset */); - - return data; -} -``` - GetHeapData ```csharp GCHeapData IGC.GetHeapData() @@ -629,6 +589,46 @@ private List ReadGCHeapDataArray(TargetPointer arrayStart, uint len } ``` +GetOomData +```csharp +GCOomData IGC.GetOomData() +{ + string[] gcIdentifiers = GetGCIdentifiers(); + if (!gcType.Contains("workstation")) + throw new InvalidOperationException(); + + TargetPointer oomHistory = target.ReadGlobalPointer("GCHeapOomData"); + return GetGCOomData(oomHistoryData); +} + +GCOomData IGC.GetOomData(TargetPointer heapAddress) +{ + string[] gcIdentifiers = GetGCIdentifiers(); + if (!gcType.Contains("server")) + throw new InvalidOperationException(); + + TargetPointer oomHistory = target.ReadPointer(heapAddress + /* GCHeap::OomData offset */); + return GetGCOomData(oomHistory); +} + +private GCOomData GetGCOomData(TargetPointer oomHistory) +{ + GCOomData data = default; + + data.Reason = target.Read(oomHistory + /* OomHistory::Reason offset */); + data.AllocSize = target.ReadNUInt(oomHistory + /* OomHistory::AllocSize offset */); + data.Reserved = target.ReadPointer(oomHistory + /* OomHistory::Reserved offset */); + data.Allocated = target.ReadPointer(oomHistory + /* OomHistory::Allocated offset */); + data.GcIndex = target.ReadNUInt(oomHistory + /* OomHistory::GcIndex offset */); + data.Fgm = target.Read(oomHistory + /* OomHistory::Fgm offset */); + data.Size = target.ReadNUInt(oomHistory + /* OomHistory::Size offset */); + data.AvailablePagefileMb = target.ReadNUInt(oomHistory + /* OomHistory::AvailablePagefileMb offset */); + data.LohP = target.Read(oomHistory + /* OomHistory::LohP offset */); + + return data; +} +``` + GetHandles ```csharp public enum HandleType @@ -777,6 +777,36 @@ private HandleData CreateHandleData(TargetPointer handleAddress, byte uBlock, ui } ``` +GetHandleExtraInfo +```csharp +TargetNUInt IGC.GetHandleExtraInfo(TargetPointer handle) +{ + // Handle table segments are aligned to their size ("HandleSegmentSize"). + // The segment base is found by masking the handle address. + // User data blocks are stored in TableSegment.RgUserData, indexed by block number. + // The block and intra-block index are computed from the handle's position within the segment. + + uint segmentSize = target.ReadGlobal("HandleSegmentSize"); + TargetPointer segment = handle & ~(ulong)(segmentSize - 1); + + uint headerSize = /* TableSegment::RgValue offset */; + uint handlesPerBlock = target.ReadGlobal("HandlesPerBlock"); + + uint handleIndex = (uint)((handle - segment - headerSize) / (uint)target.PointerSize); + uint block = handleIndex / handlesPerBlock; + uint intraBlockIndex = handleIndex % handlesPerBlock; + + byte userDataBlockIndex = target.Read(segment + /* TableSegment::RgUserData offset */ + block); + if (userDataBlockIndex == target.ReadGlobal("BlockInvalid")) + return new TargetNUInt(0); + + uint offset = userDataBlockIndex * handlesPerBlock + intraBlockIndex; + TargetPointer extraInfoAddr = segment + headerSize + offset * (uint)target.PointerSize; + + return target.ReadNUInt(extraInfoAddr); +} +``` + GetGlobalAllocationContext ```csharp void IGC.GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointer allocLimit) @@ -939,33 +969,3 @@ void AddSegmentList(TargetPointer start, FreeRegionKind kind, List("HandleSegmentSize"); - TargetPointer segment = handle & ~(ulong)(segmentSize - 1); - - uint headerSize = /* TableSegment::RgValue offset */; - uint handlesPerBlock = target.ReadGlobal("HandlesPerBlock"); - - uint handleIndex = (uint)((handle - segment - headerSize) / (uint)target.PointerSize); - uint block = handleIndex / handlesPerBlock; - uint intraBlockIndex = handleIndex % handlesPerBlock; - - byte userDataBlockIndex = target.Read(segment + /* TableSegment::RgUserData offset */ + block); - if (userDataBlockIndex == target.ReadGlobal("BlockInvalid")) - return new TargetNUInt(0); - - uint offset = userDataBlockIndex * handlesPerBlock + intraBlockIndex; - TargetPointer extraInfoAddr = segment + headerSize + offset * (uint)target.PointerSize; - - return target.ReadNUInt(extraInfoAddr); -} -``` From 8195d2bda9bee907e31e671b9c05f867b9fc3130 Mon Sep 17 00:00:00 2001 From: Rachel Date: Sat, 18 Apr 2026 16:26:17 -0700 Subject: [PATCH 26/31] Update GC.md --- docs/design/datacontracts/GC.md | 313 +++++--------------------------- 1 file changed, 42 insertions(+), 271 deletions(-) diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index 2e3a7117d644c4..b8d59f80475369 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 server variant. - // The workstation variant implicitly operates on the global heap. + // The following APIs have both a workstation and serer variant. + // The workstation variant implitly operates on the global heap. // The server variants allow passing in a heap pointer. // Gets data about a GC heap @@ -114,37 +114,8 @@ public readonly struct GCOomData HandleType[] GetSupportedHandleTypes(); // Converts integer types into HandleType enum HandleType[] GetHandleTypes(uint[] types); - // Gets the extra info (user data) associated with a dependent handle - TargetNUInt GetHandleExtraInfo(TargetPointer handle); // Gets the global allocation context pointer and limit void GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointer allocLimit); - - // Gets handle table memory regions (segments) - IReadOnlyList GetHandleTableMemoryRegions(); - // Gets GC bookkeeping memory regions (card table info linked list) - IReadOnlyList GetGCBookkeepingMemoryRegions(); - // Gets GC free regions (free region lists and freeable segments) - IReadOnlyList GetGCFreeRegions(); -``` - -```csharp -public enum FreeRegionKind -{ - FreeUnknownRegion = 0, - FreeGlobalHugeRegion = 1, - FreeGlobalRegion = 2, - FreeRegion = 3, - FreeSohSegment = 4, - FreeUohSegment = 5, -} - -public readonly struct GCMemoryRegionData -{ - public TargetPointer Start { get; init; } - public ulong Size { get; init; } - public ulong ExtraData { get; init; } - public int Heap { get; init; } -} ``` ## Version 1 @@ -204,13 +175,6 @@ Data descriptors used: | `TableSegment` | RgAllocation | GC | Circular block-list links per block | | `TableSegment` | RgValue | GC | Start of handle value storage | | `TableSegment` | RgUserData | GC | Auxiliary per-block metadata (e.g. secondary handle blocks) | -| `CardTableInfo` | Recount | GC | Reference count for the card table | -| `CardTableInfo` | Size | GC | Total size of the bookkeeping allocation | -| `CardTableInfo` | NextCardTable | GC | Pointer to the next card table in the linked list | -| `RegionFreeList` | HeadFreeRegion | GC | Head of the free region segment list | -| `GCHeap` | FreeableSohSegment | GC | Head of the freeable SOH segment linked list (server builds, background GC) | -| `GCHeap` | FreeableUohSegment | GC | Head of the freeable UOH segment linked list (server builds, background GC) | -| `GCHeap` | FreeRegions | GC | Start of the per-heap free region list array (server builds, region GC) | | `GCAllocContext` | AllocBytes | VM | Number of bytes allocated on SOH by this context | | `GCAllocContext` | AllocBytesLoh | VM | Number of bytes allocated not on SOH by this context | | `EEAllocContext` | GCAllocationContext | VM | The `GCAllocContext` struct within an `EEAllocContext` | @@ -258,7 +222,6 @@ Global variables used: | `HandleMaxInternalTypes` | uint | GC | Number of handle types (length of `TableSegment.RgTail`) | | `HandlesPerBlock` | uint | GC | Number of handles in each handle block | | `BlockInvalid` | byte | GC | Sentinel value indicating an invalid handle block index | -| `HandleSegmentSize` | uint | GC | Size of a handle table segment | | `DebugDestroyedHandleValue` | TargetPointer | GC | Sentinel handle value used for destroyed handles | | `FeatureCOMInterop` | byte | VM | Non-zero when COM interop support is enabled | | `FeatureComWrappers` | byte | VM | Non-zero when `ComWrappers` support is enabled | @@ -266,15 +229,6 @@ Global variables used: | `FeatureJavaMarshal` | byte | VM | Non-zero when Java marshal support is enabled | | `GlobalAllocContext` | TargetPointer | VM | Pointer to the global `EEAllocContext` | | `TotalCpuCount` | uint | GC | Number of available processors | -| `HandleSegmentSize` | uint | GC | Size of each handle table segment allocation | -| `CardTableInfoSize` | uint | GC | Size of the `dac_card_table_info` structure | -| `CountFreeRegionKinds` | uint | GC | Number of free region kinds (basic, large, huge) | -| `GlobalFreeHugeRegions` | TargetPointer | GC | Pointer to the global free huge region list | -| `GlobalRegionsToDecommit` | TargetPointer | GC | Pointer to the global regions-to-decommit array | -| `BookkeepingStart` | TargetPointer | GC | Pointer to the bookkeeping start address | -| `GCHeapFreeableSohSegment` | TargetPointer | GC | Pointer to the freeable SOH segment head (workstation builds) | -| `GCHeapFreeableUohSegment` | TargetPointer | GC | Pointer to the freeable UOH segment head (workstation builds) | -| `GCHeapFreeRegions` | TargetPointer | GC | Pointer to the free regions array (workstation builds) | Contracts used: | Contract Name | @@ -404,6 +358,46 @@ IEnumerable IGC.GetGCHeaps() } ``` +GetOomData +```csharp +GCOomData IGC.GetOomData() +{ + string[] gcIdentifiers = GetGCIdentifiers(); + if (!gcType.Contains("workstation")) + throw new InvalidOperationException(); + + TargetPointer oomHistory = target.ReadGlobalPointer("GCHeapOomData"); + return GetGCOomData(oomHistoryData); +} + +GCOomData IGC.GetOomData(TargetPointer heapAddress) +{ + string[] gcIdentifiers = GetGCIdentifiers(); + if (!gcType.Contains("server")) + throw new InvalidOperationException(); + + TargetPointer oomHistory = target.ReadPointer(heapAddress + /* GCHeap::OomData offset */); + return GetGCOomData(oomHistory); +} + +private GCOomData GetGCOomData(TargetPointer oomHistory) +{ + GCOomData data = default; + + data.Reason = target.Read(oomHistory + /* OomHistory::Reason offset */); + data.AllocSize = target.ReadNUInt(oomHistory + /* OomHistory::AllocSize offset */); + data.Reserved = target.ReadPointer(oomHistory + /* OomHistory::Reserved offset */); + data.Allocated = target.ReadPointer(oomHistory + /* OomHistory::Allocated offset */); + data.GcIndex = target.ReadNUInt(oomHistory + /* OomHistory::GcIndex offset */); + data.Fgm = target.Read(oomHistory + /* OomHistory::Fgm offset */); + data.Size = target.ReadNUInt(oomHistory + /* OomHistory::Size offset */); + data.AvailablePagefileMb = target.ReadNUInt(oomHistory + /* OomHistory::AvailablePagefileMb offset */); + data.LohP = target.Read(oomHistory + /* OomHistory::LohP offset */); + + return data; +} +``` + GetHeapData ```csharp GCHeapData IGC.GetHeapData() @@ -589,46 +583,6 @@ private List ReadGCHeapDataArray(TargetPointer arrayStart, uint len } ``` -GetOomData -```csharp -GCOomData IGC.GetOomData() -{ - string[] gcIdentifiers = GetGCIdentifiers(); - if (!gcType.Contains("workstation")) - throw new InvalidOperationException(); - - TargetPointer oomHistory = target.ReadGlobalPointer("GCHeapOomData"); - return GetGCOomData(oomHistoryData); -} - -GCOomData IGC.GetOomData(TargetPointer heapAddress) -{ - string[] gcIdentifiers = GetGCIdentifiers(); - if (!gcType.Contains("server")) - throw new InvalidOperationException(); - - TargetPointer oomHistory = target.ReadPointer(heapAddress + /* GCHeap::OomData offset */); - return GetGCOomData(oomHistory); -} - -private GCOomData GetGCOomData(TargetPointer oomHistory) -{ - GCOomData data = default; - - data.Reason = target.Read(oomHistory + /* OomHistory::Reason offset */); - data.AllocSize = target.ReadNUInt(oomHistory + /* OomHistory::AllocSize offset */); - data.Reserved = target.ReadPointer(oomHistory + /* OomHistory::Reserved offset */); - data.Allocated = target.ReadPointer(oomHistory + /* OomHistory::Allocated offset */); - data.GcIndex = target.ReadNUInt(oomHistory + /* OomHistory::GcIndex offset */); - data.Fgm = target.Read(oomHistory + /* OomHistory::Fgm offset */); - data.Size = target.ReadNUInt(oomHistory + /* OomHistory::Size offset */); - data.AvailablePagefileMb = target.ReadNUInt(oomHistory + /* OomHistory::AvailablePagefileMb offset */); - data.LohP = target.Read(oomHistory + /* OomHistory::LohP offset */); - - return data; -} -``` - GetHandles ```csharp public enum HandleType @@ -777,36 +731,6 @@ private HandleData CreateHandleData(TargetPointer handleAddress, byte uBlock, ui } ``` -GetHandleExtraInfo -```csharp -TargetNUInt IGC.GetHandleExtraInfo(TargetPointer handle) -{ - // Handle table segments are aligned to their size ("HandleSegmentSize"). - // The segment base is found by masking the handle address. - // User data blocks are stored in TableSegment.RgUserData, indexed by block number. - // The block and intra-block index are computed from the handle's position within the segment. - - uint segmentSize = target.ReadGlobal("HandleSegmentSize"); - TargetPointer segment = handle & ~(ulong)(segmentSize - 1); - - uint headerSize = /* TableSegment::RgValue offset */; - uint handlesPerBlock = target.ReadGlobal("HandlesPerBlock"); - - uint handleIndex = (uint)((handle - segment - headerSize) / (uint)target.PointerSize); - uint block = handleIndex / handlesPerBlock; - uint intraBlockIndex = handleIndex % handlesPerBlock; - - byte userDataBlockIndex = target.Read(segment + /* TableSegment::RgUserData offset */ + block); - if (userDataBlockIndex == target.ReadGlobal("BlockInvalid")) - return new TargetNUInt(0); - - uint offset = userDataBlockIndex * handlesPerBlock + intraBlockIndex; - TargetPointer extraInfoAddr = segment + headerSize + offset * (uint)target.PointerSize; - - return target.ReadNUInt(extraInfoAddr); -} -``` - GetGlobalAllocationContext ```csharp void IGC.GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointer allocLimit) @@ -816,156 +740,3 @@ void IGC.GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointe allocLimit = target.ReadPointer(globalAllocContextAddress + /* EEAllocContext::GCAllocationContext offset */ + /* GCAllocContext::Limit offset */); } ``` - -GetHandleTableMemoryRegions -```csharp -IReadOnlyList IGC.GetHandleTableMemoryRegions() -{ - List regions = new(); - uint handleSegmentSize = /* global value "HandleSegmentSize" */; - uint tableCount = isServerGC - ? /* global value "TotalCpuCount" */ - : 1; - - // Safety caps matching native DAC - const int MaxHandleTableRegions = 8192; - const int MaxBookkeepingRegions = 32; - const int MaxSegmentListIterations = 2048; - - int maxRegions = MaxHandleTableRegions; - TargetPointer handleTableMap = target.ReadGlobalPointer("HandleTableMap"); - while (handleTableMap != TargetPointer.Null && maxRegions >= 0) - { - TargetPointer bucketsPtr = target.ReadPointer(handleTableMap + /* HandleTableMap::BucketsPtr offset */); - foreach (/* read global variable "InitialHandleTableArraySize" bucketPtrs starting at bucketsPtr */) - { - if (bucketPtr == TargetPointer.Null) continue; - TargetPointer table = target.ReadPointer(bucketPtr + /* HandleTableBucket::Table offset */); - for (uint j = 0; j < tableCount; j++) - { - TargetPointer htPtr = target.ReadPointer(table + j * target.PointerSize); - if (htPtr == TargetPointer.Null) continue; - TargetPointer segList = target.ReadPointer(htPtr + /* HandleTable::SegmentList offset */); - if (segList == TargetPointer.Null) continue; - TargetPointer seg = segList; - TargetPointer first = seg; - do - { - regions.Add(new GCMemoryRegionData { Start = seg, Size = handleSegmentSize, Heap = (int)j }); - seg = target.ReadPointer(seg + /* TableSegment::NextSegment offset */); - } while (seg != TargetPointer.Null && seg != first); - } - } - handleTableMap = target.ReadPointer(handleTableMap + /* HandleTableMap::Next offset */); - maxRegions--; - } - return regions; -} -``` - -GetGCBookkeepingMemoryRegions -```csharp -IReadOnlyList IGC.GetGCBookkeepingMemoryRegions() -{ - List regions = new(); - TargetPointer bkGlobal = target.ReadGlobalPointer("BookkeepingStart"); - if (bkGlobal == TargetPointer.Null) throw E_FAIL; - TargetPointer bookkeepingStart = target.ReadPointer(bkGlobal); - if (bookkeepingStart == TargetPointer.Null) throw E_FAIL; - - uint cardTableInfoSize = /* global value "CardTableInfoSize" */; - uint recount = target.ReadNUInt(bookkeepingStart + /* CardTableInfo::Recount offset */); - ulong size = target.ReadNUInt(bookkeepingStart + /* CardTableInfo::Size offset */); - if (recount != 0 && size != 0) - regions.Add(new GCMemoryRegionData { Start = bookkeepingStart, Size = size }); - - TargetPointer next = target.ReadPointer(bookkeepingStart + /* CardTableInfo::NextCardTable offset */); - TargetPointer firstNext = next; - int maxRegions = MaxBookkeepingRegions; - // Compare next > cardTableInfoSize to guard against underflow when subtracting - // cardTableInfoSize. Matches native DAC: `while (next > card_table_info_size)`. - while (next != TargetPointer.Null && next > cardTableInfoSize && maxRegions > 0) - { - TargetPointer ctAddr = next - cardTableInfoSize; - recount = target.ReadNUInt(ctAddr + /* CardTableInfo::Recount offset */); - size = target.ReadNUInt(ctAddr + /* CardTableInfo::Size offset */); - if (recount != 0 && size != 0) - regions.Add(new GCMemoryRegionData { Start = ctAddr, Size = size }); - next = target.ReadPointer(ctAddr + /* CardTableInfo::NextCardTable offset */); - if (next == firstNext) break; - maxRegions--; - } - return regions; -} -``` - -GetGCFreeRegions -```csharp -IReadOnlyList IGC.GetGCFreeRegions() -{ - List regions = new(); - uint countFreeRegionKinds = min(/* global value "CountFreeRegionKinds" */, 16); - uint regionFreeListSize = /* size of RegionFreeList data descriptor */; - - // Global free huge regions - if (target.TryReadGlobalPointer("GlobalFreeHugeRegions", out TargetPointer? globalHuge)) - AddFreeList(globalHuge, FreeGlobalHugeRegion, regions); - - // Global regions to decommit - if (target.TryReadGlobalPointer("GlobalRegionsToDecommit", out TargetPointer? globalDecommit)) - for (int i = 0; i < countFreeRegionKinds; i++) - AddFreeList(globalDecommit + i * regionFreeListSize, FreeGlobalRegion, regions); - - if (isServerGC) - { - // For each server heap: enumerate per-heap free regions + freeable segments - for each heap in server heaps: - TargetPointer freeRegionsBase = heapAddress + /* GCHeap::FreeRegions offset */; - if (freeRegionsBase != TargetPointer.Null) - for (int j = 0; j < countFreeRegionKinds; j++) - AddFreeList(freeRegionsBase + j * regionFreeListSize, FreeRegion, regions, heapIndex); - TargetPointer sohSeg = target.ReadPointer(heapAddress + /* GCHeap::FreeableSohSegment offset */); - AddSegmentList(sohSeg, FreeSohSegment, regions, heapIndex); - TargetPointer uohSeg = target.ReadPointer(heapAddress + /* GCHeap::FreeableUohSegment offset */); - AddSegmentList(uohSeg, FreeUohSegment, regions, heapIndex); - } - else - { - // Workstation: use globals for free regions and freeable segments - if (target.TryReadGlobalPointer("GCHeapFreeRegions", out TargetPointer? freeRegions)) - for (int i = 0; i < countFreeRegionKinds; i++) - AddFreeList(freeRegions + i * regionFreeListSize, FreeRegion, regions); - if (target.TryReadGlobalPointer("GCHeapFreeableSohSegment", out TargetPointer? soh)) - AddSegmentList(target.ReadPointer(soh), FreeSohSegment, regions); - if (target.TryReadGlobalPointer("GCHeapFreeableUohSegment", out TargetPointer? uoh)) - AddSegmentList(target.ReadPointer(uoh), FreeUohSegment, regions); - } - return regions; -} - -void AddFreeList(TargetPointer freeListAddr, FreeRegionKind kind, List regions, int heap = 0) -{ - TargetPointer headFreeRegion = target.ReadPointer(freeListAddr + /* RegionFreeList::HeadFreeRegion offset */); - if (headFreeRegion != TargetPointer.Null) - AddSegmentList(headFreeRegion, kind, regions, heap); -} - -void AddSegmentList(TargetPointer start, FreeRegionKind kind, List regions, int heap = 0) -{ - int iterationMax = MaxSegmentListIterations; - TargetPointer curr = start; - while (curr != TargetPointer.Null) - { - TargetPointer mem = target.ReadPointer(curr + /* HeapSegment::Mem offset */); - if (mem != TargetPointer.Null) - { - TargetPointer committed = target.ReadPointer(curr + /* HeapSegment::Committed offset */); - ulong size = (mem < committed) ? committed - mem : 0; - regions.Add(new GCMemoryRegionData { Start = mem, Size = size, ExtraData = kind, Heap = heap }); - } - curr = target.ReadPointer(curr + /* HeapSegment::Next offset */); - if (curr == start) break; - if (iterationMax-- <= 0) break; - } -} -``` From e75df125467f0447f2fc8940a60baa7e9ce09990 Mon Sep 17 00:00:00 2001 From: Rachel Date: Sat, 18 Apr 2026 16:27:30 -0700 Subject: [PATCH 27/31] Update GC.md --- docs/design/datacontracts/GC.md | 243 +++++++++++++++++++++++++++++++- 1 file changed, 241 insertions(+), 2 deletions(-) diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index b8d59f80475369..5924457cc1bca9 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 @@ -114,8 +114,37 @@ public readonly struct GCOomData HandleType[] GetSupportedHandleTypes(); // Converts integer types into HandleType enum HandleType[] GetHandleTypes(uint[] types); + // Gets the extra info (user data) associated with a dependent handle + TargetNUInt GetHandleExtraInfo(TargetPointer handle); // Gets the global allocation context pointer and limit void GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointer allocLimit); + + // Gets handle table memory regions (segments) + IReadOnlyList GetHandleTableMemoryRegions(); + // Gets GC bookkeeping memory regions (card table info linked list) + IReadOnlyList GetGCBookkeepingMemoryRegions(); + // Gets GC free regions (free region lists and freeable segments) + IReadOnlyList GetGCFreeRegions(); +``` + +```csharp +public enum FreeRegionKind +{ + FreeUnknownRegion = 0, + FreeGlobalHugeRegion = 1, + FreeGlobalRegion = 2, + FreeRegion = 3, + FreeSohSegment = 4, + FreeUohSegment = 5, +} + +public readonly struct GCMemoryRegionData +{ + public TargetPointer Start { get; init; } + public ulong Size { get; init; } + public ulong ExtraData { get; init; } + public int Heap { get; init; } +} ``` ## Version 1 @@ -175,6 +204,13 @@ Data descriptors used: | `TableSegment` | RgAllocation | GC | Circular block-list links per block | | `TableSegment` | RgValue | GC | Start of handle value storage | | `TableSegment` | RgUserData | GC | Auxiliary per-block metadata (e.g. secondary handle blocks) | +| `CardTableInfo` | Recount | GC | Reference count for the card table | +| `CardTableInfo` | Size | GC | Total size of the bookkeeping allocation | +| `CardTableInfo` | NextCardTable | GC | Pointer to the next card table in the linked list | +| `RegionFreeList` | HeadFreeRegion | GC | Head of the free region segment list | +| `GCHeap` | FreeableSohSegment | GC | Head of the freeable SOH segment linked list (server builds, background GC) | +| `GCHeap` | FreeableUohSegment | GC | Head of the freeable UOH segment linked list (server builds, background GC) | +| `GCHeap` | FreeRegions | GC | Start of the per-heap free region list array (server builds, region GC) | | `GCAllocContext` | AllocBytes | VM | Number of bytes allocated on SOH by this context | | `GCAllocContext` | AllocBytesLoh | VM | Number of bytes allocated not on SOH by this context | | `EEAllocContext` | GCAllocationContext | VM | The `GCAllocContext` struct within an `EEAllocContext` | @@ -222,6 +258,7 @@ Global variables used: | `HandleMaxInternalTypes` | uint | GC | Number of handle types (length of `TableSegment.RgTail`) | | `HandlesPerBlock` | uint | GC | Number of handles in each handle block | | `BlockInvalid` | byte | GC | Sentinel value indicating an invalid handle block index | +| `HandleSegmentSize` | uint | GC | Size of a handle table segment | | `DebugDestroyedHandleValue` | TargetPointer | GC | Sentinel handle value used for destroyed handles | | `FeatureCOMInterop` | byte | VM | Non-zero when COM interop support is enabled | | `FeatureComWrappers` | byte | VM | Non-zero when `ComWrappers` support is enabled | @@ -229,6 +266,15 @@ Global variables used: | `FeatureJavaMarshal` | byte | VM | Non-zero when Java marshal support is enabled | | `GlobalAllocContext` | TargetPointer | VM | Pointer to the global `EEAllocContext` | | `TotalCpuCount` | uint | GC | Number of available processors | +| `HandleSegmentSize` | uint | GC | Size of each handle table segment allocation | +| `CardTableInfoSize` | uint | GC | Size of the `dac_card_table_info` structure | +| `CountFreeRegionKinds` | uint | GC | Number of free region kinds (basic, large, huge) | +| `GlobalFreeHugeRegions` | TargetPointer | GC | Pointer to the global free huge region list | +| `GlobalRegionsToDecommit` | TargetPointer | GC | Pointer to the global regions-to-decommit array | +| `BookkeepingStart` | TargetPointer | GC | Pointer to the bookkeeping start address | +| `GCHeapFreeableSohSegment` | TargetPointer | GC | Pointer to the freeable SOH segment head (workstation builds) | +| `GCHeapFreeableUohSegment` | TargetPointer | GC | Pointer to the freeable UOH segment head (workstation builds) | +| `GCHeapFreeRegions` | TargetPointer | GC | Pointer to the free regions array (workstation builds) | Contracts used: | Contract Name | @@ -740,3 +786,196 @@ void IGC.GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointe allocLimit = target.ReadPointer(globalAllocContextAddress + /* EEAllocContext::GCAllocationContext offset */ + /* GCAllocContext::Limit offset */); } ``` + +GetHandleTableMemoryRegions +```csharp +IReadOnlyList IGC.GetHandleTableMemoryRegions() +{ + List regions = new(); + uint handleSegmentSize = /* global value "HandleSegmentSize" */; + uint tableCount = isServerGC + ? /* global value "TotalCpuCount" */ + : 1; + + // Safety caps matching native DAC + const int MaxHandleTableRegions = 8192; + const int MaxBookkeepingRegions = 32; + const int MaxSegmentListIterations = 2048; + + int maxRegions = MaxHandleTableRegions; + TargetPointer handleTableMap = target.ReadGlobalPointer("HandleTableMap"); + while (handleTableMap != TargetPointer.Null && maxRegions >= 0) + { + TargetPointer bucketsPtr = target.ReadPointer(handleTableMap + /* HandleTableMap::BucketsPtr offset */); + foreach (/* read global variable "InitialHandleTableArraySize" bucketPtrs starting at bucketsPtr */) + { + if (bucketPtr == TargetPointer.Null) continue; + TargetPointer table = target.ReadPointer(bucketPtr + /* HandleTableBucket::Table offset */); + for (uint j = 0; j < tableCount; j++) + { + TargetPointer htPtr = target.ReadPointer(table + j * target.PointerSize); + if (htPtr == TargetPointer.Null) continue; + TargetPointer segList = target.ReadPointer(htPtr + /* HandleTable::SegmentList offset */); + if (segList == TargetPointer.Null) continue; + TargetPointer seg = segList; + TargetPointer first = seg; + do + { + regions.Add(new GCMemoryRegionData { Start = seg, Size = handleSegmentSize, Heap = (int)j }); + seg = target.ReadPointer(seg + /* TableSegment::NextSegment offset */); + } while (seg != TargetPointer.Null && seg != first); + } + } + handleTableMap = target.ReadPointer(handleTableMap + /* HandleTableMap::Next offset */); + maxRegions--; + } + return regions; +} +``` + +GetGCBookkeepingMemoryRegions +```csharp +IReadOnlyList IGC.GetGCBookkeepingMemoryRegions() +{ + List regions = new(); + TargetPointer bkGlobal = target.ReadGlobalPointer("BookkeepingStart"); + if (bkGlobal == TargetPointer.Null) throw E_FAIL; + TargetPointer bookkeepingStart = target.ReadPointer(bkGlobal); + if (bookkeepingStart == TargetPointer.Null) throw E_FAIL; + + uint cardTableInfoSize = /* global value "CardTableInfoSize" */; + uint recount = target.ReadNUInt(bookkeepingStart + /* CardTableInfo::Recount offset */); + ulong size = target.ReadNUInt(bookkeepingStart + /* CardTableInfo::Size offset */); + if (recount != 0 && size != 0) + regions.Add(new GCMemoryRegionData { Start = bookkeepingStart, Size = size }); + + TargetPointer next = target.ReadPointer(bookkeepingStart + /* CardTableInfo::NextCardTable offset */); + TargetPointer firstNext = next; + int maxRegions = MaxBookkeepingRegions; + // Compare next > cardTableInfoSize to guard against underflow when subtracting + // cardTableInfoSize. Matches native DAC: `while (next > card_table_info_size)`. + while (next != TargetPointer.Null && next > cardTableInfoSize && maxRegions > 0) + { + TargetPointer ctAddr = next - cardTableInfoSize; + recount = target.ReadNUInt(ctAddr + /* CardTableInfo::Recount offset */); + size = target.ReadNUInt(ctAddr + /* CardTableInfo::Size offset */); + if (recount != 0 && size != 0) + regions.Add(new GCMemoryRegionData { Start = ctAddr, Size = size }); + next = target.ReadPointer(ctAddr + /* CardTableInfo::NextCardTable offset */); + if (next == firstNext) break; + maxRegions--; + } + return regions; +} +``` + +GetGCFreeRegions +```csharp +IReadOnlyList IGC.GetGCFreeRegions() +{ + List regions = new(); + uint countFreeRegionKinds = min(/* global value "CountFreeRegionKinds" */, 16); + uint regionFreeListSize = /* size of RegionFreeList data descriptor */; + + // Global free huge regions + if (target.TryReadGlobalPointer("GlobalFreeHugeRegions", out TargetPointer? globalHuge)) + AddFreeList(globalHuge, FreeGlobalHugeRegion, regions); + + // Global regions to decommit + if (target.TryReadGlobalPointer("GlobalRegionsToDecommit", out TargetPointer? globalDecommit)) + for (int i = 0; i < countFreeRegionKinds; i++) + AddFreeList(globalDecommit + i * regionFreeListSize, FreeGlobalRegion, regions); + + if (isServerGC) + { + // For each server heap: enumerate per-heap free regions + freeable segments + for each heap in server heaps: + TargetPointer freeRegionsBase = heapAddress + /* GCHeap::FreeRegions offset */; + if (freeRegionsBase != TargetPointer.Null) + for (int j = 0; j < countFreeRegionKinds; j++) + AddFreeList(freeRegionsBase + j * regionFreeListSize, FreeRegion, regions, heapIndex); + TargetPointer sohSeg = target.ReadPointer(heapAddress + /* GCHeap::FreeableSohSegment offset */); + AddSegmentList(sohSeg, FreeSohSegment, regions, heapIndex); + TargetPointer uohSeg = target.ReadPointer(heapAddress + /* GCHeap::FreeableUohSegment offset */); + AddSegmentList(uohSeg, FreeUohSegment, regions, heapIndex); + } + else + { + // Workstation: use globals for free regions and freeable segments + if (target.TryReadGlobalPointer("GCHeapFreeRegions", out TargetPointer? freeRegions)) + for (int i = 0; i < countFreeRegionKinds; i++) + AddFreeList(freeRegions + i * regionFreeListSize, FreeRegion, regions); + if (target.TryReadGlobalPointer("GCHeapFreeableSohSegment", out TargetPointer? soh)) + AddSegmentList(target.ReadPointer(soh), FreeSohSegment, regions); + if (target.TryReadGlobalPointer("GCHeapFreeableUohSegment", out TargetPointer? uoh)) + AddSegmentList(target.ReadPointer(uoh), FreeUohSegment, regions); + } + return regions; +} + +void AddFreeList(TargetPointer freeListAddr, FreeRegionKind kind, List regions, int heap = 0) +{ + TargetPointer headFreeRegion = target.ReadPointer(freeListAddr + /* RegionFreeList::HeadFreeRegion offset */); + if (headFreeRegion != TargetPointer.Null) + AddSegmentList(headFreeRegion, kind, regions, heap); +} + +void AddSegmentList(TargetPointer start, FreeRegionKind kind, List regions, int heap = 0) +{ + int iterationMax = MaxSegmentListIterations; + TargetPointer curr = start; + while (curr != TargetPointer.Null) + { + TargetPointer mem = target.ReadPointer(curr + /* HeapSegment::Mem offset */); + if (mem != TargetPointer.Null) + { + TargetPointer committed = target.ReadPointer(curr + /* HeapSegment::Committed offset */); + ulong size = (mem < committed) ? committed - mem : 0; + regions.Add(new GCMemoryRegionData { Start = mem, Size = size, ExtraData = kind, Heap = heap }); + } + curr = target.ReadPointer(curr + /* HeapSegment::Next offset */); + if (curr == start) break; + if (iterationMax-- <= 0) break; + } +} +``` + +GetHandleExtraInfo +```csharp +TargetNUInt IGC.GetHandleExtraInfo(TargetPointer handle) +{ + // Handle table segments are aligned to their size ("HandleSegmentSize"). + // The segment base is found by masking the handle address. + // User data blocks are stored in TableSegment.RgUserData, indexed by block number. + // The block and intra-block index are computed from the handle's position within the segment. + + uint segmentSize = target.ReadGlobal("HandleSegmentSize"); + TargetPointer segment = handle & ~(ulong)(segmentSize - 1); + + uint headerSize = /* TableSegment::RgValue offset */; + uint handlesPerBlock = target.ReadGlobal("HandlesPerBlock"); + + uint handleIndex = (uint)((handle - segment - headerSize) / (uint)target.PointerSize); + uint block = handleIndex / handlesPerBlock; + uint intraBlockIndex = handleIndex % handlesPerBlock; + + byte userDataBlockIndex = target.Read(segment + /* TableSegment::RgUserData offset */ + block); + if (userDataBlockIndex == target.ReadGlobal("BlockInvalid")) + return new TargetNUInt(0); + + uint offset = userDataBlockIndex * handlesPerBlock + intraBlockIndex; + TargetPointer extraInfoAddr = segment + headerSize + offset * (uint)target.PointerSize; + + return target.ReadNUInt(extraInfoAddr); +} +``` + +GetGlobalAllocationContext +```csharp +void IGC.GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointer allocLimit) +{ + TargetPointer globalAllocContextAddress = target.ReadGlobalPointer("GlobalAllocContext"); + allocPtr = target.ReadPointer(globalAllocContextAddress + /* EEAllocContext::GCAllocationContext offset */ + /* GCAllocContext::Pointer offset */); + allocLimit = target.ReadPointer(globalAllocContextAddress + /* EEAllocContext::GCAllocationContext offset */ + /* GCAllocContext::Limit offset */); +} +``` From 3a81183be23f20b4015028793fc6a01e5f4b00fe Mon Sep 17 00:00:00 2001 From: Rachel Date: Sat, 18 Apr 2026 16:28:22 -0700 Subject: [PATCH 28/31] Update GC.md --- docs/design/datacontracts/GC.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index 5924457cc1bca9..50e3a954ba6f4e 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -969,13 +969,3 @@ TargetNUInt IGC.GetHandleExtraInfo(TargetPointer handle) return target.ReadNUInt(extraInfoAddr); } ``` - -GetGlobalAllocationContext -```csharp -void IGC.GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointer allocLimit) -{ - TargetPointer globalAllocContextAddress = target.ReadGlobalPointer("GlobalAllocContext"); - allocPtr = target.ReadPointer(globalAllocContextAddress + /* EEAllocContext::GCAllocationContext offset */ + /* GCAllocContext::Pointer offset */); - allocLimit = target.ReadPointer(globalAllocContextAddress + /* EEAllocContext::GCAllocationContext offset */ + /* GCAllocContext::Limit offset */); -} -``` From 56eee51652f1f50486136ba7eed3689464a639cf Mon Sep 17 00:00:00 2001 From: Rachel Jarvi Date: Mon, 20 Apr 2026 11:23:57 -0700 Subject: [PATCH 29/31] Update Loader.md to remove Webcil section details Removed details about Webcil section headers from the documentation. --- docs/design/datacontracts/Loader.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 71fb14e533ed52..c2e5a4d83b2c6b 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -213,11 +213,6 @@ enum ClrModifiableAssemblies : uint | `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 | -| `WebcilHeader` | `CoffSections` | Number of COFF sections in the Webcil image | -| `WebcilSectionHeader` | `VirtualSize` | Virtual size of the section | -| `WebcilSectionHeader` | `VirtualAddress` | RVA of the section | -| `WebcilSectionHeader` | `SizeOfRawData` | Size of the section's raw data | -| `WebcilSectionHeader` | `PointerToRawData` | File offset to the section's raw data | | `EEConfig` | `ModifiableAssemblies` | Controls Edit and Continue support (ClrModifiableAssemblies enum) | From ecbab65b34b3ce822e36bde2843ee1f19aafb79f Mon Sep 17 00:00:00 2001 From: rcj1 Date: Mon, 20 Apr 2026 13:24:34 -0700 Subject: [PATCH 30/31] code review feedback --- docs/design/datacontracts/Loader.md | 36 +++++----- .../vm/datadescriptor/datadescriptor.inc | 1 + .../Contracts/ILoader.cs | 13 ++-- .../Contracts/Loader_1.cs | 21 ++---- .../SOSDacImpl.cs | 66 ++++--------------- .../managed/cdac/tests/LoaderHeapTests.cs | 14 ++-- 6 files changed, 55 insertions(+), 96 deletions(-) diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index c2e5a4d83b2c6b..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 @@ -94,12 +101,8 @@ 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 size of the reserved virtual memory region for the given loader heap block -TargetNUInt GetLoaderHeapBlockSize(TargetPointer block); -// Returns the start address of the reserved virtual memory for the given loader heap block -TargetPointer GetLoaderHeapBlockAddress(TargetPointer block); -// Returns the next block in the loader heap linked list, or TargetPointer.Null if there are no more blocks -TargetPointer GetNextLoaderHeapBlock(TargetPointer block); +// 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); @@ -960,7 +963,7 @@ class InstMethodHashTable } ``` -#### GetFirstLoaderHeapBlock, GetLoaderHeapBlockAddress, GetLoaderHeapBlockSize, GetNextLoaderHeapBlock +#### GetFirstLoaderHeapBlock, GetLoaderHeapBlockData ```csharp TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap) @@ -968,18 +971,13 @@ TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap) return target.ReadPointer(loaderHeap + /* LoaderHeap::FirstBlock offset */); } -TargetPointer ILoader.GetLoaderHeapBlockAddress(TargetPointer block) -{ - return target.ReadPointer(block + /* LoaderHeapBlock::VirtualAddress offset */); -} - -TargetNUInt ILoader.GetLoaderHeapBlockSize(TargetPointer block) +LoaderHeapBlockData ILoader.GetLoaderHeapBlockData(TargetPointer block) { - return target.ReadNUInt(block + /* LoaderHeapBlock::VirtualSize offset */); -} - -TargetPointer ILoader.GetNextLoaderHeapBlock(TargetPointer block) -{ - return target.ReadPointer(block + /* LoaderHeapBlock::Next offset */); + 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/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 0fe18c29e6405d..b81afab6d88ead 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -319,6 +319,7 @@ CDAC_TYPE_FIELD(LoaderHeapBlock, T_POINTER, Next, offsetof(LoaderHeapBlock, pNex 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 e89672a901ba16..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); @@ -119,10 +126,8 @@ public interface ILoader : IContract // 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(); - TargetNUInt GetLoaderHeapBlockSize(TargetPointer block) => throw new NotImplementedException(); - TargetPointer GetLoaderHeapBlockAddress(TargetPointer block) => throw new NotImplementedException(); - // Returns the next block in the loader heap linked list, or TargetPointer.Null if there are no more blocks - TargetPointer GetNextLoaderHeapBlock(TargetPointer block) => 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.Contracts/Contracts/Loader_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs index 5c6a7aba7e8624..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 @@ -672,22 +672,15 @@ TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap) return _target.ProcessedData.GetOrAdd(loaderHeap).FirstBlock; } - TargetNUInt ILoader.GetLoaderHeapBlockSize(TargetPointer block) + LoaderHeapBlockData ILoader.GetLoaderHeapBlockData(TargetPointer block) { Data.LoaderHeapBlock blockData = _target.ProcessedData.GetOrAdd(block); - return blockData.VirtualSize; - } - - TargetPointer ILoader.GetLoaderHeapBlockAddress(TargetPointer block) - { - Data.LoaderHeapBlock blockData = _target.ProcessedData.GetOrAdd(block); - return blockData.VirtualAddress; - } - - TargetPointer ILoader.GetNextLoaderHeapBlock(TargetPointer block) - { - Data.LoaderHeapBlock blockData = _target.ProcessedData.GetOrAdd(block); - return blockData.Next; + return new LoaderHeapBlockData + { + Address = blockData.VirtualAddress, + Size = blockData.VirtualSize, + NextBlock = blockData.Next, + }; } IReadOnlyDictionary ILoader.GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer) 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 43f7ee8eabd217..d67adff3ab3489 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -4667,7 +4667,8 @@ private static void TraverseLoaderHeapDebugCallback(ulong virtualAddress, nuint Debug.Assert(found, $"Unexpected loader heap block: address={virtualAddress:x}, size={virtualSize:x}"); } #endif - int ISOSDacInterface.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, delegate* unmanaged pCallback) + + private int TraverseLoaderHeapCore(ClrDataAddress loaderHeapAddr, delegate* unmanaged pCallback) { int hr = HResults.S_OK; #if DEBUG @@ -4687,22 +4688,20 @@ int ISOSDacInterface.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, delegate* int i = 0; while (block != TargetPointer.Null && i++ < iterationMax) { - TargetPointer blockAddress; - TargetNUInt blockSize; + Contracts.LoaderHeapBlockData blockData; try { - blockAddress = loader.GetLoaderHeapBlockAddress(block); - blockSize = loader.GetLoaderHeapBlockSize(block); + blockData = loader.GetLoaderHeapBlockData(block); } catch (VirtualReadException) { throw new NullReferenceException(); } - pCallback(blockAddress.Value, (nuint)blockSize.Value, block == firstBlock ? Interop.BOOL.TRUE : Interop.BOOL.FALSE); + pCallback(blockData.Address.Value, (nuint)blockData.Size.Value, block == firstBlock ? Interop.BOOL.TRUE : Interop.BOOL.FALSE); #if DEBUG - DebugTraverseLoaderHeapBlocks.Add((blockAddress.Value, (nuint)blockSize.Value)); + DebugTraverseLoaderHeapBlocks.Add((blockData.Address.Value, (nuint)blockData.Size.Value)); #endif - block = loader.GetNextLoaderHeapBlock(block); + block = blockData.NextBlock; if (block == firstBlock) throw new NullReferenceException(); } @@ -4713,6 +4712,12 @@ int ISOSDacInterface.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, delegate* { 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) { @@ -6202,50 +6207,7 @@ int ISOSDacInterface12.GetGlobalAllocationContext(ClrDataAddress* allocPtr, ClrD int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*LoaderHeapKind*/ int kind, /*VISITHEAP*/ 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) - { - TargetPointer blockAddress; - TargetNUInt blockSize; - try - { - blockAddress = loader.GetLoaderHeapBlockAddress(block); - blockSize = loader.GetLoaderHeapBlockSize(block); - } - catch (VirtualReadException) - { - throw new NullReferenceException(); - } - pCallback(blockAddress.Value, (nuint)blockSize.Value, block == firstBlock ? Interop.BOOL.TRUE : Interop.BOOL.FALSE); -#if DEBUG - DebugTraverseLoaderHeapBlocks.Add((blockAddress.Value, (nuint)blockSize.Value)); -#endif - block = loader.GetNextLoaderHeapBlock(block); - if (block == firstBlock) - throw new NullReferenceException(); - } - if (i >= iterationMax) - hr = HResults.S_FALSE; - } - catch (System.Exception ex) - { - hr = ex.HResult; - } + int hr = TraverseLoaderHeapCore(loaderHeapAddr, pCallback); #if DEBUG if (_legacyImpl13 is not null) { diff --git a/src/native/managed/cdac/tests/LoaderHeapTests.cs b/src/native/managed/cdac/tests/LoaderHeapTests.cs index 0057fa4350bf58..a3979b1326c89c 100644 --- a/src/native/managed/cdac/tests/LoaderHeapTests.cs +++ b/src/native/managed/cdac/tests/LoaderHeapTests.cs @@ -68,11 +68,10 @@ public void SingleBlockLoaderHeap(MockTarget.Architecture arch) TargetPointer firstBlock = loader.GetFirstLoaderHeapBlock(heapAddr); Assert.NotEqual(TargetPointer.Null, firstBlock); - Assert.Equal(virtualAddress, loader.GetLoaderHeapBlockAddress(firstBlock).Value); - Assert.Equal(virtualSize, loader.GetLoaderHeapBlockSize(firstBlock).Value); - - TargetPointer nextBlock = loader.GetNextLoaderHeapBlock(firstBlock); - Assert.Equal(TargetPointer.Null, nextBlock); + LoaderHeapBlockData blockData = loader.GetLoaderHeapBlockData(firstBlock); + Assert.Equal(virtualAddress, blockData.Address.Value); + Assert.Equal(virtualSize, blockData.Size.Value); + Assert.Equal(TargetPointer.Null, blockData.NextBlock); } [Theory] @@ -96,8 +95,9 @@ public void MultipleBlockLoaderHeap(MockTarget.Architecture arch) TargetPointer block = loader.GetFirstLoaderHeapBlock(heapAddr); while (block != TargetPointer.Null) { - blocks.Add((loader.GetLoaderHeapBlockAddress(block).Value, loader.GetLoaderHeapBlockSize(block).Value)); - block = loader.GetNextLoaderHeapBlock(block); + LoaderHeapBlockData blockData = loader.GetLoaderHeapBlockData(block); + blocks.Add((blockData.Address.Value, blockData.Size.Value)); + block = blockData.NextBlock; } Assert.Equal(2, blocks.Count); From 2486f0dde09bdd0862b9b85a30f027d4370ed3f8 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 21 Apr 2026 09:41:56 -0700 Subject: [PATCH 31/31] fix test --- src/native/managed/cdac/tests/LoaderHeapTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/native/managed/cdac/tests/LoaderHeapTests.cs b/src/native/managed/cdac/tests/LoaderHeapTests.cs index a3979b1326c89c..4db75ffd95bce3 100644 --- a/src/native/managed/cdac/tests/LoaderHeapTests.cs +++ b/src/native/managed/cdac/tests/LoaderHeapTests.cs @@ -22,14 +22,14 @@ public class LoaderHeapTests private static ILoader CreateLoaderContract(MockTarget.Architecture arch, Action configure) { - TestPlaceholderTarget.Builder targetBuilder = new(arch); + var targetBuilder = new TestPlaceholderTarget.Builder(arch); MockLoaderBuilder loader = new(targetBuilder.MemoryBuilder); configure(loader); - TestPlaceholderTarget target = targetBuilder + var target = targetBuilder .AddTypes(CreateContractTypes(loader)) - .AddContract(version: 1) + .AddContract(version: "c1") .Build(); return target.Contracts.Loader; }