Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 27 additions & 13 deletions docs/design/datacontracts/Loader.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@ readonly struct LoaderHeapBlockData
TargetNUInt Size { get; init; }
TargetPointer NextBlock { get; init; }
}

enum LoaderAllocatorHeapType : uint
{
LowFrequency = 0,
HighFrequency = 1,
Statics = 2,
Stub = 3,
Executable = 4,
FixupPrecode = 5,
NewStubPrecode = 6,
DynamicHelpers = 7,
Indcell = 8,
CacheEntry = 9,
}
```

``` csharp
Expand Down Expand Up @@ -105,7 +119,7 @@ TargetPointer GetDynamicIL(ModuleHandle handle, uint token);
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);
IReadOnlyDictionary<LoaderAllocatorHeapType, TargetPointer> GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer);

DebuggerAssemblyControlFlags GetDebuggerInfoBits(ModuleHandle handle);
void SetDebuggerInfoBits(ModuleHandle handle, DebuggerAssemblyControlFlags newBits);
Expand Down Expand Up @@ -787,39 +801,39 @@ TargetPointer GetObjectHandle(TargetPointer loaderAllocatorPointer)
return target.ReadPointer(loaderAllocatorPointer + /* LoaderAllocator::ObjectHandle offset */);
}

IReadOnlyDictionary<string, TargetPointer> GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer)
IReadOnlyDictionary<LoaderAllocatorHeapType, TargetPointer> GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer)
{
// Read LoaderAllocator data
LoaderAllocator la = // read LoaderAllocator object at loaderAllocatorPointer

// Always-present heaps
Dictionary<string, TargetPointer> heaps = {
["LowFrequencyHeap"] = la.LowFrequencyHeap,
["HighFrequencyHeap"] = la.HighFrequencyHeap,
["StaticsHeap"] = la.StaticsHeap,
["StubHeap"] = la.StubHeap,
["ExecutableHeap"] = la.ExecutableHeap,
Dictionary<LoaderAllocatorHeapType, TargetPointer> heaps = {
[LoaderAllocatorHeapType.LowFrequency] = la.LowFrequencyHeap,
[LoaderAllocatorHeapType.HighFrequency] = la.HighFrequencyHeap,
[LoaderAllocatorHeapType.Statics] = la.StaticsHeap,
[LoaderAllocatorHeapType.Stub] = la.StubHeap,
[LoaderAllocatorHeapType.Executable] = la.ExecutableHeap,
};

// Feature-conditional heaps: only included when the data descriptor field exists
if (LoaderAllocator type has "FixupPrecodeHeap" field)
heaps["FixupPrecodeHeap"] = la.FixupPrecodeHeap;
heaps[LoaderAllocatorHeapType.FixupPrecode] = la.FixupPrecodeHeap;

if (LoaderAllocator type has "NewStubPrecodeHeap" field)
heaps["NewStubPrecodeHeap"] = la.NewStubPrecodeHeap;
heaps[LoaderAllocatorHeapType.NewStubPrecode] = la.NewStubPrecodeHeap;

if (LoaderAllocator type has "DynamicHelpersStubHeap" field)
heaps["DynamicHelpersStubHeap"] = la.DynamicHelpersStubHeap;
heaps[LoaderAllocatorHeapType.DynamicHelpers] = la.DynamicHelpersStubHeap;

// VirtualCallStubManager heaps: only included when VirtualCallStubManager is non-null
if (la.VirtualCallStubManager != null)
{
VirtualCallStubManager vcsMgr = // read VirtualCallStubManager object at la.VirtualCallStubManager

heaps["IndcellHeap"] = vcsMgr.IndcellHeap;
heaps[LoaderAllocatorHeapType.Indcell] = vcsMgr.IndcellHeap;

if (VirtualCallStubManager type has "CacheEntryHeap" field)
heaps["CacheEntryHeap"] = vcsMgr.CacheEntryHeap;
heaps[LoaderAllocatorHeapType.CacheEntry] = vcsMgr.CacheEntryHeap;
}

return heaps;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@ public enum ClrModifiableAssemblies : uint
Debug = 2,
}

public enum LoaderAllocatorHeapType : uint
{
LowFrequency = 0,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe reserve 0 for an Unknown type in case there is something added in the future?

HighFrequency = 1,
Statics = 2,
Stub = 3,
Executable = 4,
FixupPrecode = 5,
NewStubPrecode = 6,
DynamicHelpers = 7,
Indcell = 8,
CacheEntry = 9,
}
Comment on lines +47 to +59
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to explicitly define the size of this enum and values? The flags above do, but given this is a true managed enum, I don't think we need to.


[Flags]
public enum AssemblyIterationFlags
{
Expand Down Expand Up @@ -130,7 +144,7 @@ public interface ILoader : IContract
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();
IReadOnlyDictionary<LoaderAllocatorHeapType, TargetPointer> GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer) => throw new NotImplementedException();

DebuggerAssemblyControlFlags GetDebuggerInfoBits(ModuleHandle handle) => throw new NotImplementedException();
void SetDebuggerInfoBits(ModuleHandle handle, DebuggerAssemblyControlFlags newBits) => throw new NotImplementedException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -704,38 +704,38 @@ LoaderHeapBlockData ILoader.GetLoaderHeapBlockData(TargetPointer block)
};
}

