Skip to content

Commit 980c9d1

Browse files
Copilotrcj1Copilot
authored
[cDAC] Reshape DacDbi API GetHeapSegments to EnumerateHeapSegments and implement in cDAC (#128054)
Existing native GetHeapSegments allocates - to avoid this, I reworked it into EnumerateHeapSegments which calls a callback on each segment to add it to a DacDbiArray structure (potentially allocating). There are a bunch of APIs like this. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> Co-authored-by: rcj1 <rachel.jarvi@gmail.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 5262eca commit 980c9d1

13 files changed

Lines changed: 694 additions & 75 deletions

File tree

docs/design/datacontracts/GC.md

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ public readonly struct GCOomData
125125
IReadOnlyList<GCMemoryRegionData> GetGCBookkeepingMemoryRegions();
126126
// Gets GC free regions (free region lists and freeable segments)
127127
IReadOnlyList<GCMemoryRegionData> GetGCFreeRegions();
128+
129+
// Enumerates every GC heap segment for the supplied heap data. Each yielded GCHeapSegmentInfo
130+
// describes a single segment with the inclusive start and exclusive end of its memory range
131+
// and its generation tag (or Ephemeral).
132+
IEnumerable<GCHeapSegmentInfo> EnumerateHeapSegments(GCHeapData heapData);
128133
```
129134

130135
```csharp
@@ -145,6 +150,26 @@ public readonly struct GCMemoryRegionData
145150
public ulong ExtraData { get; init; }
146151
public int Heap { get; init; }
147152
}
153+
154+
public enum GCSegmentClassification
155+
{
156+
Unknown,
157+
Gen0,
158+
Gen1,
159+
Gen2,
160+
LOH,
161+
POH,
162+
NonGC,
163+
// Segments-GC only: marker used by IGC.EnumerateHeapSegments to denote the ephemeral
164+
// segment on the gen2 list. The caller is responsible for splitting it into the Gen1
165+
// piece and an optional Gen2 prefix.
166+
Ephemeral,
167+
}
168+
169+
public readonly record struct GCHeapSegmentInfo(
170+
TargetPointer Start,
171+
TargetPointer End,
172+
GCSegmentClassification Generation);
148173
```
149174

150175
## Version 1
@@ -286,6 +311,7 @@ Constants used:
286311
| Name | Type | Purpose | Value |
287312
| --- | --- | --- | --- |
288313
| `WRK_HEAP_COUNT` | uint | The number of heaps in the `workstation` GC type | `1` |
314+
| `HEAP_SEGMENT_FLAGS_READONLY` | ulong | `HeapSegment.Flags` bit identifying a readonly (e.g. frozen, non-GC) segment. | `1` |
289315

290316
```csharp
291317
GCHeapType IGC.GetGCIdentifiers()
@@ -858,7 +884,7 @@ IReadOnlyList<GCMemoryRegionData> IGC.GetHandleTableMemoryRegions()
858884
// Safety caps matching native DAC
859885
const int MaxHandleTableRegions = 8192;
860886
const int MaxBookkeepingRegions = 32;
861-
const int MaxSegmentListIterations = 2048;
887+
const int MaxSegmentListIterations = 65536;
862888

