Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
04535d2
Initial plan
Copilot Mar 3, 2026
f5cfd10
Implement cDAC TraverseLoaderHeap API using GC contract
Copilot Mar 3, 2026
ae89c94
Address code review: add kind validation, debug validation block, and…
Copilot Mar 3, 2026
80a6655
Move LoaderHeap APIs from GC contract to Loader contract
Copilot Mar 3, 2026
6c76e30
Use offsetof() directly in datadescriptor.inc instead of cdac_data st…
Copilot Mar 3, 2026
c6ebd5a
Revert stray template<typename T> cdac_data forward declaration from …
Copilot Mar 3, 2026
2073ae1
Add LoaderHeapKind enum and separate ExplicitControlLoaderHeap cDAC t…
Copilot Mar 3, 2026
a04f5c8
Fix build break: add cdac_data specialization and friend declaration …
Copilot Mar 3, 2026
668fc1c
fix
rcj1 Mar 6, 2026
0b34106
simplify loader heap
rcj1 Mar 6, 2026
54a628c
Update docs/design/datacontracts/GC.md
rcj1 Mar 6, 2026
b15b9ce
moving debug helpers
rcj1 Mar 6, 2026
221ef85
comments
rcj1 Mar 6, 2026
f21396f
Revert "simplify loader heap"
rcj1 Mar 7, 2026
60e0be1
code review
rcj1 Mar 7, 2026
d8c4a3f
fix callconv
rcj1 Mar 9, 2026
805c47f
Merge branch 'main' into copilot/implement-cdac-api-traverse-loader-heap
rcj1 Mar 17, 2026
36f229e
incorporating refactor
rcj1 Mar 24, 2026
4f54031
nits
rcj1 Mar 24, 2026
5c557d9
validation
rcj1 Mar 24, 2026
dbaf075
Merge branch 'main' into copilot/implement-cdac-api-traverse-loader-heap
rcj1 Mar 24, 2026
3889a45
Merge branch 'main' into copilot/implement-cdac-api-traverse-loader-heap
rcj1 Apr 17, 2026
8c8e760
updating datadescriptor types
rcj1 Apr 17, 2026
5b0d234
Merge branch 'main' into copilot/implement-cdac-api-traverse-loader-heap
rcj1 Apr 18, 2026
dde9736
Update docs/design/datacontracts/Loader.md
rcj1 Apr 18, 2026
c1181d6
Update src/coreclr/inc/loaderheap.h
rcj1 Apr 18, 2026
eccd8b1
Update LoaderHeapTests to use new TypedView/Layout test infrastructure
Copilot Apr 18, 2026
52d60d2
Remove duplicate GetGlobalAllocationContext section from GC.md
Copilot Apr 18, 2026
e3e63ec
Reorder pseudocode blocks in GC.md to match declarations order
Copilot Apr 18, 2026
8195d2b
Update GC.md
rcj1 Apr 18, 2026
e75df12
Update GC.md
rcj1 Apr 18, 2026
3a81183
Update GC.md
rcj1 Apr 18, 2026
281aad6
Merge branch 'main' into copilot/implement-cdac-api-traverse-loader-heap
rcj1 Apr 20, 2026
56eee51
Update Loader.md to remove Webcil section details
rcj1 Apr 20, 2026
ecbab65
code review feedback
rcj1 Apr 20, 2026
2486f0d
fix test
rcj1 Apr 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/design/datacontracts/GC.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ public readonly struct GCOomData
// Returns pointers to all GC heaps
IEnumerable<TargetPointer> 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
Expand Down
34 changes: 34 additions & 0 deletions docs/design/datacontracts/Loader.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -92,6 +99,10 @@ TargetPointer GetStubHeap(TargetPointer loaderAllocatorPointer);
TargetPointer GetObjectHandle(TargetPointer loaderAllocatorPointer);
TargetPointer GetILHeader(ModuleHandle handle, uint token);
TargetPointer GetDynamicIL(ModuleHandle handle, uint token);
// Returns the first block of the loader heap linked list, or TargetPointer.Null if the heap has no blocks.
TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap);
// Returns the data for the given loader heap block (address, size, and next block pointer).
LoaderHeapBlockData GetLoaderHeapBlockData(TargetPointer block);
IReadOnlyDictionary<string, TargetPointer> GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer);

