diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index bde7b40f264a3d..f133d232a25740 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -125,6 +125,11 @@ public readonly struct GCOomData IReadOnlyList GetGCBookkeepingMemoryRegions(); // Gets GC free regions (free region lists and freeable segments) IReadOnlyList GetGCFreeRegions(); + + // Enumerates every GC heap segment for the supplied heap data. Each yielded GCHeapSegmentInfo + // describes a single segment with the inclusive start and exclusive end of its memory range + // and its generation tag (or Ephemeral). + IEnumerable EnumerateHeapSegments(GCHeapData heapData); ``` ```csharp @@ -145,6 +150,26 @@ public readonly struct GCMemoryRegionData public ulong ExtraData { get; init; } public int Heap { get; init; } } + +public enum GCSegmentClassification +{ + Unknown, + Gen0, + Gen1, + Gen2, + LOH, + POH, + NonGC, + // Segments-GC only: marker used by IGC.EnumerateHeapSegments to denote the ephemeral + // segment on the gen2 list. The caller is responsible for splitting it into the Gen1 + // piece and an optional Gen2 prefix. + Ephemeral, +} + +public readonly record struct GCHeapSegmentInfo( + TargetPointer Start, + TargetPointer End, + GCSegmentClassification Generation); ``` ## Version 1 @@ -286,6 +311,7 @@ Constants used: | Name | Type | Purpose | Value | | --- | --- | --- | --- | | `WRK_HEAP_COUNT` | uint | The number of heaps in the `workstation` GC type | `1` | +| `HEAP_SEGMENT_FLAGS_READONLY` | ulong | `HeapSegment.Flags` bit identifying a readonly (e.g. frozen, non-GC) segment. | `1` | ```csharp GCHeapType IGC.GetGCIdentifiers() @@ -858,7 +884,7 @@ IReadOnlyList IGC.GetHandleTableMemoryRegions() // Safety caps matching native DAC const int MaxHandleTableRegions = 8192; const int MaxBookkeepingRegions = 32; - const int MaxSegmentListIterations = 2048; + const int MaxSegmentListIterations = 65536; int maxRegions = MaxHandleTableRegions; TargetPointer handleTableMap = target.ReadGlobalPointer("HandleTableMap"); @@ -1027,3 +1053,79 @@ TargetNUInt IGC.GetHandleExtraInfo(TargetPointer handle) return target.ReadNUInt(extraInfoAddr); } ``` + +EnumerateHeapSegments + +Returns the raw GC heap segments for a single heap by walking the per-generation segment +lists. +```csharp +IEnumerable IGC.EnumerateHeapSegments(GCHeapData heapData) +{ + // The generation table is laid out as gen0, gen1, gen2, LOH, POH (plus optional extras). + var gens = heapData.GenerationTable; + bool regions = GetGCIdentifiers().Contains("regions"); + + TargetPointer ephemeralSegment = heapData.EphemeralHeapSegment; + TargetPointer allocAllocated = heapData.AllocAllocated; + + if (regions) + { + // In regions mode each generation has its own segment list. Readonly entries on + // the gen2 list represent non-GC (e.g. frozen) regions and are reported as NonGC. + foreach (var (seg, _) in WalkSegmentList(gens[2].StartSegment)) + { + var type = (seg.Flags & HEAP_SEGMENT_FLAGS_READONLY) != 0 + ? GCSegmentClassification.NonGC + : GCSegmentClassification.Gen2; + yield return new GCHeapSegmentInfo(seg.Mem, seg.Allocated, type); + } + foreach (var (seg, _) in WalkSegmentList(gens[1].StartSegment)) + yield return new GCHeapSegmentInfo(seg.Mem, seg.Allocated, GCSegmentClassification.Gen1); + foreach (var (seg, segAddr) in WalkSegmentList(gens[0].StartSegment)) + { + // For the gen0 segment that matches the ephemeral_heap_segment, end is alloc_allocated. + TargetPointer end = segAddr == ephemeralSegment ? allocAllocated : seg.Allocated; + yield return new GCHeapSegmentInfo(seg.Mem, end, GCSegmentClassification.Gen0); + } + } + else + { + // In segments mode the gen2 list contains every SOH segment. The ephemeral + // segment is tagged Ephemeral as the layer-2 split marker; non-ephemeral entries + // are reported with their true generation (Gen2 or NonGC for readonly). + foreach (var (seg, segAddr) in WalkSegmentList(gens[2].StartSegment)) + { + GCSegmentClassification type; + if (segAddr == ephemeralSegment) + type = GCSegmentClassification.Ephemeral; + else if ((seg.Flags & HEAP_SEGMENT_FLAGS_READONLY) != 0) + type = GCSegmentClassification.NonGC; + else + type = GCSegmentClassification.Gen2; + TargetPointer end = segAddr == ephemeralSegment ? allocAllocated : seg.Allocated; + yield return new GCHeapSegmentInfo(seg.Mem, end, type); + } + } + + // LOH and POH segments are always reported as-is regardless of GC mode. + foreach (var (seg, _) in WalkSegmentList(gens[3].StartSegment)) + yield return new GCHeapSegmentInfo(seg.Mem, seg.Allocated, GCSegmentClassification.LOH); + foreach (var (seg, _) in WalkSegmentList(gens[4].StartSegment)) + yield return new GCHeapSegmentInfo(seg.Mem, seg.Allocated, GCSegmentClassification.POH); +} + +IEnumerable<(HeapSegment Segment, TargetPointer Address)> WalkSegmentList(TargetPointer startSegment) +{ + // Bounded traversal of the singly-linked HeapSegment list, guarding against cycles or + // corrupt links via a fixed iteration cap (MaxSegmentListIterations = 65536). + int iterationMax = MaxSegmentListIterations; + TargetPointer current = startSegment; + while (current != TargetPointer.Null) + { + HeapSegment seg = /* read HeapSegment at current */; + yield return (seg, current); + current = seg.Next; + if (iterationMax-- <= 0) throw /* cycle detected */; + } +} +``` diff --git a/src/coreclr/debug/daccess/daccess.cpp b/src/coreclr/debug/daccess/daccess.cpp index 76ce61ed975e4f..8271d1c9b3bbf4 100644 --- a/src/coreclr/debug/daccess/daccess.cpp +++ b/src/coreclr/debug/daccess/daccess.cpp @@ -7976,7 +7976,7 @@ void DacFreeRegionEnumerator::AddSingleSegment(const dac_heap_segment &curr, Fre void DacFreeRegionEnumerator::AddSegmentList(DPTR(dac_heap_segment) start, FreeRegionKind kind, int heap) { - int iterationMax = 2048; + int iterationMax = 65536; DPTR(dac_heap_segment) curr = start; while (curr != nullptr) diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index c4d079ac828453..eee1d1586f5de8 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -6875,10 +6875,14 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::WalkHeap(HeapWalkHandle handle, -HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetHeapSegments(OUT DacDbiArrayList *pSegments) +HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::EnumerateHeapSegments( + FP_HEAPSEGMENT_CALLBACK fpCallback, + CALLBACK_DATA pUserData) { DD_ENTER_MAY_THROW; + if (fpCallback == NULL) + return E_POINTER; size_t heapCount = 0; HeapData *heaps = 0; @@ -6893,53 +6897,28 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetHeapSegments(OUT DacDbiArrayLi NewArrayHolder _heapHolder = heaps; - // Count the number of segments to know how much to allocate. - int total = 0; - for (size_t i = 0; i < heapCount; ++i) - { - total += (int)heaps[i].SegmentCount; - if (!region) - { - // SegmentCount is +1 due to the ephemeral segment containing more than one - // generation (Gen1 + Gen0, and sometimes part of Gen2). - total++; - - // It's possible that part of Gen2 lives on the ephemeral segment. If so, - // we need to add one more to the output. - const size_t eph = heaps[i].EphemeralSegment; - _ASSERTE(eph < heaps[i].SegmentCount); - if (heaps[i].Segments[eph].Start != heaps[i].Gen1Start) - total++; - } - } - - pSegments->Alloc(total); + if (FAILED(hr)) + return hr; - // Now walk all segments and write them to the array. - int curr = 0; + // Now walk all segments and emit them through the callback. for (size_t i = 0; i < heapCount; ++i) { - _ASSERTE(curr < total); if (!region) { // Generation 0 is not in the segment list. - COR_SEGMENT &seg = (*pSegments)[curr++]; - seg.start = heaps[i].Gen0Start; - seg.end = heaps[i].Gen0End; - seg.type = CorDebug_Gen0; - seg.heap = (ULONG)i; + fpCallback(heaps[i].Gen0Start, heaps[i].Gen0End, (int)CorDebug_Gen0, (ULONG)i, pUserData); } for (size_t j = 0; j < heaps[i].SegmentCount; ++j) { if (region) { - _ASSERTE(curr < total); - COR_SEGMENT &seg = (*pSegments)[curr++]; - seg.start = heaps[i].Segments[j].Start; - seg.end = heaps[i].Segments[j].End; - seg.type = (CorDebugGenerationTypes)heaps[i].Segments[j].Generation; - seg.heap = (ULONG)i; + fpCallback( + heaps[i].Segments[j].Start, + heaps[i].Segments[j].End, + (int)heaps[i].Segments[j].Generation, + (ULONG)i, + pUserData); } else if (heaps[i].Segments[j].Generation == 1) { @@ -6948,43 +6927,29 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetHeapSegments(OUT DacDbiArrayLi _ASSERTE(heaps[i].Segments[j].Start <= heaps[i].Gen1Start); _ASSERTE(heaps[i].Segments[j].End > heaps[i].Gen1Start); - { - _ASSERTE(curr < total); - COR_SEGMENT &seg = (*pSegments)[curr++]; - seg.start = heaps[i].Gen1Start; - seg.end = heaps[i].Gen0Start; - seg.type = CorDebug_Gen1; - seg.heap = (ULONG)i; - } + fpCallback(heaps[i].Gen1Start, heaps[i].Gen0Start, (int)CorDebug_Gen1, (ULONG)i, pUserData); // It's possible for Gen2 to take up a portion of the ephemeral segment. // We test for that here. if (heaps[i].Segments[j].Start != heaps[i].Gen1Start) { - _ASSERTE(curr < total); - COR_SEGMENT &seg = (*pSegments)[curr++]; - seg.start = heaps[i].Segments[j].Start; - seg.end = heaps[i].Gen1Start; - seg.type = CorDebug_Gen2; - seg.heap = (ULONG)i; + fpCallback(heaps[i].Segments[j].Start, heaps[i].Gen1Start, (int)CorDebug_Gen2, (ULONG)i, pUserData); } } else { // Otherwise, we have a gen2, POH, LOH or NonGC - _ASSERTE(curr < total); - COR_SEGMENT &seg = (*pSegments)[curr++]; - seg.start = heaps[i].Segments[j].Start; - seg.end = heaps[i].Segments[j].End; - _ASSERTE(heaps[i].Segments[j].Generation <= CorDebug_NonGC); - seg.type = (CorDebugGenerationTypes)heaps[i].Segments[j].Generation; - seg.heap = (ULONG)i; + fpCallback( + heaps[i].Segments[j].Start, + heaps[i].Segments[j].End, + (int)heaps[i].Segments[j].Generation, + (ULONG)i, + pUserData); } } } - _ASSERTE(total == curr); return hr; } diff --git a/src/coreclr/debug/daccess/dacdbiimpl.h b/src/coreclr/debug/daccess/dacdbiimpl.h index 171b067e02afe4..da74f528abd8fc 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.h +++ b/src/coreclr/debug/daccess/dacdbiimpl.h @@ -118,7 +118,7 @@ class DacDbiInterfaceImpl : OUT COR_HEAPOBJECT * objects, OUT ULONG *fetched); - HRESULT STDMETHODCALLTYPE GetHeapSegments(OUT DacDbiArrayList *pSegments); + HRESULT STDMETHODCALLTYPE EnumerateHeapSegments(FP_HEAPSEGMENT_CALLBACK fpCallback, CALLBACK_DATA pUserData); HRESULT STDMETHODCALLTYPE IsValidObject(CORDB_ADDRESS obj, OUT BOOL * pResult); diff --git a/src/coreclr/debug/di/process.cpp b/src/coreclr/debug/di/process.cpp index 34752e9f98cb29..5f6dfdeb36b11f 100644 --- a/src/coreclr/debug/di/process.cpp +++ b/src/coreclr/debug/di/process.cpp @@ -2188,6 +2188,43 @@ HRESULT CordbProcess::GetGCHeapInformation(COR_HEAPINFO *pHeapInfo) return hr; } +namespace +{ + struct HeapSegmentAccumulator + { + CQuickArrayList segments; + HRESULT hrError; + }; + + void HeapSegmentCallback( + CORDB_ADDRESS rangeStart, + CORDB_ADDRESS rangeEnd, + int generation, + ULONG heap, + CALLBACK_DATA pUserData) + { + HeapSegmentAccumulator *acc = + reinterpret_cast(pUserData); + + if (FAILED(acc->hrError)) + return; + + COR_SEGMENT s; + s.start = rangeStart; + s.end = rangeEnd; + s.type = (CorDebugGenerationTypes)generation; + s.heap = heap; + HRESULT hr = S_OK; + EX_TRY + { + acc->segments.Push(s); + } + EX_CATCH_HRESULT(hr); + if (FAILED(hr)) + acc->hrError = hr; + } +} + HRESULT CordbProcess::EnumerateHeapRegions(ICorDebugHeapSegmentEnum **ppRegions) { if (!ppRegions) @@ -2199,14 +2236,18 @@ HRESULT CordbProcess::EnumerateHeapRegions(ICorDebugHeapSegmentEnum **ppRegions) EX_TRY { - DacDbiArrayList segments; - hr = GetDAC()->GetHeapSegments(&segments); + HeapSegmentAccumulator acc; + acc.hrError = S_OK; + + hr = GetDAC()->EnumerateHeapSegments(&HeapSegmentCallback, &acc); + if (SUCCEEDED(hr) && FAILED(acc.hrError)) + hr = acc.hrError; if (SUCCEEDED(hr)) { - if (!segments.IsEmpty()) + if (acc.segments.Size() != 0) { - CordbHeapSegmentEnumerator *segEnum = new CordbHeapSegmentEnumerator(this, &segments[0], (DWORD)segments.Count()); + CordbHeapSegmentEnumerator *segEnum = new CordbHeapSegmentEnumerator(this, acc.segments.Ptr(), (DWORD)acc.segments.Size()); GetContinueNeuterList()->Add(this, segEnum); hr = segEnum->QueryInterface(__uuidof(ICorDebugHeapSegmentEnum), (void**)ppRegions); } diff --git a/src/coreclr/debug/inc/dacdbiinterface.h b/src/coreclr/debug/inc/dacdbiinterface.h index b5065633be8a09..d0f3b75d63a72b 100644 --- a/src/coreclr/debug/inc/dacdbiinterface.h +++ b/src/coreclr/debug/inc/dacdbiinterface.h @@ -1984,7 +1984,23 @@ IDacDbiInterface : public IUnknown OUT COR_HEAPOBJECT * objects, OUT ULONG * pFetched) = 0; - virtual HRESULT STDMETHODCALLTYPE GetHeapSegments(OUT DacDbiArrayList * pSegments) = 0; + // Callback invoked for each GC heap segment. + // + // Arguments: + // rangeStart - start address of the segment (inclusive). + // rangeEnd - end address of the segment (exclusive). + // generation - CorDebugGenerationTypes value identifying the generation/kind of the segment. + // heap - index of the heap the segment belongs to (0 for workstation GC). + // pUserData - user data passed to EnumerateHeapSegments. + typedef void (*FP_HEAPSEGMENT_CALLBACK)(CORDB_ADDRESS rangeStart, CORDB_ADDRESS rangeEnd, int generation, ULONG heap, CALLBACK_DATA pUserData); + + // Enumerate the GC heap segments. + // + // The callback is invoked once per segment and must not throw. To accumulate segments, callers + // typically stash a buffer (and any failure flag) in pUserData. The walker runs to completion + // regardless of any per-segment failure the callback decides to record; the caller surfaces the + // recorded failure after EnumerateHeapSegments returns. + virtual HRESULT STDMETHODCALLTYPE EnumerateHeapSegments(FP_HEAPSEGMENT_CALLBACK fpCallback, CALLBACK_DATA pUserData) = 0; virtual HRESULT STDMETHODCALLTYPE IsValidObject(CORDB_ADDRESS obj, OUT BOOL * pResult) = 0; diff --git a/src/coreclr/inc/dacdbi.idl b/src/coreclr/inc/dacdbi.idl index 1cb5b13e4301e4..b19a1059318177 100644 --- a/src/coreclr/inc/dacdbi.idl +++ b/src/coreclr/inc/dacdbi.idl @@ -143,6 +143,7 @@ typedef void (*FP_ASSEMBLY_ENUMERATION_CALLBACK)(VMPTR_Assembly vmAssembly, CALL typedef void (*FP_MODULE_ENUMERATION_CALLBACK)(VMPTR_Assembly vmAssembly, CALLBACK_DATA pUserData); typedef void (*FP_THREAD_ENUMERATION_CALLBACK)(VMPTR_Thread vmThread, CALLBACK_DATA pUserData); typedef BOOL (*FP_INTERNAL_FRAME_ENUMERATION_CALLBACK)(FramePointer fpFrame, CALLBACK_DATA pUserData); +typedef void (*FP_HEAPSEGMENT_CALLBACK)(CORDB_ADDRESS rangeStart, CORDB_ADDRESS rangeEnd, int generation, ULONG heap, CALLBACK_DATA pUserData); // @@ -378,7 +379,7 @@ interface IDacDbiInterface : IUnknown HRESULT CreateHeapWalk([out] HeapWalkHandle * pHandle); HRESULT DeleteHeapWalk([in] HeapWalkHandle handle); HRESULT WalkHeap([in] HeapWalkHandle handle, [in] ULONG count, [out] COR_HEAPOBJECT * objects, [out] ULONG * pFetched); - HRESULT GetHeapSegments([out] DacDbiArrayList_COR_SEGMENT * pSegments); + HRESULT EnumerateHeapSegments([in] FP_HEAPSEGMENT_CALLBACK fpCallback, [in] CALLBACK_DATA pUserData); HRESULT IsValidObject([in] CORDB_ADDRESS obj, [out] BOOL * pResult); // Reference Walking 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 c5fd1e18bff813..cc4b5593541b83 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 @@ -116,6 +116,23 @@ public readonly struct GCMemoryRegionData public int Heap { get; init; } } +public enum GCSegmentClassification +{ + Unknown, + Gen0, + Gen1, + Gen2, + LOH, + POH, + NonGC, + Ephemeral, +} + +public readonly record struct GCHeapSegmentInfo( + TargetPointer Start, + TargetPointer End, + GCSegmentClassification Generation); + public interface IGC : IContract { static string IContract.Name { get; } = nameof(GC); @@ -151,6 +168,10 @@ public interface IGC : IContract IReadOnlyList GetHandleTableMemoryRegions() => throw new NotImplementedException(); IReadOnlyList GetGCBookkeepingMemoryRegions() => throw new NotImplementedException(); IReadOnlyList GetGCFreeRegions() => throw new NotImplementedException(); + + // Enumerates the raw GC heap segments for a single heap as recorded by the GC's per-generation + // segment lists. + IEnumerable EnumerateHeapSegments(GCHeapData heapData) => throw new NotImplementedException(); } public readonly struct GC : IGC 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 932a31af52e3e2..8648fde6ef7975 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 @@ -16,7 +16,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; // Safety caps to limit traversals in case of memory corruption, matching native DAC. private const int MaxHandleTableRegions = 8192; private const int MaxBookkeepingRegions = 32; - private const int MaxSegmentListIterations = 2048; + private const int MaxSegmentListIterations = 65536; private enum GCType { @@ -43,6 +43,7 @@ private enum HandleType_1 private readonly TargetPointer _debugDestroyedHandleValue; private readonly uint _handleMaxInternalTypes; private readonly uint _handleSegmentSize; + private readonly uint _heapSegmentFlagsReadonly = 1; internal GC_1(Target target) { @@ -362,6 +363,85 @@ List IGC.GetHandles(HandleType[] types) return handles; } + IEnumerable IGC.EnumerateHeapSegments(GCHeapData heapData) + { + // The generation table is laid out as gen0, gen1, gen2, LOH, POH (plus optional extras). + IReadOnlyList gens = heapData.GenerationTable; + if (gens.Count < 5) + throw new InvalidOperationException($"Expected at least 5 generations in the generation table, got {gens.Count}."); + + bool regions = ((IGC)this).GetGCIdentifiers().Contains(GCIdentifiers.Regions); + + TargetPointer ephemeralSegment = heapData.EphemeralHeapSegment; + TargetPointer allocAllocated = heapData.AllocAllocated; + + if (regions) + { + // In regions mode each generation has its own segment list. Readonly entries on the + // gen2 list represent non-GC (e.g. frozen) regions and are reported as NonGC. + foreach ((Data.HeapSegment seg, TargetPointer _) in WalkSegmentList(gens[2].StartSegment)) + { + GCSegmentClassification type = (seg.Flags.Value & _heapSegmentFlagsReadonly) != 0 + ? GCSegmentClassification.NonGC + : GCSegmentClassification.Gen2; + yield return new GCHeapSegmentInfo(seg.Mem, seg.Allocated, type); + } + foreach ((Data.HeapSegment seg, TargetPointer _) in WalkSegmentList(gens[1].StartSegment)) + { + yield return new GCHeapSegmentInfo(seg.Mem, seg.Allocated, GCSegmentClassification.Gen1); + } + foreach ((Data.HeapSegment seg, TargetPointer segAddr) in WalkSegmentList(gens[0].StartSegment)) + { + // For the gen0 segment that matches the ephemeral_heap_segment, end is alloc_allocated. + TargetPointer end = segAddr == ephemeralSegment ? allocAllocated : seg.Allocated; + yield return new GCHeapSegmentInfo(seg.Mem, end, GCSegmentClassification.Gen0); + } + } + else + { + // In segments mode the gen2 list contains every SOH segment. + foreach ((Data.HeapSegment seg, TargetPointer segAddr) in WalkSegmentList(gens[2].StartSegment)) + { + GCSegmentClassification type; + if (segAddr == ephemeralSegment) + type = GCSegmentClassification.Ephemeral; + else if ((seg.Flags.Value & _heapSegmentFlagsReadonly) != 0) + type = GCSegmentClassification.NonGC; + else + type = GCSegmentClassification.Gen2; + + TargetPointer end = segAddr == ephemeralSegment ? allocAllocated : seg.Allocated; + yield return new GCHeapSegmentInfo(seg.Mem, end, type); + } + } + + // Large object heap segments. + foreach ((Data.HeapSegment seg, TargetPointer _) in WalkSegmentList(gens[3].StartSegment)) + { + yield return new GCHeapSegmentInfo(seg.Mem, seg.Allocated, GCSegmentClassification.LOH); + } + + // Pinned object heap segments. + foreach ((Data.HeapSegment seg, TargetPointer _) in WalkSegmentList(gens[4].StartSegment)) + { + yield return new GCHeapSegmentInfo(seg.Mem, seg.Allocated, GCSegmentClassification.POH); + } + } + + private IEnumerable<(Data.HeapSegment Segment, TargetPointer Address)> WalkSegmentList(TargetPointer startSegment) + { + int iterationMax = MaxSegmentListIterations; + TargetPointer current = startSegment; + while (current != TargetPointer.Null) + { + Data.HeapSegment seg = _target.ProcessedData.GetOrAdd(current); + yield return (seg, current); + current = seg.Next; + if (iterationMax-- <= 0) + throw new InvalidOperationException($"Segment list exceeded {MaxSegmentListIterations} iterations; possible cycle."); + } + } + HandleType[] IGC.GetSupportedHandleTypes() { List supportedTypes = diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index ddac34b9199c96..0b9bfe91dd9785 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -1793,8 +1793,128 @@ public int DeleteHeapWalk(nuint handle) public int WalkHeap(nuint handle, uint count, COR_HEAPOBJECT* objects, uint* fetched) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.WalkHeap(handle, count, objects, fetched) : HResults.E_NOTIMPL; - public int GetHeapSegments(nint pSegments) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetHeapSegments(pSegments) : HResults.E_NOTIMPL; +#if DEBUG + [ThreadStatic] + private static List<(ulong Start, ulong End, int Generation, uint Heap)>? _debugEnumerateHeapSegments; + + private static List<(ulong Start, ulong End, int Generation, uint Heap)> DebugEnumerateHeapSegments + => _debugEnumerateHeapSegments ??= new(); + + [UnmanagedCallersOnly] + private static void EnumerateHeapSegmentsDebugCallback(ulong start, ulong end, int generation, uint heap, nint _) + { + DebugEnumerateHeapSegments.Add((start, end, generation, heap)); + } +#endif + + public int EnumerateHeapSegments(delegate* unmanaged fpCallback, nint pUserData) + { + int hr = HResults.S_OK; + List<(ulong Start, ulong End, int Generation, uint Heap)> segments = new(); + try + { + if (fpCallback is null) + throw new ArgumentNullException(nameof(fpCallback)); + + IGC gc = _target.Contracts.GC; + string[] gcIdentifiers = gc.GetGCIdentifiers(); + bool regions = gcIdentifiers.Contains(GCIdentifiers.Regions); + bool isWorkstation = gcIdentifiers.Contains(GCIdentifiers.Workstation); + + uint heapIndex = 0; + foreach (GCHeapData heapData in EnumerateHeaps(gc, isWorkstation)) + { + TargetPointer gen0AllocStart = heapData.GenerationTable[0].AllocationStart; + TargetPointer gen1AllocStart = heapData.GenerationTable[1].AllocationStart; + + // In segments mode, Gen0 lives outside the segment list - synthesize it as a + // heap-level entry bracketed by [gen0.AllocationStart, alloc_allocated). + if (!regions) + segments.Add((gen0AllocStart.Value, heapData.AllocAllocated.Value, (int)CorDebugGenerationTypes.CorDebug_Gen0, heapIndex)); + + foreach (GCHeapSegmentInfo raw in gc.EnumerateHeapSegments(heapData)) + { + if (raw.Generation != GCSegmentClassification.Ephemeral) + { + segments.Add((raw.Start.Value, raw.End.Value, (int)ToCorDebugGenerationType(raw.Generation), heapIndex)); + } + else + { + // Segments mode only: split the ephemeral marker into the Gen1 piece + // ([gen1.AllocationStart, gen0.AllocationStart)) plus an optional Gen2 + // prefix ([raw.Start, gen1.AllocationStart)). + segments.Add((gen1AllocStart.Value, gen0AllocStart.Value, (int)CorDebugGenerationTypes.CorDebug_Gen1, heapIndex)); + if (raw.Start != gen1AllocStart) + segments.Add((raw.Start.Value, gen1AllocStart.Value, (int)CorDebugGenerationTypes.CorDebug_Gen2, heapIndex)); + } + } + + heapIndex++; + } + + foreach ((ulong start, ulong end, int generation, uint heap) in segments) + fpCallback(start, end, generation, heap, pUserData); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacy is not null) + { + DebugEnumerateHeapSegments.Clear(); + delegate* unmanaged debugCallbackPtr = &EnumerateHeapSegmentsDebugCallback; + int hrLocal = _legacy.EnumerateHeapSegments(debugCallbackPtr, 0); + Debug.ValidateHResult(hr, hrLocal); + + if (hr == HResults.S_OK && hrLocal == HResults.S_OK) + { + List<(ulong Start, ulong End, int Generation, uint Heap)> legacySegments = DebugEnumerateHeapSegments; + if (!segments.SequenceEqual(legacySegments)) + { + Debug.Assert(segments.Count == legacySegments.Count, + $"cDAC: {segments.Count} segments, DAC: {legacySegments.Count} segments"); + + int compareCount = Math.Min(segments.Count, legacySegments.Count); + for (int i = 0; i < compareCount; i++) + { + Debug.Assert(segments[i] == legacySegments[i], + $"Segment {i} mismatch - cDAC: (0x{segments[i].Start:x}, 0x{segments[i].End:x}, gen={segments[i].Generation}, heap={segments[i].Heap}), DAC: (0x{legacySegments[i].Start:x}, 0x{legacySegments[i].End:x}, gen={legacySegments[i].Generation}, heap={legacySegments[i].Heap})"); + } + } + } + DebugEnumerateHeapSegments.Clear(); + } +#endif + return hr; + } + + private static IEnumerable EnumerateHeaps(IGC gc, bool isWorkstation) + { + if (isWorkstation) + { + yield return gc.GetHeapData(); + } + else + { + foreach (TargetPointer heapAddress in gc.GetGCHeaps()) + yield return gc.GetHeapData(heapAddress); + } + } + + private static CorDebugGenerationTypes ToCorDebugGenerationType(GCSegmentClassification generation) => generation switch + { + GCSegmentClassification.Gen0 => CorDebugGenerationTypes.CorDebug_Gen0, + GCSegmentClassification.Gen1 => CorDebugGenerationTypes.CorDebug_Gen1, + GCSegmentClassification.Gen2 => CorDebugGenerationTypes.CorDebug_Gen2, + GCSegmentClassification.LOH => CorDebugGenerationTypes.CorDebug_LOH, + GCSegmentClassification.POH => CorDebugGenerationTypes.CorDebug_POH, + GCSegmentClassification.NonGC => CorDebugGenerationTypes.CorDebug_NonGC, + // Ephemeral is an internal marker that must be split by the caller; it never appears in + // emitted output. + _ => throw new ArgumentOutOfRangeException(nameof(generation), generation, null), + }; public int IsValidObject(ulong obj, Interop.BOOL* pResult) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.IsValidObject(obj, pResult) : HResults.E_NOTIMPL; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs index 3e3d0860352180..79c0e36953db9f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs @@ -176,6 +176,16 @@ public enum CorDebugUserState USER_THREADPOOL = 0x100, } +public enum CorDebugGenerationTypes +{ + CorDebug_Gen0 = 0, + CorDebug_Gen1 = 1, + CorDebug_Gen2 = 2, + CorDebug_LOH = 3, + CorDebug_POH = 4, + CorDebug_NonGC = 0x7FFFFFFF, +} + public enum IlNum : int { TYPECTXT_ILNUM = -3, @@ -500,7 +510,7 @@ public unsafe partial interface IDacDbiInterface int WalkHeap(nuint handle, uint count, COR_HEAPOBJECT* objects, uint* fetched); [PreserveSig] - int GetHeapSegments(nint pSegments); + int EnumerateHeapSegments(/*FP_HEAPSEGMENT_CALLBACK*/ delegate* unmanaged fpCallback, nint pUserData); [PreserveSig] int IsValidObject(ulong obj, Interop.BOOL* pResult); diff --git a/src/native/managed/cdac/tests/GCTests.cs b/src/native/managed/cdac/tests/GCTests.cs index 7b7ae3a372046c..ff3c3feb8c01ab 100644 --- a/src/native/managed/cdac/tests/GCTests.cs +++ b/src/native/managed/cdac/tests/GCTests.cs @@ -116,6 +116,171 @@ public void GetHeapData_WithFiveGenerations(MockTarget.Architecture arch) Assert.Equal(fillPointers[i], (ulong)heapData.FillPointers[i]); } } + + private sealed record CapturedSegment(ulong Start, ulong End, GCSegmentClassification Generation); + + private static MockGCBuilder.Generation[] MakeGenerations(ulong gen0Seg, ulong gen0Start, ulong gen1Seg, ulong gen1Start, ulong gen2Seg, ulong lohSeg, ulong pohSeg) + => + [ + new() { StartSegment = gen0Seg, AllocationStart = gen0Start, AllocContextPointer = 0, AllocContextLimit = 0 }, + new() { StartSegment = gen1Seg, AllocationStart = gen1Start, AllocContextPointer = 0, AllocContextLimit = 0 }, + new() { StartSegment = gen2Seg, AllocationStart = 0, AllocContextPointer = 0, AllocContextLimit = 0 }, + new() { StartSegment = lohSeg, AllocationStart = 0, AllocContextPointer = 0, AllocContextLimit = 0 }, + new() { StartSegment = pohSeg, AllocationStart = 0, AllocContextPointer = 0, AllocContextLimit = 0 }, + ]; + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void EnumerateHeapSegments_Wks_Regions(MockTarget.Architecture arch) + { + ulong allocAllocated = 0x4000_0500; + ulong frozenSeg = 0, gen2Seg = 0, gen1Seg = 0, gen0Seg = 0, lohSeg = 0, pohSeg = 0; + + ulong[] fillPointers = [0x1001, 0x2002, 0x3003, 0x4004, 0x5005, 0x6006, 0x7007]; + + Target target = new TestPlaceholderTarget.Builder(arch) + .AddGCHeapWks(gc => + { + gc.GCIdentifiers = "workstation,regions"; + gc.AllocAllocated = allocAllocated; + gc.FillPointers = fillPointers; + gc.ConfigureMemory = b => + { + Layout segLayout = b.GetHeapSegmentLayout(includeHeapField: false); + // gen2 list: gen2 seg -> frozen (readonly) seg + frozenSeg = b.AddHeapSegment(segLayout, mem: 0x6000_0000, allocated: 0x6000_1000, next: 0, flags: 1, name: "FrozenSeg").Address; + gen2Seg = b.AddHeapSegment(segLayout, mem: 0x2000_0000, allocated: 0x2000_1000, next: frozenSeg, name: "Gen2Seg").Address; + gen1Seg = b.AddHeapSegment(segLayout, mem: 0x3000_0000, allocated: 0x3000_1000, next: 0, name: "Gen1Seg").Address; + gen0Seg = b.AddHeapSegment(segLayout, mem: 0x4000_0000, allocated: 0x4000_FFFF, next: 0, name: "Gen0Seg").Address; + lohSeg = b.AddHeapSegment(segLayout, mem: 0x5000_0000, allocated: 0x5000_1000, next: 0, name: "LohSeg").Address; + pohSeg = b.AddHeapSegment(segLayout, mem: 0x5100_0000, allocated: 0x5100_1000, next: 0, name: "PohSeg").Address; + // Ephemeral is the gen0 segment in regions mode -> end is overridden to alloc_allocated. + b.WritePointerGlobal(b.EphemeralHeapSegmentGlobalAddress, gen0Seg); + }; + gc.GenerationsFactory = b => MakeGenerations(gen0Seg, 0, gen1Seg, 0, gen2Seg, lohSeg, pohSeg); + }) + .Build(); + IGC gc = target.Contracts.GC; + + List captured = new(); + foreach (GCHeapSegmentInfo seg in gc.EnumerateHeapSegments(gc.GetHeapData())) + captured.Add(new CapturedSegment(seg.Start, seg.End, seg.Generation)); + + // Raw per-heap walk: gen2 list (gen2Seg, frozenSeg), gen1 list (gen1Seg), + // gen0 list (gen0Seg ephemeral - end overridden to allocAllocated), LOH, POH. + Assert.Equal(6, captured.Count); + Assert.Equal(new CapturedSegment(0x2000_0000, 0x2000_1000, GCSegmentClassification.Gen2), captured[0]); + Assert.Equal(new CapturedSegment(0x6000_0000, 0x6000_1000, GCSegmentClassification.NonGC), captured[1]); + Assert.Equal(new CapturedSegment(0x3000_0000, 0x3000_1000, GCSegmentClassification.Gen1), captured[2]); + Assert.Equal(new CapturedSegment(0x4000_0000, allocAllocated, GCSegmentClassification.Gen0), captured[3]); + Assert.Equal(new CapturedSegment(0x5000_0000, 0x5000_1000, GCSegmentClassification.LOH), captured[4]); + Assert.Equal(new CapturedSegment(0x5100_0000, 0x5100_1000, GCSegmentClassification.POH), captured[5]); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void EnumerateHeapSegments_Wks_Segments(MockTarget.Architecture arch) + { + // In segments mode, the gen2 list contains all SOH segments. The ephemeral segment is + // surfaced as a single Ephemeral-tagged entry; the caller is responsible for splitting it. + ulong gen1Start = 0x2000_4000; + ulong gen0Start = 0x2000_6000; + ulong allocAllocated = 0x2000_7000; + + ulong gen2OnlySeg = 0, ephSeg = 0, lohSeg = 0, pohSeg = 0; + ulong[] fillPointers = [0x1001, 0x2002, 0x3003, 0x4004, 0x5005, 0x6006, 0x7007]; + + Target target = new TestPlaceholderTarget.Builder(arch) + .AddGCHeapWks(gc => + { + gc.GCIdentifiers = "workstation,segments"; + gc.AllocAllocated = allocAllocated; + gc.FillPointers = fillPointers; + gc.ConfigureMemory = b => + { + Layout segLayout = b.GetHeapSegmentLayout(includeHeapField: false); + // Allocated on the ephemeral seg is ignored - end is alloc_allocated. + ephSeg = b.AddHeapSegment(segLayout, mem: 0x2000_0000, allocated: allocAllocated, next: 0, name: "EphSeg").Address; + gen2OnlySeg = b.AddHeapSegment(segLayout, mem: 0x1000_0000, allocated: 0x1000_1000, next: ephSeg, name: "Gen2Only").Address; + lohSeg = b.AddHeapSegment(segLayout, mem: 0x5000_0000, allocated: 0x5000_1000, next: 0, name: "LohSeg").Address; + pohSeg = b.AddHeapSegment(segLayout, mem: 0x5100_0000, allocated: 0x5100_1000, next: 0, name: "PohSeg").Address; + b.WritePointerGlobal(b.EphemeralHeapSegmentGlobalAddress, ephSeg); + }; + // In segments mode, gen0/gen1 segments are not used; gen2 starts at the head of the SOH list. + gc.GenerationsFactory = b => new MockGCBuilder.Generation[] + { + new() { StartSegment = 0, AllocationStart = gen0Start, AllocContextPointer = 0, AllocContextLimit = 0 }, + new() { StartSegment = 0, AllocationStart = gen1Start, AllocContextPointer = 0, AllocContextLimit = 0 }, + new() { StartSegment = gen2OnlySeg, AllocationStart = 0, AllocContextPointer = 0, AllocContextLimit = 0 }, + new() { StartSegment = lohSeg, AllocationStart = 0, AllocContextPointer = 0, AllocContextLimit = 0 }, + new() { StartSegment = pohSeg, AllocationStart = 0, AllocContextPointer = 0, AllocContextLimit = 0 }, + }; + }) + .Build(); + IGC gc = target.Contracts.GC; + + List captured = new(); + foreach (GCHeapSegmentInfo seg in gc.EnumerateHeapSegments(gc.GetHeapData())) + captured.Add(new CapturedSegment(seg.Start, seg.End, seg.Generation)); + + // Raw per-heap walk in segments mode: gen2 list (gen2OnlySeg as Gen2, ephSeg + // tagged Ephemeral as the marker), then LOH, POH. No synthetic Gen0 and no + // Gen1/Gen2 split - those are the caller's responsibility. + Assert.Equal(4, captured.Count); + Assert.Equal(new CapturedSegment(0x1000_0000, 0x1000_1000, GCSegmentClassification.Gen2), captured[0]); + Assert.Equal(new CapturedSegment(0x2000_0000, allocAllocated, GCSegmentClassification.Ephemeral), captured[1]); + Assert.Equal(new CapturedSegment(0x5000_0000, 0x5000_1000, GCSegmentClassification.LOH), captured[2]); + Assert.Equal(new CapturedSegment(0x5100_0000, 0x5100_1000, GCSegmentClassification.POH), captured[3]); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void EnumerateHeapSegments_Svr_Regions(MockTarget.Architecture arch) + { + ulong allocAllocated = 0x4000_0500; + ulong gen2Seg = 0, gen1Seg = 0, gen0Seg = 0, lohSeg = 0, pohSeg = 0; + ulong[] fillPointers = [0x1001, 0x2002, 0x3003, 0x4004, 0x5005, 0x6006, 0x7007]; + + Target target = new TestPlaceholderTarget.Builder(arch) + .AddGCHeapSvr(gc => + { + gc.GCIdentifiers = "server,regions"; + gc.AllocAllocated = allocAllocated; + gc.FillPointers = fillPointers; + gc.ConfigureMemory = b => + { + Layout segLayout = b.GetHeapSegmentLayout(includeHeapField: false); + gen2Seg = b.AddHeapSegment(segLayout, mem: 0x2000_0000, allocated: 0x2000_1000, next: 0, name: "SvrGen2Seg").Address; + gen1Seg = b.AddHeapSegment(segLayout, mem: 0x3000_0000, allocated: 0x3000_1000, next: 0, name: "SvrGen1Seg").Address; + gen0Seg = b.AddHeapSegment(segLayout, mem: 0x4000_0000, allocated: 0x4000_FFFF, next: 0, name: "SvrGen0Seg").Address; + lohSeg = b.AddHeapSegment(segLayout, mem: 0x5000_0000, allocated: 0x5000_1000, next: 0, name: "SvrLohSeg").Address; + pohSeg = b.AddHeapSegment(segLayout, mem: 0x5100_0000, allocated: 0x5100_1000, next: 0, name: "SvrPohSeg").Address; + }; + gc.EphemeralHeapSegment = 0; // set after segments allocated; use post-config + gc.GenerationsFactory = b => MakeGenerations(gen0Seg, 0, gen1Seg, 0, gen2Seg, lohSeg, pohSeg); + }, out _) + .Build(); + + // EphemeralHeapSegment is on the SVR per-heap struct; set it explicitly via the heap pointer. + // For simplicity here we leave it null so the gen0 segment is NOT considered ephemeral and its + // Allocated is emitted as-is. + IGC gc = target.Contracts.GC; + + List captured = new(); + foreach (TargetPointer heapAddr in gc.GetGCHeaps()) + { + foreach (GCHeapSegmentInfo seg in gc.EnumerateHeapSegments(gc.GetHeapData(heapAddr))) + captured.Add(new CapturedSegment(seg.Start, seg.End, seg.Generation)); + } + + // Raw regions-mode walk: gen2, gen1, gen0 (non-ephemeral so end=allocated), LOH, POH. + Assert.Equal(5, captured.Count); + Assert.Equal(new CapturedSegment(0x2000_0000, 0x2000_1000, GCSegmentClassification.Gen2), captured[0]); + Assert.Equal(new CapturedSegment(0x3000_0000, 0x3000_1000, GCSegmentClassification.Gen1), captured[1]); + Assert.Equal(new CapturedSegment(0x4000_0000, 0x4000_FFFF, GCSegmentClassification.Gen0), captured[2]); + Assert.Equal(new CapturedSegment(0x5000_0000, 0x5000_1000, GCSegmentClassification.LOH), captured[3]); + Assert.Equal(new CapturedSegment(0x5100_0000, 0x5100_1000, GCSegmentClassification.POH), captured[4]); + } } /// @@ -131,6 +296,11 @@ internal class GCHeapBuilder public MockGCBuilder.Generation[] Generations { get; set; } = new MockGCBuilder.Generation[DefaultGenerationCount]; public ulong[] FillPointers { get; set; } = new ulong[DefaultGenerationCount + ExtraSegCount]; + public string? GCIdentifiers { get; set; } + public ulong EphemeralHeapSegment { get; set; } + public ulong AllocAllocated { get; set; } + public Action? ConfigureMemory { get; set; } + public Func? GenerationsFactory { get; set; } } internal static class GCHeapBuilderExtensions @@ -165,6 +335,7 @@ public static TestPlaceholderTarget.Builder AddGCHeapSvr( [DataType.Generation] = TargetTestHelpers.CreateTypeInfo(gcBuilder.GenerationLayout), [DataType.CFinalize] = TargetTestHelpers.CreateTypeInfo(gcBuilder.CFinalizeLayout), [DataType.OomHistory] = TargetTestHelpers.CreateTypeInfo(gcBuilder.OomHistoryLayout), + [DataType.HeapSegment] = TargetTestHelpers.CreateTypeInfo(gcBuilder.GetHeapSegmentLayout(includeHeapField: false)), }; private static Dictionary CreateServerContractTypes(MockGCBuilder gcBuilder, uint generationCount) @@ -180,7 +351,9 @@ private static void BuildWksHeap(TestPlaceholderTarget.Builder targetBuilder, GC MockMemorySpace.Builder memBuilder = targetBuilder.MemoryBuilder; MockGCBuilder gcBuilder = new(memBuilder); - MockGCBuilder.Generation[] generations = config.Generations; + config.ConfigureMemory?.Invoke(gcBuilder); + + MockGCBuilder.Generation[] generations = config.GenerationsFactory?.Invoke(gcBuilder) ?? config.Generations; uint generationCount = (uint)generations.Length; ulong[] fillPointers = config.FillPointers; uint fillPointersLength = (uint)fillPointers.Length; @@ -192,6 +365,10 @@ private static void BuildWksHeap(TestPlaceholderTarget.Builder targetBuilder, GC gcBuilder.WritePointerGlobal(gcBuilder.FinalizeQueueGlobalAddress, cFinalize.Address); gcBuilder.WritePointerGlobal(gcBuilder.HighestAddressGlobalAddress, 0xFFFF_0000); gcBuilder.WriteUInt32Global(gcBuilder.MaxGenerationGlobalAddress, generationCount - 1); + if (config.EphemeralHeapSegment != 0) + gcBuilder.WritePointerGlobal(gcBuilder.EphemeralHeapSegmentGlobalAddress, config.EphemeralHeapSegment); + if (config.AllocAllocated != 0) + gcBuilder.WritePointerGlobal(gcBuilder.AllocAllocatedGlobalAddress, config.AllocAllocated); targetBuilder.AddTypes(types); targetBuilder.AddGlobals( @@ -228,7 +405,7 @@ private static void BuildWksHeap(TestPlaceholderTarget.Builder targetBuilder, GC (nameof(Constants.Globals.StructureInvalidCount), gcBuilder.StructureInvalidCountGlobalAddress), (nameof(Constants.Globals.MaxGeneration), gcBuilder.MaxGenerationGlobalAddress)); targetBuilder.AddGlobalStrings( - (nameof(Constants.Globals.GCIdentifiers), "workstation,segments")); + (nameof(Constants.Globals.GCIdentifiers), config.GCIdentifiers ?? "workstation,segments")); } private static ulong BuildSvrHeap(TestPlaceholderTarget.Builder targetBuilder, GCHeapBuilder config) @@ -236,7 +413,9 @@ private static ulong BuildSvrHeap(TestPlaceholderTarget.Builder targetBuilder, G MockMemorySpace.Builder memBuilder = targetBuilder.MemoryBuilder; MockGCBuilder gcBuilder = new(memBuilder); - MockGCBuilder.Generation[] generations = config.Generations; + config.ConfigureMemory?.Invoke(gcBuilder); + + MockGCBuilder.Generation[] generations = config.GenerationsFactory?.Invoke(gcBuilder) ?? config.Generations; uint generationCount = (uint)generations.Length; ulong[] fillPointers = config.FillPointers; uint fillPointersLength = (uint)fillPointers.Length; @@ -244,6 +423,10 @@ private static ulong BuildSvrHeap(TestPlaceholderTarget.Builder targetBuilder, G Dictionary types = CreateServerContractTypes(gcBuilder, generationCount); MockCFinalize cFinalize = gcBuilder.AddCFinalize(fillPointers, "CFinalize_SVR"); MockGCHeapSVR gcHeap = gcBuilder.AddGCHeapSVR(generations, cFinalize.Address); + if (config.EphemeralHeapSegment != 0) + gcHeap.EphemeralHeapSegment = config.EphemeralHeapSegment; + if (config.AllocAllocated != 0) + gcHeap.AllocAllocated = config.AllocAllocated; ulong heapTableAddress = gcBuilder.AddPointerGlobal(gcHeap.Address, "HeapTable"); gcBuilder.WritePointerGlobal(gcBuilder.HeapsGlobalAddress, heapTableAddress); gcBuilder.WritePointerGlobal(gcBuilder.HighestAddressGlobalAddress, 0x7FFF_0000); @@ -269,7 +452,7 @@ private static ulong BuildSvrHeap(TestPlaceholderTarget.Builder targetBuilder, G (nameof(Constants.Globals.StructureInvalidCount), gcBuilder.StructureInvalidCountGlobalAddress), (nameof(Constants.Globals.MaxGeneration), gcBuilder.MaxGenerationGlobalAddress)); targetBuilder.AddGlobalStrings( - (nameof(Constants.Globals.GCIdentifiers), "server,segments")); + (nameof(Constants.Globals.GCIdentifiers), config.GCIdentifiers ?? "server,segments")); return gcHeap.Address; } diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.GC.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.GC.cs index b4ae34dadeb841..b5ece366d68857 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.GC.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.GC.cs @@ -47,6 +47,16 @@ internal static class MockGCFieldNames internal const string GCHeapSvrCompactReasons = "CompactReasons"; internal const string GCHeapSvrExpandMechanisms = "ExpandMechanisms"; internal const string GCHeapSvrInterestingMechanismBits = "InterestingMechanismBits"; + + internal const string HeapSegmentAllocated = "Allocated"; + internal const string HeapSegmentCommitted = "Committed"; + internal const string HeapSegmentReserved = "Reserved"; + internal const string HeapSegmentUsed = "Used"; + internal const string HeapSegmentMem = "Mem"; + internal const string HeapSegmentFlags = "Flags"; + internal const string HeapSegmentNext = "Next"; + internal const string HeapSegmentBackgroundAllocated = "BackgroundAllocated"; + internal const string HeapSegmentHeap = "Heap"; } internal sealed class MockGeneration : TypedView @@ -190,6 +200,18 @@ public ulong FinalizeQueue set => WritePointerField(FinalizeQueueFieldName, value); } + public ulong AllocAllocated + { + get => ReadPointerField(AllocAllocatedFieldName); + set => WritePointerField(AllocAllocatedFieldName, value); + } + + public ulong EphemeralHeapSegment + { + get => ReadPointerField(EphemeralHeapSegmentFieldName); + set => WritePointerField(EphemeralHeapSegmentFieldName, value); + } + public MockGeneration GetGeneration(Layout generationLayout, int index) { LayoutField generationTableField = Layout.GetField(GenerationTableFieldName); @@ -198,6 +220,45 @@ public MockGeneration GetGeneration(Layout generationLayout, int } } +internal sealed class MockHeapSegment : TypedView +{ + private const string AllocatedFieldName = MockGCFieldNames.HeapSegmentAllocated; + private const string CommittedFieldName = MockGCFieldNames.HeapSegmentCommitted; + private const string ReservedFieldName = MockGCFieldNames.HeapSegmentReserved; + private const string UsedFieldName = MockGCFieldNames.HeapSegmentUsed; + private const string MemFieldName = MockGCFieldNames.HeapSegmentMem; + private const string FlagsFieldName = MockGCFieldNames.HeapSegmentFlags; + private const string NextFieldName = MockGCFieldNames.HeapSegmentNext; + private const string BackgroundAllocatedFieldName = MockGCFieldNames.HeapSegmentBackgroundAllocated; + private const string HeapFieldName = MockGCFieldNames.HeapSegmentHeap; + + public static Layout CreateLayout(MockTarget.Architecture architecture, bool includeHeapField) + { + SequentialLayoutBuilder b = new SequentialLayoutBuilder("HeapSegment", architecture) + .AddPointerField(AllocatedFieldName) + .AddPointerField(CommittedFieldName) + .AddPointerField(ReservedFieldName) + .AddPointerField(UsedFieldName) + .AddPointerField(MemFieldName) + .AddNUIntField(FlagsFieldName) + .AddPointerField(NextFieldName) + .AddPointerField(BackgroundAllocatedFieldName); + if (includeHeapField) + b = b.AddPointerField(HeapFieldName); + return b.Build(); + } + + public ulong Allocated { get => ReadPointerField(AllocatedFieldName); set => WritePointerField(AllocatedFieldName, value); } + public ulong Committed { get => ReadPointerField(CommittedFieldName); set => WritePointerField(CommittedFieldName, value); } + public ulong Reserved { get => ReadPointerField(ReservedFieldName); set => WritePointerField(ReservedFieldName, value); } + public ulong Used { get => ReadPointerField(UsedFieldName); set => WritePointerField(UsedFieldName, value); } + public ulong Mem { get => ReadPointerField(MemFieldName); set => WritePointerField(MemFieldName, value); } + public ulong Next { get => ReadPointerField(NextFieldName); set => WritePointerField(NextFieldName, value); } + public ulong BackgroundAllocated { get => ReadPointerField(BackgroundAllocatedFieldName); set => WritePointerField(BackgroundAllocatedFieldName, value); } + public ulong Flags { get => ReadPointerField(FlagsFieldName); set => WritePointerField(FlagsFieldName, value); } + public ulong Heap { get => ReadPointerField(HeapFieldName); set => WritePointerField(HeapFieldName, value); } +} + internal sealed class MockGCBuilder { private const ulong DefaultAllocationRangeStart = 0x0010_0000; @@ -326,6 +387,25 @@ internal MockOomHistory AddOomHistory(string name = "OomHistory") internal Layout GetGCHeapSVRLayout(uint generationCount) => MockGCHeapSVR.CreateLayout(Builder.TargetTestHelpers.Arch, GenerationLayout, OomHistoryLayout, generationCount); + internal Layout GetHeapSegmentLayout(bool includeHeapField) + => MockHeapSegment.CreateLayout(Builder.TargetTestHelpers.Arch, includeHeapField); + + internal MockHeapSegment AddHeapSegment(Layout layout, ulong mem, ulong allocated, ulong next, ulong flags = 0, ulong heap = 0, string name = "HeapSegment") + { + MockHeapSegment seg = Add(layout, name); + seg.Mem = mem; + seg.Allocated = allocated; + seg.Committed = allocated; + seg.Reserved = allocated; + seg.Used = allocated; + seg.BackgroundAllocated = allocated; + seg.Next = next; + seg.Flags = flags; + if (Array.Exists(layout.Fields, f => f.Name == MockGCFieldNames.HeapSegmentHeap)) + seg.Heap = heap; + return seg; + } + internal MockGCHeapSVR AddGCHeapSVR( Generation[] generations, ulong finalizeQueueAddress,