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
58 changes: 58 additions & 0 deletions docs/design/datacontracts/GC.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,17 @@ public readonly struct GCOomData
// describes a single segment with the inclusive start and exclusive end of its memory range
// and its generation tag (or Ephemeral).
IEnumerable<GCHeapSegmentInfo> EnumerateHeapSegments(GCHeapData heapData);

// Given the current probe address within a heap segment and the (aligned) size of the
// object that lives at that address, returns the next candidate object address.
// Implementations may consult cached per-target allocation-context state.
TargetPointer GetPotentialNextObjectAddress(
TargetPointer currentAddress,
ulong currentObjectSize,
GCHeapSegmentInfo segment);

// Aligns an object's raw size (base size + component bytes) to the alignment required by its containing segment
ulong AlignObjectSize(ulong size, GCSegmentClassification generation);
Comment thread
rcj1 marked this conversation as resolved.
```

```csharp
Expand Down Expand Up @@ -306,6 +317,7 @@ Contracts used:
| --- |
| BuiltInCOM |
| Object |
| Thread |

Constants used:
| Name | Type | Purpose | Value |
Expand Down Expand Up @@ -1129,3 +1141,49 @@ IEnumerable<(HeapSegment Segment, TargetPointer Address)> WalkSegmentList(Target
}
}
```

GetPotentialNextObjectAddress

Computes the next candidate object address when walking a Gen0/Ephemeral segment.
Active allocation contexts (per-thread, the global non-thread-local context, and
the per-heap Gen0 context) carve out reserved-but-not-yet-allocated ranges inside
such segments; when the naive `current + size` lands on one of those ranges the
walk must skip past it. The contexts are collected via `IThread.GetThreadStoreData`
and `IThread.GetThreadData` (per-thread contexts), `IGC.GetGlobalAllocationContext`
(global context), and `IGC.GetGCIdentifiers` + `IGC.GetGCHeaps` + `IGC.GetHeapData`
(per-heap Gen0 contexts).

```csharp
TargetPointer IGC.GetPotentialNextObjectAddress(
TargetPointer currentAddress,
ulong currentObjectSize,
GCHeapSegmentInfo segment)
{
Comment thread
rcj1 marked this conversation as resolved.
TargetPointer next = new TargetPointer(currentAddress.Value + currentObjectSize);

if (segment.Generation is not (GCSegmentClassification.Gen0 or GCSegmentClassification.Ephemeral))
return next;

ulong minObjSize = AlignForSmallObject((ulong)_target.PointerSize * 3);
foreach (/* context in allocation contexts */ )
{
if (next == /* context pointer */)
return new TargetPointer(/* context limit */ + minObjSize);
}
return next;
}
Comment thread
rcj1 marked this conversation as resolved.
```

AlignObjectSize

Aligns a raw object size to the alignment required by its containing segment. SOH segments
use pointer-sized alignment (`Align()`); LOH/POH use 8-byte alignment (`AlignLarge()`).

```csharp
ulong IGC.AlignObjectSize(ulong size, GCSegmentClassification generation)
{
return generation is GCSegmentClassification.LOH or GCSegmentClassification.POH
? AlignForLargeObject(size) // (size + 7) & ~7
: AlignForSmallObject(size); // pointer-sized alignment
}
```
92 changes: 44 additions & 48 deletions src/coreclr/debug/daccess/dacdbiimpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6334,43 +6334,24 @@ HRESULT DacHeapWalker::Next(CORDB_ADDRESS *pValue, CORDB_ADDRESS *pMT, ULONG64 *
if (pSize)
*pSize = (ULONG64)mCurrSize;

HRESULT hr = MoveToNextObject();
return FAILED(hr) ? hr : S_OK;
}



HRESULT DacHeapWalker::MoveToNextObject()
{
do
{
// Move to the next object
mCurrObj += mCurrSize;
mCurrObj += mCurrSize;

// Check to see if we are in the correct bounds.
bool isGen0 = IsRegionGCEnabled() ? (mHeaps[mCurrHeap].Segments[mCurrSeg].Generation == 0) :
bool isGen0 = IsRegionGCEnabled() ? (mHeaps[mCurrHeap].Segments[mCurrSeg].Generation == 0) :
(mHeaps[mCurrHeap].Gen0Start <= mCurrObj && mHeaps[mCurrHeap].Gen0End > mCurrObj);
if (isGen0)
CheckAllocAndSegmentRange();

if (isGen0)
CheckAllocAndSegmentRange();

// Check to see if we've moved off the end of a segment
if (mCurrObj >= mHeaps[mCurrHeap].Segments[mCurrSeg].End || mCurrObj > mEnd)
{
HRESULT hr = NextSegment();
if (FAILED(hr) || hr == S_FALSE)
return hr;
}

// Get the method table pointer
if (!mCache.ReadMT(mCurrObj, &mCurrMT))
return E_FAIL;
if (mCurrObj >= mHeaps[mCurrHeap].Segments[mCurrSeg].End || mCurrObj > mEnd)
{
return AdvanceToNextValidObject();
}

if (!GetSize(mCurrMT, mCurrSize))
return E_FAIL;
} while (mCurrObj < mStart);
if (!mCache.ReadMT(mCurrObj, &mCurrMT) || !GetSize(mCurrMT, mCurrSize))
{
(void)AdvanceToNextValidObject();
return E_FAIL;
}
Comment thread
rcj1 marked this conversation as resolved.
Comment thread
rcj1 marked this conversation as resolved.

_ASSERTE(mStart <= mCurrObj && mCurrObj <= mEnd);
return S_OK;
}

Expand Down Expand Up @@ -6421,14 +6402,17 @@ bool DacHeapWalker::GetSize(TADDR tMT, size_t &size)
}


HRESULT DacHeapWalker::NextSegment()
HRESULT DacHeapWalker::AdvanceToNextValidObject()
{
HRESULT hr = S_OK;
mCurrObj = 0;
mCurrMT = 0;
mCurrSize = 0;

bool gotValidStart = false;
do
{
// If we run out of segments entirely, we're done.
do
{
mCurrSeg++;
Expand All @@ -6439,11 +6423,20 @@ HRESULT DacHeapWalker::NextSegment()

if (mCurrHeap >= mHeapCount)
{
return S_FALSE;
// If we accumulated an E_FAIL while trying
// to skip a corrupt segment, propagate it so the caller
// knows the walk didn't end cleanly.
return (hr == S_OK) ? S_FALSE : hr;
}
}
} while (mHeaps[mCurrHeap].Segments[mCurrSeg].Start >= mHeaps[mCurrHeap].Segments[mCurrSeg].End);

if ((mHeaps[mCurrHeap].Segments[mCurrSeg].Start > mEnd)
|| (mHeaps[mCurrHeap].Segments[mCurrSeg].End < mStart))
{
continue;
}

mCurrObj = mHeaps[mCurrHeap].Segments[mCurrSeg].Start;

bool isGen0 = IsRegionGCEnabled() ? (mHeaps[mCurrHeap].Segments[mCurrSeg].Generation == 0) :
Expand All @@ -6452,18 +6445,19 @@ HRESULT DacHeapWalker::NextSegment()
if (isGen0)
CheckAllocAndSegmentRange();

if (!mCache.ReadMT(mCurrObj, &mCurrMT))
{
return E_FAIL;
}

if (!GetSize(mCurrMT, mCurrSize))
gotValidStart = mCache.ReadMT(mCurrObj, &mCurrMT) && GetSize(mCurrMT, mCurrSize);
if (!gotValidStart)
{
return E_FAIL;
mCurrObj = 0;
mCurrMT = 0;
mCurrSize = 0;
// Remember that we skipped over corruption so callers can distinguish
// "advanced cleanly" (S_OK) from "advanced past a target-read failure.
hr = E_FAIL;
}
} while((mHeaps[mCurrHeap].Segments[mCurrSeg].Start > mEnd) || (mHeaps[mCurrHeap].Segments[mCurrSeg].End < mStart));
} while (!gotValidStart);

return S_OK;
return hr;
}

void DacHeapWalker::CheckAllocAndSegmentRange()
Expand Down Expand Up @@ -6522,6 +6516,7 @@ HRESULT DacHeapWalker::Init(CORDB_ADDRESS start, CORDB_ADDRESS end)
{
mAllocInfo[j].Ptr = (CORDB_ADDRESS)globalCtx.alloc_ptr;
mAllocInfo[j].Limit = (CORDB_ADDRESS)globalCtx.alloc_limit;
j++;
}

mThreadCount = j;
Expand Down Expand Up @@ -6562,7 +6557,7 @@ HRESULT DacHeapWalker::Reset(CORDB_ADDRESS start, CORDB_ADDRESS end)

// it's possible the first segment is empty
if (mCurrObj >= mHeaps[0].Segments[0].End)
hr = MoveToNextObject();
hr = AdvanceToNextValidObject();

if (!mCache.ReadMT(mCurrObj, &mCurrMT))
return E_FAIL;
Expand All @@ -6571,7 +6566,7 @@ HRESULT DacHeapWalker::Reset(CORDB_ADDRESS start, CORDB_ADDRESS end)
return E_FAIL;

if (mCurrObj < mStart || mCurrObj > mEnd)
hr = MoveToNextObject();
hr = AdvanceToNextValidObject();

return hr;
}
Expand Down Expand Up @@ -6826,9 +6821,7 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::WalkHeap(HeapWalkHandle handle,
{
hr = walk->Next(&addr, &mt, &size);

if (FAILED(hr))
break;

// On a FAILED return Next() still has a valid prior object.
if (mt != freeMT)
{
objects[i].address = addr;
Expand All @@ -6837,6 +6830,9 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::WalkHeap(HeapWalkHandle handle,
objects[i].size = size;
i++;
}

if (FAILED(hr))
break;
}

if (SUCCEEDED(hr))
Expand Down
4 changes: 1 addition & 3 deletions src/coreclr/debug/daccess/dacimpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1765,8 +1765,6 @@ class DacHeapWalker
HRESULT ListNearObjects(CORDB_ADDRESS obj, CORDB_ADDRESS *pPrev, CORDB_ADDRESS *pContaining, CORDB_ADDRESS *pNext);

private:
HRESULT MoveToNextObject();

bool GetSize(TADDR tMT, size_t &size);

inline static size_t Align(size_t size)
Expand Down Expand Up @@ -1795,7 +1793,7 @@ class DacHeapWalker
return count;
}

HRESULT NextSegment();
HRESULT AdvanceToNextValidObject();
void CheckAllocAndSegmentRange();

private:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,15 @@ public interface IGC : IContract
// Enumerates the raw GC heap segments for a single heap as recorded by the GC's per-generation
// segment lists.
IEnumerable<GCHeapSegmentInfo> EnumerateHeapSegments(GCHeapData heapData) => throw new NotImplementedException();

// Returns the next candidate object address within a heap segment.
TargetPointer GetPotentialNextObjectAddress(
TargetPointer currentAddress,
ulong currentObjectSize,
Comment thread
rcj1 marked this conversation as resolved.
GCHeapSegmentInfo segment) => throw new NotImplementedException();

// Aligns an object's raw size to the alignment required by its containing segment.
ulong AlignObjectSize(ulong size, GCSegmentClassification generation) => throw new NotImplementedException();
Comment thread
rcj1 marked this conversation as resolved.
Comment thread
rcj1 marked this conversation as resolved.
Comment thread
rcj1 marked this conversation as resolved.
}

public readonly struct GC : IGC
Expand Down
Loading
Loading