DebuggerAssemblyControlFlags GetDebuggerInfoBits(ModuleHandle handle);
Expand Down Expand Up @@ -201,6 +212,10 @@ enum ClrModifiableAssemblies : uint
| `DynamicILBlobTable` | `EntrySize` | Size of each table entry |
| `DynamicILBlobTable` | `EntryMethodToken` | Offset of each entry method token from entry address |
| `DynamicILBlobTable` | `EntryIL` | Offset of each entry IL from entry address |
| `LoaderHeap` | `FirstBlock` | Pointer to the first `LoaderHeapBlock` in the linked list |
| `LoaderHeapBlock` | `Next` | Pointer to the next `LoaderHeapBlock` in the linked list |
| `LoaderHeapBlock` | `VirtualAddress` | Pointer to the start of the reserved virtual memory |
| `LoaderHeapBlock` | `VirtualSize` | Size in bytes of the reserved virtual memory region |
| `EEConfig` | `ModifiableAssemblies` | Controls Edit and Continue support (ClrModifiableAssemblies enum) |


Expand Down Expand Up @@ -947,3 +962,22 @@ class InstMethodHashTable
}
}
```

#### GetFirstLoaderHeapBlock, GetLoaderHeapBlockData

```csharp
TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap)
{
return target.ReadPointer(loaderHeap + /* LoaderHeap::FirstBlock offset */);
}