IReadOnlyDictionary<string, TargetPointer> ILoader.GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer)
IReadOnlyDictionary<LoaderAllocatorHeapType, TargetPointer> ILoader.GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer)
{
Data.LoaderAllocator loaderAllocator = _target.ProcessedData.GetOrAdd<Data.LoaderAllocator>(loaderAllocatorPointer);
Target.TypeInfo laType = _target.GetTypeInfo(DataType.LoaderAllocator);

Dictionary<string, TargetPointer> heaps = new()
Dictionary<LoaderAllocatorHeapType, TargetPointer> heaps = new()
{
[nameof(Data.LoaderAllocator.LowFrequencyHeap)] = loaderAllocator.LowFrequencyHeap,
[nameof(Data.LoaderAllocator.HighFrequencyHeap)] = loaderAllocator.HighFrequencyHeap,
[nameof(Data.LoaderAllocator.StaticsHeap)] = loaderAllocator.StaticsHeap,
[nameof(Data.LoaderAllocator.StubHeap)] = loaderAllocator.StubHeap,
[nameof(Data.LoaderAllocator.ExecutableHeap)] = loaderAllocator.ExecutableHeap,
[LoaderAllocatorHeapType.LowFrequency] = loaderAllocator.LowFrequencyHeap,
[LoaderAllocatorHeapType.HighFrequency] = loaderAllocator.HighFrequencyHeap,
[LoaderAllocatorHeapType.Statics] = loaderAllocator.StaticsHeap,
[LoaderAllocatorHeapType.Stub] = loaderAllocator.StubHeap,
[LoaderAllocatorHeapType.Executable] = loaderAllocator.ExecutableHeap,
};

if (laType.Fields.ContainsKey(nameof(Data.LoaderAllocator.FixupPrecodeHeap)))
heaps[nameof(Data.LoaderAllocator.FixupPrecodeHeap)] = loaderAllocator.FixupPrecodeHeap!.Value;
heaps[LoaderAllocatorHeapType.FixupPrecode] = loaderAllocator.FixupPrecodeHeap!.Value;

if (laType.Fields.ContainsKey(nameof(Data.LoaderAllocator.NewStubPrecodeHeap)))
heaps[nameof(Data.LoaderAllocator.NewStubPrecodeHeap)] = loaderAllocator.NewStubPrecodeHeap!.Value;
heaps[LoaderAllocatorHeapType.NewStubPrecode] = loaderAllocator.NewStubPrecodeHeap!.Value;

if (laType.Fields.ContainsKey(nameof(Data.LoaderAllocator.DynamicHelpersStubHeap)))
heaps[nameof(Data.LoaderAllocator.DynamicHelpersStubHeap)] = loaderAllocator.DynamicHelpersStubHeap!.Value;
heaps[LoaderAllocatorHeapType.DynamicHelpers] = loaderAllocator.DynamicHelpersStubHeap!.Value;

if (loaderAllocator.VirtualCallStubManager != TargetPointer.Null)
{
Data.VirtualCallStubManager vcsMgr = _target.ProcessedData.GetOrAdd<Data.VirtualCallStubManager>(loaderAllocator.VirtualCallStubManager);
Target.TypeInfo vcsType = _target.GetTypeInfo(DataType.VirtualCallStubManager);

heaps[nameof(Data.VirtualCallStubManager.IndcellHeap)] = vcsMgr.IndcellHeap;
heaps[LoaderAllocatorHeapType.Indcell] = vcsMgr.IndcellHeap;

if (vcsType.Fields.ContainsKey(nameof(Data.VirtualCallStubManager.CacheEntryHeap)))
heaps[nameof(Data.VirtualCallStubManager.CacheEntryHeap)] = vcsMgr.CacheEntryHeap!.Value;
heaps[LoaderAllocatorHeapType.CacheEntry] = vcsMgr.CacheEntryHeap!.Value;
}

return heaps;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6378,10 +6378,8 @@ int ISOSDacInterface13.GetLoaderAllocatorHeaps(ClrDataAddress loaderAllocator, i
try
{
Contracts.ILoader contract = _target.Contracts.Loader;
IReadOnlyDictionary<string, TargetPointer> heaps = contract.GetLoaderAllocatorHeaps(loaderAllocator.ToTargetPointer(_target));

var filteredEntries = GetFilteredHeapNameEntries();
int loaderHeapCount = filteredEntries.Length;
IReadOnlyDictionary<LoaderAllocatorHeapType, TargetPointer> heaps = contract.GetLoaderAllocatorHeaps(loaderAllocator.ToTargetPointer(_target));
int loaderHeapCount = heaps.Count;

if (pNeeded != null)
*pNeeded = loaderHeapCount;
Expand All @@ -6394,12 +6392,15 @@ int ISOSDacInterface13.GetLoaderAllocatorHeaps(ClrDataAddress loaderAllocator, i
}
else
{
for (int i = 0; i < loaderHeapCount; i++)
int i = 0;
foreach (LoaderAllocatorHeapType heapType in Enum.GetValues<LoaderAllocatorHeapType>())
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API requires the order of the loaderallocator heaps match those provided by GetLoaderAllocatorHeapNames.

I don't want to rely on the order of the managed enum to assert that we output in the correct order.

Can we use the previous behavior and map that to the expected enum values?

Copy link
Copy Markdown
Contributor Author

@rcj1 rcj1 Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we'd have something like heaps.TryGetValue(GetEnumForFilteredEntry(filteredEntries[i].Name)?

{
pLoaderHeaps[i] = heaps.TryGetValue(filteredEntries[i].Name, out TargetPointer heapAddr)
? heapAddr.ToClrDataAddress(_target)
: 0;
pKinds[i] = 0; // LoaderHeapKindNormal
if (heaps.TryGetValue(heapType, out TargetPointer heapAddr))
{
pLoaderHeaps[i] = heapAddr.ToClrDataAddress(_target);
pKinds[i] = 0; // LoaderHeapKindNormal
i++;
}
}
}
}
Expand Down
47 changes: 23 additions & 24 deletions src/native/managed/cdac/tests/LoaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,17 +154,17 @@
Assert.Throws<DecoderFallbackException>(() => contract.TryGetSimpleName(handle, out _));
}

private static readonly Dictionary<string, TargetPointer> MockHeapDictionary = new()
{
["LowFrequencyHeap"] = new(0x1000),
["HighFrequencyHeap"] = new(0x2000),
["StaticsHeap"] = new(0x3000),
["StubHeap"] = new(0x4000),
["ExecutableHeap"] = new(0x5000),
["FixupPrecodeHeap"] = new(0x6000),
["NewStubPrecodeHeap"] = new(0x7000),
["IndcellHeap"] = new(0x8000),
["CacheEntryHeap"] = new(0x9000),
private static readonly Dictionary<LoaderAllocatorHeapType, TargetPointer> MockHeapDictionary = new()
{
[LoaderAllocatorHeapType.LowFrequency] = new(0x1000),
[LoaderAllocatorHeapType.HighFrequency] = new(0x2000),
[LoaderAllocatorHeapType.Statics] = new(0x3000),
[LoaderAllocatorHeapType.Stub] = new(0x4000),
[LoaderAllocatorHeapType.Executable] = new(0x5000),
[LoaderAllocatorHeapType.FixupPrecode] = new(0x6000),
[LoaderAllocatorHeapType.NewStubPrecode] = new(0x7000),
[LoaderAllocatorHeapType.Indcell] = new(0x8000),
[LoaderAllocatorHeapType.CacheEntry] = new(0x9000),
};

private static ISOSDacInterface13 CreateSOSDacInterface13ForHeapTests(MockTarget.Architecture arch)
Expand Down Expand Up @@ -201,7 +201,7 @@
var target = targetBuilder
.AddTypes(types)
.AddMockContract<ILoader>(Mock.Of<ILoader>(
l => l.GetLoaderAllocatorHeaps(It.IsAny<TargetPointer>()) == (IReadOnlyDictionary<string, TargetPointer>)MockHeapDictionary
l => l.GetLoaderAllocatorHeaps(It.IsAny<TargetPointer>()) == (IReadOnlyDictionary<LoaderAllocatorHeapType, TargetPointer>)MockHeapDictionary
&& l.GetGlobalLoaderAllocator() == new TargetPointer(0x100)))
.Build();
return new SOSDacImpl(target, null);
Expand Down Expand Up @@ -235,7 +235,7 @@

Assert.Equal(HResults.S_OK, hr);
Assert.Equal(MockHeapDictionary.Count, needed);
HashSet<string> expectedNames = new(MockHeapDictionary.Keys);

Check failure on line 238 in src/native/managed/cdac/tests/LoaderTests.cs

View check run for this annotation

Azure Pipelines / runtime (Build linux-x64 checked CLR_Tools_Tests)

src/native/managed/cdac/tests/LoaderTests.cs#L238

src/native/managed/cdac/tests/LoaderTests.cs(238,45): error CS1503: (NETCORE_ENGINEERING_TELEMETRY=Build) Argument 1: cannot convert from 'System.Collections.Generic.Dictionary<Microsoft.Diagnostics.DataContractReader.Contracts.LoaderAllocatorHeapType, Microsoft.Diagnostics.DataContractReader.TargetPointer>.KeyCollection' to 'System.Collections.Generic.IEnumerable<string>'

Check failure on line 238 in src/native/managed/cdac/tests/LoaderTests.cs

View check run for this annotation

Azure Pipelines / runtime

src/native/managed/cdac/tests/LoaderTests.cs#L238

src/native/managed/cdac/tests/LoaderTests.cs(238,45): error CS1503: (NETCORE_ENGINEERING_TELEMETRY=Build) Argument 1: cannot convert from 'System.Collections.Generic.Dictionary<Microsoft.Diagnostics.DataContractReader.Contracts.LoaderAllocatorHeapType, Microsoft.Diagnostics.DataContractReader.TargetPointer>.KeyCollection' to 'System.Collections.Generic.IEnumerable<string>'
for (int i = 0; i < needed; i++)
{
string actual = Marshal.PtrToStringAnsi((nint)names[i])!;
Expand All @@ -255,7 +255,7 @@

Assert.Equal(HResults.S_FALSE, hr);
Assert.Equal(MockHeapDictionary.Count, needed);
HashSet<string> expectedNames = new(MockHeapDictionary.Keys);

Check failure on line 258 in src/native/managed/cdac/tests/LoaderTests.cs

View check run for this annotation

Azure Pipelines / runtime (Build linux-x64 checked CLR_Tools_Tests)

src/native/managed/cdac/tests/LoaderTests.cs#L258

src/native/managed/cdac/tests/LoaderTests.cs(258,45): error CS1503: (NETCORE_ENGINEERING_TELEMETRY=Build) Argument 1: cannot convert from 'System.Collections.Generic.Dictionary<Microsoft.Diagnostics.DataContractReader.Contracts.LoaderAllocatorHeapType, Microsoft.Diagnostics.DataContractReader.TargetPointer>.KeyCollection' to 'System.Collections.Generic.IEnumerable<string>'

Check failure on line 258 in src/native/managed/cdac/tests/LoaderTests.cs

View check run for this annotation

Azure Pipelines / runtime

src/native/managed/cdac/tests/LoaderTests.cs#L258

src/native/managed/cdac/tests/LoaderTests.cs(258,45): error CS1503: (NETCORE_ENGINEERING_TELEMETRY=Build) Argument 1: cannot convert from 'System.Collections.Generic.Dictionary<Microsoft.Diagnostics.DataContractReader.Contracts.LoaderAllocatorHeapType, Microsoft.Diagnostics.DataContractReader.TargetPointer>.KeyCollection' to 'System.Collections.Generic.IEnumerable<string>'
for (int i = 0; i < 2; i++)
{
string actual = Marshal.PtrToStringAnsi((nint)names[i])!;
Expand Down Expand Up @@ -293,22 +293,21 @@
ISOSDacInterface13 impl = CreateSOSDacInterface13ForHeapTests(arch);

int needed;
impl.GetLoaderAllocatorHeapNames(0, null, &needed);

char** names = stackalloc char*[needed];
impl.GetLoaderAllocatorHeapNames(needed, names, &needed);

ClrDataAddress* heaps = stackalloc ClrDataAddress[needed];
int* kinds = stackalloc int[needed];
int hr = impl.GetLoaderAllocatorHeaps(new ClrDataAddress(0x100), needed, heaps, kinds, &needed);
ClrDataAddress* heaps = stackalloc ClrDataAddress[MockHeapDictionary.Count];
int* kinds = stackalloc int[MockHeapDictionary.Count];
int hr = impl.GetLoaderAllocatorHeaps(new ClrDataAddress(0x100), MockHeapDictionary.Count, heaps, kinds, &needed);

Assert.Equal(HResults.S_OK, hr);
Assert.Equal(MockHeapDictionary.Count, needed);
for (int i = 0; i < needed; i++)
int i = 0;
foreach (LoaderAllocatorHeapType heapType in Enum.GetValues<LoaderAllocatorHeapType>())
{
string name = Marshal.PtrToStringAnsi((nint)names[i])!;
Assert.Equal((ulong)MockHeapDictionary[name], (ulong)heaps[i]);
Assert.Equal(0, kinds[i]); // LoaderHeapKindNormal
if (MockHeapDictionary.TryGetValue(heapType, out TargetPointer expected))
{
Assert.Equal((ulong)expected, (ulong)heaps[i]);
Assert.Equal(0, kinds[i]); // LoaderHeapKindNormal
i++;
}
Comment on lines +296 to +310
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this unit test. We should verify that it matches the heap names, not the enum.

}
}

Expand Down
Loading