863889
int maxRegions = MaxHandleTableRegions;
864890
TargetPointer handleTableMap = target.ReadGlobalPointer("HandleTableMap");
@@ -1027,3 +1053,79 @@ TargetNUInt IGC.GetHandleExtraInfo(TargetPointer handle)
10271053
return target.ReadNUInt(extraInfoAddr);
10281054
}
10291055
```
1056+
1057+
EnumerateHeapSegments
1058+
1059+
Returns the raw GC heap segments for a single heap by walking the per-generation segment
1060+
lists.
1061+
```csharp
1062+
IEnumerable<GCHeapSegmentInfo> IGC.EnumerateHeapSegments(GCHeapData heapData)
1063+
{
1064+
// The generation table is laid out as gen0, gen1, gen2, LOH, POH (plus optional extras).
1065+
var gens = heapData.GenerationTable;
1066+
bool regions = GetGCIdentifiers().Contains("regions");
1067+
1068+
TargetPointer ephemeralSegment = heapData.EphemeralHeapSegment;
1069+
TargetPointer allocAllocated = heapData.AllocAllocated;
1070+
1071+
if (regions)
1072+
{
1073+
// In regions mode each generation has its own segment list. Readonly entries on
1074+
// the gen2 list represent non-GC (e.g. frozen) regions and are reported as NonGC.
1075+
foreach (var (seg, _) in WalkSegmentList(gens[2].StartSegment))
1076+
{
1077+
var type = (seg.Flags & HEAP_SEGMENT_FLAGS_READONLY) != 0
1078+
? GCSegmentClassification.NonGC
1079+
: GCSegmentClassification.Gen2;
1080+
yield return new GCHeapSegmentInfo(seg.Mem, seg.Allocated, type);
1081+
}
1082+
foreach (var (seg, _) in WalkSegmentList(gens[1].StartSegment))
1083+
yield return new GCHeapSegmentInfo(seg.Mem, seg.Allocated, GCSegmentClassification.Gen1);
1084+
foreach (var (seg, segAddr) in WalkSegmentList(gens[0].StartSegment))
1085+
{
1086+
// For the gen0 segment that matches the ephemeral_heap_segment, end is alloc_allocated.
1087+
TargetPointer end = segAddr == ephemeralSegment ? allocAllocated : seg.Allocated;
1088+
yield return new GCHeapSegmentInfo(seg.Mem, end, GCSegmentClassification.Gen0);
1089+
}
1090+
}
1091+
else
1092+
{
1093+
// In segments mode the gen2 list contains every SOH segment. The ephemeral
1094+
// segment is tagged Ephemeral as the layer-2 split marker; non-ephemeral entries
1095+
// are reported with their true generation (Gen2 or NonGC for readonly).
1096+
foreach (var (seg, segAddr) in WalkSegmentList(gens[2].StartSegment))
1097+
{
1098+
GCSegmentClassification type;
1099+
if (segAddr == ephemeralSegment)
1100+
type = GCSegmentClassification.Ephemeral;
1101+
else if ((seg.Flags & HEAP_SEGMENT_FLAGS_READONLY) != 0)
1102+
type = GCSegmentClassification.NonGC;
1103+
else
1104+
type = GCSegmentClassification.Gen2;
1105+
TargetPointer end = segAddr == ephemeralSegment ? allocAllocated : seg.Allocated;
1106+
yield return new GCHeapSegmentInfo(seg.Mem, end, type);
1107+
}
1108+
}
1109+
1110+
// LOH and POH segments are always reported as-is regardless of GC mode.
1111+
foreach (var (seg, _) in WalkSegmentList(gens[3].StartSegment))
1112+
yield return new GCHeapSegmentInfo(seg.Mem, seg.Allocated, GCSegmentClassification.LOH);
1113+
foreach (var (seg, _) in WalkSegmentList(gens[4].StartSegment))
1114+
yield return new GCHeapSegmentInfo(seg.Mem, seg.Allocated, GCSegmentClassification.POH);
1115+
}
1116+
1117+
IEnumerable<(HeapSegment Segment, TargetPointer Address)> WalkSegmentList(TargetPointer startSegment)
1118+
{
1119+
// Bounded traversal of the singly-linked HeapSegment list, guarding against cycles or
1120+
// corrupt links via a fixed iteration cap (MaxSegmentListIterations = 65536).
1121+
int iterationMax = MaxSegmentListIterations;
1122+
TargetPointer current = startSegment;
1123+
while (current != TargetPointer.Null)
1124+
{
1125+
HeapSegment seg = /* read HeapSegment at current */;
1126+
yield return (seg, current);
1127+
current = seg.Next;
1128+
if (iterationMax-- <= 0) throw /* cycle detected */;
1129+
}
1130+
}
1131+
```

src/coreclr/debug/daccess/daccess.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7976,7 +7976,7 @@ void DacFreeRegionEnumerator::AddSingleSegment(const dac_heap_segment &curr, Fre
79767976

79777977
void DacFreeRegionEnumerator::AddSegmentList(DPTR(dac_heap_segment) start, FreeRegionKind kind, int heap)
79787978
{
7979-
int iterationMax = 2048;
7979+
int iterationMax = 65536;
79807980

79817981
DPTR(dac_heap_segment) curr = start;
79827982
while (curr != nullptr)

src/coreclr/debug/daccess/dacdbiimpl.cpp

Lines changed: 23 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -6875,10 +6875,14 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::WalkHeap(HeapWalkHandle handle,
68756875

68766876

68776877

6878-
HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetHeapSegments(OUT DacDbiArrayList<COR_SEGMENT> *pSegments)
6878+
HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::EnumerateHeapSegments(
6879+
FP_HEAPSEGMENT_CALLBACK fpCallback,
6880+
CALLBACK_DATA pUserData)
68796881
{
68806882
DD_ENTER_MAY_THROW;
68816883

6884+
if (fpCallback == NULL)
6885+
return E_POINTER;
68826886

68836887
size_t heapCount = 0;
68846888
HeapData *heaps = 0;
@@ -6893,53 +6897,28 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetHeapSegments(OUT DacDbiArrayLi
68936897

68946898
NewArrayHolder<HeapData> _heapHolder = heaps;
68956899

6896-
// Count the number of segments to know how much to allocate.
6897-
int total = 0;
6898-
for (size_t i = 0; i < heapCount; ++i)
6899-
{
6900-
total += (int)heaps[i].SegmentCount;
6901-
if (!region)
6902-
{
6903-
// SegmentCount is +1 due to the ephemeral segment containing more than one
6904-
// generation (Gen1 + Gen0, and sometimes part of Gen2).
6905-
total++;
6906-
6907-
// It's possible that part of Gen2 lives on the ephemeral segment. If so,
6908-
// we need to add one more to the output.
6909-
const size_t eph = heaps[i].EphemeralSegment;
6910-
_ASSERTE(eph < heaps[i].SegmentCount);
6911-
if (heaps[i].Segments[eph].Start != heaps[i].Gen1Start)
6912-
total++;
6913-
}
6914-
}
6915-
6916-
pSegments->Alloc(total);
6900+
if (FAILED(hr))
6901+
return hr;
69176902

6918-
// Now walk all segments and write them to the array.
6919-
int curr = 0;
6903+
// Now walk all segments and emit them through the callback.
69206904
for (size_t i = 0; i < heapCount; ++i)
69216905
{
6922-
_ASSERTE(curr < total);
69236906
if (!region)
69246907
{
69256908
// Generation 0 is not in the segment list.
6926-
COR_SEGMENT &seg = (*pSegments)[curr++];
6927-
seg.start = heaps[i].Gen0Start;
6928-
seg.end = heaps[i].Gen0End;
6929-
seg.type = CorDebug_Gen0;
6930-
seg.heap = (ULONG)i;
6909+
fpCallback(heaps[i].Gen0Start, heaps[i].Gen0End, (int)CorDebug_Gen0, (ULONG)i, pUserData);
69316910
}
69326911

69336912
for (size_t j = 0; j < heaps[i].SegmentCount; ++j)
69346913
{
69356914
if (region)
69366915
{
6937-
_ASSERTE(curr < total);
6938-
COR_SEGMENT &seg = (*pSegments)[curr++];
6939-
seg.start = heaps[i].Segments[j].Start;
6940-
seg.end = heaps[i].Segments[j].End;
6941-
seg.type = (CorDebugGenerationTypes)heaps[i].Segments[j].Generation;
6942-
seg.heap = (ULONG)i;
6916+
fpCallback(
6917+
heaps[i].Segments[j].Start,
6918+
heaps[i].Segments[j].End,
6919+
(int)heaps[i].Segments[j].Generation,
6920+
(ULONG)i,
6921+
pUserData);
69436922
}
69446923
else if (heaps[i].Segments[j].Generation == 1)
69456924
{
@@ -6948,43 +6927,29 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetHeapSegments(OUT DacDbiArrayLi
69486927
_ASSERTE(heaps[i].Segments[j].Start <= heaps[i].Gen1Start);
69496928
_ASSERTE(heaps[i].Segments[j].End > heaps[i].Gen1Start);
69506929

6951-
{
6952-
_ASSERTE(curr < total);
6953-
COR_SEGMENT &seg = (*pSegments)[curr++];
6954-
seg.start = heaps[i].Gen1Start;
6955-
seg.end = heaps[i].Gen0Start;
6956-
seg.type = CorDebug_Gen1;
6957-
seg.heap = (ULONG)i;
6958-
}
6930+
fpCallback(heaps[i].Gen1Start, heaps[i].Gen0Start, (int)CorDebug_Gen1, (ULONG)i, pUserData);
69596931

69606932
// It's possible for Gen2 to take up a portion of the ephemeral segment.
69616933
// We test for that here.
69626934
if (heaps[i].Segments[j].Start != heaps[i].Gen1Start)
69636935
{
6964-
_ASSERTE(curr < total);
6965-
COR_SEGMENT &seg = (*pSegments)[curr++];
6966-
seg.start = heaps[i].Segments[j].Start;
6967-
seg.end = heaps[i].Gen1Start;
6968-
seg.type = CorDebug_Gen2;
6969-
seg.heap = (ULONG)i;
6936+
fpCallback(heaps[i].Segments[j].Start, heaps[i].Gen1Start, (int)CorDebug_Gen2, (ULONG)i, pUserData);
69706937
}
69716938
}
69726939
else
69736940
{
69746941
// Otherwise, we have a gen2, POH, LOH or NonGC
6975-
_ASSERTE(curr < total);
6976-
COR_SEGMENT &seg = (*pSegments)[curr++];
6977-
seg.start = heaps[i].Segments[j].Start;
6978-
seg.end = heaps[i].Segments[j].End;
6979-
69806942
_ASSERTE(heaps[i].Segments[j].Generation <= CorDebug_NonGC);
6981-
seg.type = (CorDebugGenerationTypes)heaps[i].Segments[j].Generation;
6982-
seg.heap = (ULONG)i;
6943+
fpCallback(
6944+
heaps[i].Segments[j].Start,
6945+
heaps[i].Segments[j].End,
6946+
(int)heaps[i].Segments[j].Generation,
6947+
(ULONG)i,
6948+
pUserData);
69836949
}
69846950
}
69856951
}
69866952

6987-
_ASSERTE(total == curr);
69886953
return hr;
69896954
}
69906955

src/coreclr/debug/daccess/dacdbiimpl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ class DacDbiInterfaceImpl :
118118
OUT COR_HEAPOBJECT * objects,
119119
OUT ULONG *fetched);
120120

121-
HRESULT STDMETHODCALLTYPE GetHeapSegments(OUT DacDbiArrayList<COR_SEGMENT> *pSegments);
121+
HRESULT STDMETHODCALLTYPE EnumerateHeapSegments(FP_HEAPSEGMENT_CALLBACK fpCallback, CALLBACK_DATA pUserData);
122122

123123

124124
HRESULT STDMETHODCALLTYPE IsValidObject(CORDB_ADDRESS obj, OUT BOOL * pResult);

src/coreclr/debug/di/process.cpp

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2188,6 +2188,43 @@ HRESULT CordbProcess::GetGCHeapInformation(COR_HEAPINFO *pHeapInfo)
21882188
return hr;
21892189
}
21902190

2191+
namespace
2192+
{
2193+
struct HeapSegmentAccumulator
2194+
{
2195+
CQuickArrayList<COR_SEGMENT> segments;
2196+
HRESULT hrError;
2197+
};
2198+
2199+
void HeapSegmentCallback(
2200+
CORDB_ADDRESS rangeStart,
2201+
CORDB_ADDRESS rangeEnd,
2202+
int generation,
2203+
ULONG heap,
2204+
CALLBACK_DATA pUserData)
2205+
{
2206+
HeapSegmentAccumulator *acc =
2207+
reinterpret_cast<HeapSegmentAccumulator*>(pUserData);
2208+
2209+
if (FAILED(acc->hrError))
2210+
return;
2211+
2212+
COR_SEGMENT s;
2213+
s.start = rangeStart;
2214+
s.end = rangeEnd;
2215+
s.type = (CorDebugGenerationTypes)generation;
2216+
s.heap = heap;
2217+
HRESULT hr = S_OK;
2218+
EX_TRY
2219+
{
2220+
acc->segments.Push(s);
2221+
}
2222+
EX_CATCH_HRESULT(hr);
2223+
if (FAILED(hr))
2224+
acc->hrError = hr;
2225+
}
2226+
}
2227+
21912228
HRESULT CordbProcess::EnumerateHeapRegions(ICorDebugHeapSegmentEnum **ppRegions)
21922229
{
21932230
if (!ppRegions)
@@ -2199,14 +2236,18 @@ HRESULT CordbProcess::EnumerateHeapRegions(ICorDebugHeapSegmentEnum **ppRegions)
21992236

22002237
EX_TRY
22012238
{
2202-
DacDbiArrayList<COR_SEGMENT> segments;
2203-
hr = GetDAC()->GetHeapSegments(&segments);
2239+
HeapSegmentAccumulator acc;
2240+
acc.hrError = S_OK;
2241+
2242+
hr = GetDAC()->EnumerateHeapSegments(&HeapSegmentCallback, &acc);
2243+
if (SUCCEEDED(hr) && FAILED(acc.hrError))
2244+
hr = acc.hrError;
22042245

22052246
if (SUCCEEDED(hr))
22062247
{
2207-
if (!segments.IsEmpty())
2248+
if (acc.segments.Size() != 0)
22082249
{
2209-
CordbHeapSegmentEnumerator *segEnum = new CordbHeapSegmentEnumerator(this, &segments[0], (DWORD)segments.Count());
2250+
CordbHeapSegmentEnumerator *segEnum = new CordbHeapSegmentEnumerator(this, acc.segments.Ptr(), (DWORD)acc.segments.Size());
22102251
GetContinueNeuterList()->Add(this, segEnum);
22112252
hr = segEnum->QueryInterface(__uuidof(ICorDebugHeapSegmentEnum), (void**)ppRegions);
22122253
}

src/coreclr/debug/inc/dacdbiinterface.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1984,7 +1984,23 @@ IDacDbiInterface : public IUnknown
19841984
OUT COR_HEAPOBJECT * objects,
19851985
OUT ULONG * pFetched) = 0;
19861986

1987-
virtual HRESULT STDMETHODCALLTYPE GetHeapSegments(OUT DacDbiArrayList<COR_SEGMENT> * pSegments) = 0;
1987+
// Callback invoked for each GC heap segment.
1988+
//
1989+
// Arguments:
1990+
// rangeStart - start address of the segment (inclusive).
1991+
// rangeEnd - end address of the segment (exclusive).
1992+
// generation - CorDebugGenerationTypes value identifying the generation/kind of the segment.
1993+
// heap - index of the heap the segment belongs to (0 for workstation GC).
1994+
// pUserData - user data passed to EnumerateHeapSegments.
1995+
typedef void (*FP_HEAPSEGMENT_CALLBACK)(CORDB_ADDRESS rangeStart, CORDB_ADDRESS rangeEnd, int generation, ULONG heap, CALLBACK_DATA pUserData);
1996+
1997+
// Enumerate the GC heap segments.
1998+
//
1999+
// The callback is invoked once per segment and must not throw. To accumulate segments, callers
2000+
// typically stash a buffer (and any failure flag) in pUserData. The walker runs to completion
2001+
// regardless of any per-segment failure the callback decides to record; the caller surfaces the
2002+
// recorded failure after EnumerateHeapSegments returns.
2003+
virtual HRESULT STDMETHODCALLTYPE EnumerateHeapSegments(FP_HEAPSEGMENT_CALLBACK fpCallback, CALLBACK_DATA pUserData) = 0;
19882004

19892005
virtual HRESULT STDMETHODCALLTYPE IsValidObject(CORDB_ADDRESS obj, OUT BOOL * pResult) = 0;
19902006

src/coreclr/inc/dacdbi.idl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ typedef void (*FP_ASSEMBLY_ENUMERATION_CALLBACK)(VMPTR_Assembly vmAssembly, CALL
143143
typedef void (*FP_MODULE_ENUMERATION_CALLBACK)(VMPTR_Assembly vmAssembly, CALLBACK_DATA pUserData);
144144
typedef void (*FP_THREAD_ENUMERATION_CALLBACK)(VMPTR_Thread vmThread, CALLBACK_DATA pUserData);
145145
typedef BOOL (*FP_INTERNAL_FRAME_ENUMERATION_CALLBACK)(FramePointer fpFrame, CALLBACK_DATA pUserData);
146+
typedef void (*FP_HEAPSEGMENT_CALLBACK)(CORDB_ADDRESS rangeStart, CORDB_ADDRESS rangeEnd, int generation, ULONG heap, CALLBACK_DATA pUserData);
146147

147148

148149
//
@@ -378,7 +379,7 @@ interface IDacDbiInterface : IUnknown
378379
HRESULT CreateHeapWalk([out] HeapWalkHandle * pHandle);
379380
HRESULT DeleteHeapWalk([in] HeapWalkHandle handle);
380381
HRESULT WalkHeap([in] HeapWalkHandle handle, [in] ULONG count, [out] COR_HEAPOBJECT * objects, [out] ULONG * pFetched);
381-
HRESULT GetHeapSegments([out] DacDbiArrayList_COR_SEGMENT * pSegments);
382+
HRESULT EnumerateHeapSegments([in] FP_HEAPSEGMENT_CALLBACK fpCallback, [in] CALLBACK_DATA pUserData);
382383
HRESULT IsValidObject([in] CORDB_ADDRESS obj, [out] BOOL * pResult);
383384

384385
// Reference Walking

0 commit comments

Comments
 (0)