LoaderHeapBlockData ILoader.GetLoaderHeapBlockData(TargetPointer block)
{
return new LoaderHeapBlockData
{
Address = target.ReadPointer(block + /* LoaderHeapBlock::VirtualAddress offset */),
Size = target.ReadNUInt(block + /* LoaderHeapBlock::VirtualSize offset */),
NextBlock = target.ReadPointer(block + /* LoaderHeapBlock::Next offset */),
};
}
```
17 changes: 2 additions & 15 deletions src/coreclr/debug/daccess/request.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3575,7 +3575,7 @@ ClrDataAccess::TraverseLoaderHeap(CLRDATA_ADDRESS loaderHeapAddr, VISITHEAP pFun

SOSDacEnter();

hr = TraverseLoaderHeapBlock(PTR_UnlockedLoaderHeapBase(TO_TADDR(loaderHeapAddr))->m_pFirstBlock, pFunc);
hr = TraverseLoaderHeapBlock(PTR_UnlockedLoaderHeapBaseTraversable(TO_TADDR(loaderHeapAddr))->m_pFirstBlock, pFunc);

SOSDacLeave();
return hr;
Expand All @@ -3591,20 +3591,7 @@ ClrDataAccess::TraverseLoaderHeap(CLRDATA_ADDRESS loaderHeapAddr, LoaderHeapKind

SOSDacEnter();

switch (kind)
{
case LoaderHeapKindNormal:
hr = TraverseLoaderHeapBlock(PTR_UnlockedLoaderHeapBase(TO_TADDR(loaderHeapAddr))->m_pFirstBlock, pCallback);
break;

case LoaderHeapKindExplicitControl:
hr = TraverseLoaderHeapBlock(PTR_ExplicitControlLoaderHeap(TO_TADDR(loaderHeapAddr))->m_pFirstBlock, pCallback);
break;

default:
hr = E_NOTIMPL;
break;
}
hr = TraverseLoaderHeapBlock(PTR_UnlockedLoaderHeapBaseTraversable(TO_TADDR(loaderHeapAddr))->m_pFirstBlock, pCallback);

Comment thread
rcj1 marked this conversation as resolved.
SOSDacLeave();
return hr;
Expand Down
18 changes: 14 additions & 4 deletions src/coreclr/inc/loaderheap.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "utilcode.h"
#include "ex.h"
#include "executableallocator.h"
#include "cdacdata.h"

//==============================================================================
// Interface used to back out loader heap allocations.
Expand Down Expand Up @@ -174,8 +175,13 @@ enum class LoaderHeapImplementationKind
Interleaved
};

typedef DPTR(class UnlockedLoaderHeapBaseTraversable) PTR_UnlockedLoaderHeapBaseTraversable;
class UnlockedLoaderHeapBaseTraversable
{
friend struct cdac_data<UnlockedLoaderHeapBaseTraversable>;
#ifdef DACCESS_COMPILE
friend class ClrDataAccess;
#endif
protected:
#ifdef DACCESS_COMPILE
UnlockedLoaderHeapBaseTraversable() {}
Expand All @@ -188,6 +194,8 @@ class UnlockedLoaderHeapBaseTraversable
#endif

public:
// DO NOT REMOVE : This is needed for layout stability.
virtual ~UnlockedLoaderHeapBaseTraversable() {}
#ifdef DACCESS_COMPILE
public:
void EnumMemoryRegions(enum CLRDataEnumMemoryFlags flags);
Expand All @@ -201,12 +209,17 @@ typedef bool EnumPageRegionsCallback (PTR_VOID pvArgs, PTR_VOID pvAllocationBase
PTR_LoaderHeapBlock m_pFirstBlock;
};

template<>
struct cdac_data<UnlockedLoaderHeapBaseTraversable>
{
static constexpr size_t FirstBlock = offsetof(UnlockedLoaderHeapBaseTraversable, m_pFirstBlock);
};

//===============================================================================
// This is the base class for LoaderHeap and InterleavedLoaderHeap. It holds the
// common handling for LoaderHeap events, and the data structures used for bump
// pointer allocation (although not the actual allocation routines).
//===============================================================================
typedef DPTR(class UnlockedLoaderHeapBase) PTR_UnlockedLoaderHeapBase;
class UnlockedLoaderHeapBase : public UnlockedLoaderHeapBaseTraversable, public ILoaderHeapBackout
{
#ifdef _DEBUG
Expand Down Expand Up @@ -599,9 +612,6 @@ class UnlockedInterleavedLoaderHeap : public UnlockedLoaderHeapBase
typedef DPTR(class ExplicitControlLoaderHeap) PTR_ExplicitControlLoaderHeap;
class ExplicitControlLoaderHeap : public UnlockedLoaderHeapBaseTraversable
{
#ifdef DACCESS_COMPILE
friend class ClrDataAccess;
#endif

private:
// Allocation pointer in current block
Expand Down
11 changes: 11 additions & 0 deletions src/coreclr/vm/datadescriptor/datadescriptor.inc
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,17 @@ CDAC_TYPE_FIELD(LoaderAllocator, T_POINTER, VirtualCallStubManager, cdac_data<Lo
CDAC_TYPE_FIELD(LoaderAllocator, T_POINTER, ObjectHandle, cdac_data<LoaderAllocator>::ObjectHandle)
CDAC_TYPE_END(LoaderAllocator)

CDAC_TYPE_BEGIN(LoaderHeap)
CDAC_TYPE_INDETERMINATE(LoaderHeap)
CDAC_TYPE_FIELD(LoaderHeap, T_POINTER, FirstBlock, cdac_data<UnlockedLoaderHeapBaseTraversable>::FirstBlock)
CDAC_TYPE_END(LoaderHeap)

CDAC_TYPE_BEGIN(LoaderHeapBlock)
CDAC_TYPE_FIELD(LoaderHeapBlock, T_POINTER, Next, offsetof(LoaderHeapBlock, pNext))
CDAC_TYPE_FIELD(LoaderHeapBlock, T_POINTER, VirtualAddress, offsetof(LoaderHeapBlock, pVirtualAddress))
CDAC_TYPE_FIELD(LoaderHeapBlock, T_NUINT, VirtualSize, offsetof(LoaderHeapBlock, dwVirtualSize))
CDAC_TYPE_END(LoaderHeapBlock)

CDAC_TYPE_BEGIN(VirtualCallStubManager)
Comment thread
rcj1 marked this conversation as resolved.
CDAC_TYPE_INDETERMINATE(VirtualCallStubManager)
CDAC_TYPE_FIELD(VirtualCallStubManager, T_POINTER, IndcellHeap, cdac_data<VirtualCallStubManager>::IndcellHeap)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -116,6 +123,11 @@ public interface ILoader : IContract
TargetPointer GetILHeader(ModuleHandle handle, uint token) => throw new NotImplementedException();
TargetPointer GetObjectHandle(TargetPointer loaderAllocatorPointer) => throw new NotImplementedException();
TargetPointer GetDynamicIL(ModuleHandle handle, uint token) => throw new NotImplementedException();

// Returns the first block of the loader heap linked list, or TargetPointer.Null if the heap has no blocks.
TargetPointer GetFirstLoaderHeapBlock(TargetPointer loaderHeap) => throw new NotImplementedException();
// Returns the data for the given loader heap block (address, size, and next block pointer).
LoaderHeapBlockData GetLoaderHeapBlockData(TargetPointer block) => throw new NotImplementedException();
IReadOnlyDictionary<string, TargetPointer> GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer) => throw new NotImplementedException();

DebuggerAssemblyControlFlags GetDebuggerInfoBits(ModuleHandle handle) => throw new NotImplementedException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ public enum DataType
SystemDomain,
Assembly,
LoaderAllocator,
LoaderHeap,
LoaderHeapBlock,
PEAssembly,
AssemblyBinder,
PEImage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,22 @@ TargetPointer ILoader.GetDynamicIL(ModuleHandle handle, uint token)
return shashContract.LookupSHash(dynamicILBlobTable.HashTable, token).EntryIL;
}

TargetPointer ILoader.GetFirstLoaderHeapBlock(TargetPointer loaderHeap)
{
return _target.ProcessedData.GetOrAdd<Data.LoaderHeap>(loaderHeap).FirstBlock;
}

LoaderHeapBlockData ILoader.GetLoaderHeapBlockData(TargetPointer block)
{
Data.LoaderHeapBlock blockData = _target.ProcessedData.GetOrAdd<Data.LoaderHeapBlock>(block);
return new LoaderHeapBlockData
{
Address = blockData.VirtualAddress,
Size = blockData.VirtualSize,
NextBlock = blockData.Next,
};
}

IReadOnlyDictionary<string, TargetPointer> ILoader.GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer)
{
Data.LoaderAllocator loaderAllocator = _target.ProcessedData.GetOrAdd<Data.LoaderAllocator>(loaderAllocatorPointer);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<LoaderHeap>
{
static LoaderHeap IData<LoaderHeap>.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; }
}
Original file line number Diff line number Diff line change
@@ -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<LoaderHeapBlock>
{
static LoaderHeapBlock IData<LoaderHeapBlock>.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; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,7 @@ public unsafe partial interface ISOSDacInterface
[PreserveSig]
int GetModuleData(ClrDataAddress moduleAddr, DacpModuleData* data);
[PreserveSig]
int TraverseModuleMap(ModuleMapType mmt, ClrDataAddress moduleAddr, delegate* unmanaged[Stdcall]<uint, /*ClrDataAddress*/ ulong, void*, void> pCallback, void* token);
int TraverseModuleMap(ModuleMapType mmt, ClrDataAddress moduleAddr, delegate* unmanaged<uint, /*ClrDataAddress*/ ulong, void*, void> pCallback, void* token);
[PreserveSig]
int GetAssemblyModuleList(ClrDataAddress assembly, uint count, [In, Out, MarshalUsing(CountElementName = nameof(count))] ClrDataAddress[] modules, uint* pNeeded);
[PreserveSig]
Expand Down Expand Up @@ -814,7 +814,7 @@ public unsafe partial interface ISOSDacInterface

// Heaps
[PreserveSig]
int TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*VISITHEAP*/ void* pCallback);
int TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*VISITHEAP*/ delegate* unmanaged</*ClrDataAddress*/ ulong, nuint, Interop.BOOL, void> pCallback);
[PreserveSig]
int GetCodeHeapList(ClrDataAddress jitManager, uint count, [In, MarshalUsing(CountElementName = nameof(count)), Out] DacpJitCodeHeapInfo[]? codeHeaps, uint* pNeeded);
[PreserveSig]
Expand All @@ -840,7 +840,7 @@ public unsafe partial interface ISOSDacInterface
[PreserveSig]
int GetCCWInterfaces(ClrDataAddress ccw, uint count, [In, MarshalUsing(CountElementName = nameof(count)), Out] DacpCOMInterfacePointerData[]? interfaces, uint* pNeeded);
[PreserveSig]
int TraverseRCWCleanupList(ClrDataAddress cleanupListPtr, /*VISITRCWFORCLEANUP*/ delegate* unmanaged[Stdcall]</*ClrDataAddress*/ ulong, /*ClrDataAddress*/ ulong, /*ClrDataAddress*/ ulong, Interop.BOOL, void*, Interop.BOOL> pCallback, void* token);
int TraverseRCWCleanupList(ClrDataAddress cleanupListPtr, /*VISITRCWFORCLEANUP*/ delegate* unmanaged</*ClrDataAddress*/ ulong, /*ClrDataAddress*/ ulong, /*ClrDataAddress*/ ulong, Interop.BOOL, void*, Interop.BOOL> pCallback, void* token);

// GC Reference Functions

Expand Down Expand Up @@ -1119,7 +1119,7 @@ public unsafe partial interface ISOSDacInterface12
public unsafe partial interface ISOSDacInterface13
{
[PreserveSig]
int TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*LoaderHeapKind*/ int kind, /*VISITHEAP*/ delegate* unmanaged<ulong, nuint, Interop.BOOL> 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]
Expand Down
Loading
Loading