|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.Diagnostics.DataContractReader.Contracts.GCHelpers;
namespace Microsoft.Diagnostics.DataContractReader.Contracts;
internal readonly struct GC_1 : IGC
{
private const uint WRK_HEAP_COUNT = 1;
// 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 = 65536;
private enum GCType
{
Unknown,
Workstation,
Server,
}
private enum HandleType_1
{
WeakShort = 0,
WeakLong = 1,
Strong = 2,
Pinned = 3,
RefCounted = 5,
Dependent = 6,
WeakInteriorPointer = 10,
CrossReference = 11
}
private readonly Target _target;
private readonly uint _handlesPerBlock;
private readonly byte _blockInvalid;
private readonly TargetPointer _debugDestroyedHandleValue;
private readonly uint _handleMaxInternalTypes;
private readonly uint _handleSegmentSize;
private readonly uint _heapSegmentFlagsReadonly = 1;
internal GC_1(Target target)
{
_target = target;
_handlesPerBlock = target.ReadGlobal<uint>(Constants.Globals.HandlesPerBlock);
_blockInvalid = target.ReadGlobal<byte>(Constants.Globals.BlockInvalid);
_debugDestroyedHandleValue = target.ReadGlobalPointer(Constants.Globals.DebugDestroyedHandleValue);
_handleMaxInternalTypes = target.ReadGlobal<uint>(Constants.Globals.HandleMaxInternalTypes);
_handleSegmentSize = target.ReadGlobal<uint>(Constants.Globals.HandleSegmentSize);
}
string[] IGC.GetGCIdentifiers()
{
string gcIdentifiers = _target.ReadGlobalString(Constants.Globals.GCIdentifiers);
return gcIdentifiers.Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
}
uint IGC.GetGCHeapCount()
{
switch (GetGCType())
{
case GCType.Workstation:
return WRK_HEAP_COUNT; // Workstation GC has a single heap
case GCType.Server:
TargetPointer pNumHeaps = _target.ReadGlobalPointer(Constants.Globals.NumHeaps);
return (uint)_target.Read<int>(pNumHeaps);
default:
throw new NotImplementedException("Unknown GC type");
}
}
bool IGC.GetGCStructuresValid()
{
TargetPointer pInvalidCount = _target.ReadGlobalPointer(Constants.Globals.StructureInvalidCount);
int invalidCount = _target.Read<int>(pInvalidCount);
return invalidCount == 0; // Structures are valid if the count of invalid structures is zero
}
uint IGC.GetMaxGeneration()
{
TargetPointer pMaxGeneration = _target.ReadGlobalPointer(Constants.Globals.MaxGeneration);
return _target.Read<uint>(pMaxGeneration);
}
void IGC.GetGCBounds(out TargetPointer minAddr, out TargetPointer maxAddr)
{
minAddr = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.GCLowestAddress));
maxAddr = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.GCHighestAddress));
}
uint IGC.GetCurrentGCState()
{
if (!IsBackgroundGCEnabled())
return 0;
return _target.Read<uint>(_target.ReadGlobalPointer(Constants.Globals.CurrentGCState));
}
bool IGC.TryGetGCDynamicAdaptationMode(out int mode)
{
mode = default;
if (!IsDatasEnabled())
return false;
mode = _target.Read<int>(_target.ReadGlobalPointer(Constants.Globals.DynamicAdaptationMode));
return true;
}
GCHeapSegmentData IGC.GetHeapSegmentData(TargetPointer segmentAddress)
{
Data.HeapSegment heapSegment = _target.ProcessedData.GetOrAdd<Data.HeapSegment>(segmentAddress);
return new GCHeapSegmentData()
{
Allocated = heapSegment.Allocated,
Committed = heapSegment.Committed,
Reserved = heapSegment.Reserved,
Used = heapSegment.Used,
Mem = heapSegment.Mem,
Flags = heapSegment.Flags,
Next = heapSegment.Next,
BackgroundAllocated = heapSegment.BackgroundAllocated,
Heap = heapSegment.Heap ?? TargetPointer.Null,
};
}
IReadOnlyList<TargetNUInt> IGC.GetGlobalMechanisms()
{
if (!_target.TryReadGlobalPointer(Constants.Globals.GCGlobalMechanisms, out TargetPointer? globalMechanismsArrayStart))
return Array.Empty<TargetNUInt>();
uint globalMechanismsLength = _target.ReadGlobal<uint>(Constants.Globals.GlobalMechanismsLength);
return ReadGCHeapDataArray(globalMechanismsArrayStart.Value, globalMechanismsLength);
}
IEnumerable<TargetPointer> IGC.GetGCHeaps()
{
if (GetGCType() != GCType.Server)
yield break; // Only server GC has multiple heaps
uint heapCount = ((IGC)this).GetGCHeapCount();
TargetPointer heapTable = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.Heaps));
for (uint i = 0; i < heapCount; i++)
{
yield return _target.ReadPointer(heapTable + (i * (uint)_target.PointerSize));
}
}
GCHeapData IGC.GetHeapData()
{
if (GetGCType() != GCType.Workstation)
throw new InvalidOperationException("GetHeapData() is only valid for Workstation GC.");
return GetGCHeapDataFromHeap(new GCHeapWKS(_target));
}
GCHeapData IGC.GetHeapData(TargetPointer heapAddress)
{
if (GetGCType() != GCType.Server)
throw new InvalidOperationException("GetHeapData(TargetPointer heap) is only valid for Server GC.");
Data.GCHeapSVR heap = _target.ProcessedData.GetOrAdd<Data.GCHeapSVR>(heapAddress);
return GetGCHeapDataFromHeap(heap);
}
private GCHeapData GetGCHeapDataFromHeap(IGCHeap heap)
{
Data.CFinalize finalize = _target.ProcessedData.GetOrAdd<Data.CFinalize>(heap.FinalizeQueue);
return new GCHeapData()
{
MarkArray = heap.MarkArray ?? TargetPointer.Null,
NextSweepObject = heap.NextSweepObj ?? TargetPointer.Null,
BackGroundSavedMinAddress = heap.BackgroundMinSavedAddr ?? TargetPointer.Null,
BackGroundSavedMaxAddress = heap.BackgroundMaxSavedAddr ?? TargetPointer.Null,
AllocAllocated = heap.AllocAllocated,
EphemeralHeapSegment = heap.EphemeralHeapSegment,
CardTable = heap.CardTable,
GenerationTable = GetGenerationData(heap.GenerationTable).AsReadOnly(),
FillPointers = GetFillPointers(finalize).AsReadOnly(),
SavedSweepEphemeralSegment = heap.SavedSweepEphemeralSeg ?? TargetPointer.Null,
SavedSweepEphemeralStart = heap.SavedSweepEphemeralStart ?? TargetPointer.Null,
InternalRootArray = heap.InternalRootArray,
InternalRootArrayIndex = heap.InternalRootArrayIndex,
HeapAnalyzeSuccess = heap.HeapAnalyzeSuccess,
InterestingData = ReadGCHeapDataArray(
heap.InterestingData,
_target.ReadGlobal<uint>(Constants.Globals.InterestingDataLength))
.AsReadOnly(),
CompactReasons = ReadGCHeapDataArray(
heap.CompactReasons,
_target.ReadGlobal<uint>(Constants.Globals.CompactReasonsLength))
.AsReadOnly(),
ExpandMechanisms = ReadGCHeapDataArray(
heap.ExpandMechanisms,
_target.ReadGlobal<uint>(Constants.Globals.ExpandMechanismsLength))
.AsReadOnly(),
InterestingMechanismBits = ReadGCHeapDataArray(
heap.InterestingMechanismBits,
_target.ReadGlobal<uint>(Constants.Globals.InterestingMechanismBitsLength))
.AsReadOnly(),
};
}
private List<GCGenerationData> GetGenerationData(TargetPointer generationTableArrayStart)
{
uint generationTableLength = _target.ReadGlobal<uint>(Constants.Globals.TotalGenerationCount);
uint generationSize = _target.GetTypeInfo(DataType.Generation).Size ?? throw new InvalidOperationException("Type Generation has no size");
List<Data.Generation> generationTable = [];
for (uint i = 0; i < generationTableLength; i++)
{
TargetPointer generationAddress = generationTableArrayStart + i * generationSize;
generationTable.Add(_target.ProcessedData.GetOrAdd<Data.Generation>(generationAddress));
}
List<GCGenerationData> generationDataList = generationTable.Select(gen =>
new GCGenerationData()
{
StartSegment = gen.StartSegment,
AllocationStart = gen.AllocationStart ?? 0,
AllocationContextPointer = gen.AllocationContext.Pointer,
AllocationContextLimit = gen.AllocationContext.Limit,
}).ToList();
return generationDataList;
}
private List<TargetPointer> GetFillPointers(Data.CFinalize cFinalize)
{
uint fillPointersLength = _target.ReadGlobal<uint>(Constants.Globals.CFinalizeFillPointersLength);
TargetPointer fillPointersArrayStart = cFinalize.FillPointers;
List<TargetPointer> fillPointers = [];
for (uint i = 0; i < fillPointersLength; i++)
fillPointers.Add(_target.ReadPointer(fillPointersArrayStart + i * (uint)_target.PointerSize));
return fillPointers;
}
private List<TargetNUInt> ReadGCHeapDataArray(TargetPointer arrayStart, uint length)
{
List<TargetNUInt> arr = [];
for (uint i = 0; i < length; i++)
arr.Add(_target.ReadNUInt(arrayStart + (i * (uint)_target.PointerSize)));
return arr;
}
GCOomData IGC.GetOomData()
{
if (GetGCType() != GCType.Workstation)
throw new InvalidOperationException("GetOomData() is only valid for Workstation GC.");
TargetPointer oomHistory = _target.ReadGlobalPointer(Constants.Globals.GCHeapOomData);
Data.OomHistory oomHistoryData = _target.ProcessedData.GetOrAdd<Data.OomHistory>(oomHistory);
return GetGCOomData(oomHistoryData);
}
GCOomData IGC.GetOomData(TargetPointer heapAddress)
{
if (GetGCType() != GCType.Server)
throw new InvalidOperationException("GetOomData(TargetPointer heap) is only valid for Server GC.");
Data.GCHeapSVR heap = _target.ProcessedData.GetOrAdd<Data.GCHeapSVR>(heapAddress);
return GetGCOomData(heap.OomData);
}
private static GCOomData GetGCOomData(Data.OomHistory oomHistory)
=> new GCOomData()
{
Reason = oomHistory.Reason,
AllocSize = oomHistory.AllocSize,
Reserved = oomHistory.Reserved,
Allocated = oomHistory.Allocated,
GCIndex = oomHistory.GcIndex,
Fgm = oomHistory.Fgm,
Size = oomHistory.Size,
AvailablePagefileMB = oomHistory.AvailablePagefileMb,
LohP = oomHistory.LohP != 0,
};
void IGC.GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointer allocLimit)
{
TargetPointer globalAllocContextAddress = _target.ReadGlobalPointer(Constants.Globals.GlobalAllocContext);
Data.EEAllocContext eeAllocContext = _target.ProcessedData.GetOrAdd<Data.EEAllocContext>(globalAllocContextAddress);
allocPtr = eeAllocContext.GCAllocationContext.Pointer;
allocLimit = eeAllocContext.GCAllocationContext.Limit;
}
private GCType GetGCType()
{
string[] identifiers = ((IGC)this).GetGCIdentifiers();
if (identifiers.Contains(GCIdentifiers.Workstation))
{
return GCType.Workstation;
}
else if (identifiers.Contains(GCIdentifiers.Server))
{
return GCType.Server;
}
else
{
return GCType.Unknown; // Unknown or unsupported GC type
}
}
private bool IsBackgroundGCEnabled()
{
string[] identifiers = ((IGC)this).GetGCIdentifiers();
return identifiers.Contains(GCIdentifiers.Background);
}
private bool IsDatasEnabled()
{
string[] identifiers = ((IGC)this).GetGCIdentifiers();
return identifiers.Contains(GCIdentifiers.DynamicHeapCount);
}
List<HandleData> IGC.GetHandles(HandleType[] types)
{
List<HandleType> typesList = types.ToList();
typesList.Sort();
List<HandleData> handles = new();
TargetPointer handleTableMap = _target.ReadGlobalPointer(Constants.Globals.HandleTableMap);
GCType gcType = GetGCType();
uint tableCount = gcType switch
{
GCType.Workstation => 1,
GCType.Server => _target.Read<uint>(_target.ReadGlobalPointer(Constants.Globals.TotalCpuCount)),
_ => 0 // unknown
};
while (handleTableMap != TargetPointer.Null)
{
Data.HandleTableMap handleTableData = _target.ProcessedData.GetOrAdd<Data.HandleTableMap>(handleTableMap);
foreach (TargetPointer bucketPtr in handleTableData.BucketsPtr)
{
if (bucketPtr == TargetPointer.Null)
continue;
Data.HandleTableBucket bucket = _target.ProcessedData.GetOrAdd<Data.HandleTableBucket>(bucketPtr);
for (uint j = 0; j < tableCount; j++)
{
TargetPointer handleTablePtr = _target.ReadPointer(bucket.Table + (ulong)(j * _target.PointerSize));
if (handleTablePtr == TargetPointer.Null)
continue;
Data.HandleTable handleTable = _target.ProcessedData.GetOrAdd<Data.HandleTable>(handleTablePtr);
if (handleTable.SegmentList == TargetPointer.Null)
continue;
foreach (HandleType type in typesList)
{
TargetPointer segmentPtr = handleTable.SegmentList;
do
{
Data.TableSegment tableSegment = _target.ProcessedData.GetOrAdd<Data.TableSegment>(segmentPtr);
segmentPtr = tableSegment.NextSegment;
GetHandlesForSegment(tableSegment, type, handles);
} while (segmentPtr != TargetPointer.Null);
}
}
}
handleTableMap = handleTableData.Next;
}
return handles;
}
IEnumerable<GCHeapSegmentInfo> IGC.EnumerateHeapSegments(GCHeapData heapData)
{
// The generation table is laid out as gen0, gen1, gen2, LOH, POH (plus optional extras).
IReadOnlyList<GCGenerationData> 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<Data.HeapSegment>(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<HandleType> supportedTypes =
[
HandleType.WeakShort,
HandleType.WeakLong,
HandleType.Strong,
HandleType.Pinned,
HandleType.Dependent,
HandleType.WeakInteriorPointer
];
if (_target.ReadGlobal<byte>(Constants.Globals.FeatureCOMInterop) != 0 || _target.ReadGlobal<byte>(Constants.Globals.FeatureComWrappers) != 0 || _target.ReadGlobal<byte>(Constants.Globals.FeatureObjCMarshal) != 0)
{
supportedTypes.Add(HandleType.RefCounted);
}
if (_target.ReadGlobal<byte>(Constants.Globals.FeatureJavaMarshal) != 0)
{
supportedTypes.Add(HandleType.CrossReference);
}
return supportedTypes.ToArray();
}
HandleType[] IGC.GetHandleTypes(uint[] types)
{
List<HandleType> handleTypes = new();
foreach (uint type in types)
{
if (type >= _handleMaxInternalTypes)
continue;
HandleType? mappedType = type switch
{
(uint)HandleType_1.WeakShort => HandleType.WeakShort,
(uint)HandleType_1.WeakLong => HandleType.WeakLong,
(uint)HandleType_1.Strong => HandleType.Strong,
(uint)HandleType_1.Pinned => HandleType.Pinned,
(uint)HandleType_1.RefCounted => HandleType.RefCounted,
(uint)HandleType_1.Dependent => HandleType.Dependent,
(uint)HandleType_1.WeakInteriorPointer => HandleType.WeakInteriorPointer,
(uint)HandleType_1.CrossReference => HandleType.CrossReference,
_ => null,
};
if (mappedType is HandleType concreteType)
{
handleTypes.Add(concreteType);
}
}
return handleTypes.ToArray();
}
private static uint GetInternalHandleType(HandleType type)
{
return type switch
{
HandleType.WeakShort => (uint)HandleType_1.WeakShort,
HandleType.WeakLong => (uint)HandleType_1.WeakLong,
HandleType.Strong => (uint)HandleType_1.Strong,
HandleType.Pinned => (uint)HandleType_1.Pinned,
HandleType.Dependent => (uint)HandleType_1.Dependent,
HandleType.WeakInteriorPointer => (uint)HandleType_1.WeakInteriorPointer,
HandleType.RefCounted => (uint)HandleType_1.RefCounted,
HandleType.CrossReference => (uint)HandleType_1.CrossReference,
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};
}
private void GetHandlesForSegment(Data.TableSegment tableSegment, HandleType type, List<HandleData> handles)
{
Debug.Assert(GetInternalHandleType(type) < _handleMaxInternalTypes);
byte uBlock = tableSegment.RgTail[(int)GetInternalHandleType(type)];
if (uBlock == _blockInvalid)
return;
uBlock = tableSegment.RgAllocation[uBlock];
byte uHead = uBlock;
// for each block in the segment for the given handle type
do
{
GetHandlesForBlock(tableSegment, uBlock, type, handles);
uBlock = tableSegment.RgAllocation[uBlock];
} while (uBlock != uHead);
}
private void GetHandlesForBlock(Data.TableSegment tableSegment, byte uBlock, HandleType type, List<HandleData> handles)
{
for (uint k = 0; k < _handlesPerBlock; k++)
{
uint offset = uBlock * _handlesPerBlock + k;
TargetPointer handleAddress = tableSegment.RgValue + offset * (uint)_target.PointerSize;
TargetPointer handle = _target.ReadPointer(handleAddress);
if (handle == TargetPointer.Null || handle == _debugDestroyedHandleValue)
continue;
handles.Add(CreateHandleData(handleAddress, uBlock, k, tableSegment, type));
}
}
private static bool IsStrongReference(HandleType type) => type == HandleType.Strong || type == HandleType.Pinned;
private static bool HasSecondary(HandleType type) => type == HandleType.Dependent || type == HandleType.WeakInteriorPointer || type == HandleType.CrossReference;
private static bool IsRefCounted(HandleType type) => type == HandleType.RefCounted;
private HandleData CreateHandleData(TargetPointer handleAddress, byte uBlock, uint intraBlockIndex, Data.TableSegment tableSegment, HandleType type)
{
HandleData handleData = default;
handleData.Handle = handleAddress;
handleData.Type = GetInternalHandleType(type);
handleData.JupiterRefCount = 0;
handleData.IsPegged = false;
handleData.StrongReference = IsStrongReference(type);
if (HasSecondary(type))
{
byte blockIndex = tableSegment.RgUserData[uBlock];
if (blockIndex == _blockInvalid)
handleData.Secondary = 0;
else
{
uint offset = blockIndex * _handlesPerBlock + intraBlockIndex;
handleData.Secondary = _target.ReadPointer(tableSegment.RgValue + offset * (uint)_target.PointerSize);
}
}
else
{
handleData.Secondary = 0;
}
if (_target.ReadGlobal<byte>(Constants.Globals.FeatureCOMInterop) != 0 && IsRefCounted(type))
{
IObject obj = _target.Contracts.Object;
TargetPointer handle = _target.ReadPointer(handleAddress);
obj.GetBuiltInComData(handle, out _, out TargetPointer ccw, out _);
if (ccw != TargetPointer.Null)
{
IBuiltInCOM builtInCOM = _target.Contracts.BuiltInCOM;
SimpleComCallWrapperData sccwData = builtInCOM.GetSimpleComCallWrapperData(ccw);
handleData.RefCount = (uint)sccwData.RefCount;
handleData.StrongReference = handleData.StrongReference || (handleData.RefCount > 0 && !sccwData.IsHandleWeak);
}
}
return handleData;
}
IReadOnlyList<GCMemoryRegionData> IGC.GetHandleTableMemoryRegions()
{
List<GCMemoryRegionData> regions = new();
uint handleSegmentSize = _handleSegmentSize;
uint tableCount = GetGCType() switch
{
GCType.Workstation => 1,
GCType.Server => _target.Read<uint>(_target.ReadGlobalPointer(Constants.Globals.TotalCpuCount)),
_ => 0
};
int maxRegions = MaxHandleTableRegions;
TargetPointer handleTableMap = _target.ReadGlobalPointer(Constants.Globals.HandleTableMap);
while (handleTableMap != TargetPointer.Null && maxRegions > 0)
{
Data.HandleTableMap map = _target.ProcessedData.GetOrAdd<Data.HandleTableMap>(handleTableMap);
foreach (TargetPointer bucketPtr in map.BucketsPtr)
{
if (bucketPtr == TargetPointer.Null)
continue;
Data.HandleTableBucket bucket = _target.ProcessedData.GetOrAdd<Data.HandleTableBucket>(bucketPtr);
for (uint j = 0; j < tableCount; j++)
{
TargetPointer handleTablePtr = _target.ReadPointer(bucket.Table + (ulong)(j * _target.PointerSize));
if (handleTablePtr == TargetPointer.Null)
continue;
Data.HandleTable handleTable = _target.ProcessedData.GetOrAdd<Data.HandleTable>(handleTablePtr);
if (handleTable.SegmentList == TargetPointer.Null)
continue;
TargetPointer segmentPtr = handleTable.SegmentList;
TargetPointer firstSegment = segmentPtr;
int segmentIterations = MaxSegmentListIterations;
do
{
Data.TableSegment segment = _target.ProcessedData.GetOrAdd<Data.TableSegment>(segmentPtr);
regions.Add(new GCMemoryRegionData
{
Start = segmentPtr,
Size = handleSegmentSize,
Heap = (int)j,
});
segmentPtr = segment.NextSegment;
} while (segmentPtr != TargetPointer.Null && segmentPtr != firstSegment && --segmentIterations > 0);
}
}
handleTableMap = map.Next;
maxRegions--;
}
return regions;
}
IReadOnlyList<GCMemoryRegionData> IGC.GetGCBookkeepingMemoryRegions()
{
List<GCMemoryRegionData> regions = new();
TargetPointer bookkeepingStartGlobal = _target.ReadGlobalPointer(Constants.Globals.BookkeepingStart);
if (bookkeepingStartGlobal == TargetPointer.Null)
throw new InvalidOperationException("BookkeepingStart global pointer is null");
TargetPointer bookkeepingStart = _target.ReadPointer(bookkeepingStartGlobal);
if (bookkeepingStart == TargetPointer.Null)
throw new InvalidOperationException("bookkeeping_start is null");
uint cardTableInfoSize = _target.ReadGlobal<uint>(Constants.Globals.CardTableInfoSize);
Data.CardTableInfo cardTableInfo = _target.ProcessedData.GetOrAdd<Data.CardTableInfo>(bookkeepingStart);
if (cardTableInfo.Recount != 0 && cardTableInfo.Size.Value != 0)
{
regions.Add(new GCMemoryRegionData
{
Start = bookkeepingStart,
Size = cardTableInfo.Size.Value,
});
}
TargetPointer next = cardTableInfo.NextCardTable;
TargetPointer firstNext = next;
int maxRegions = MaxBookkeepingRegions;
// This comparison guards against underflow when subtracting cardTableInfoSize from `next`.
// If `next` is a corrupted or small value, the subtraction would underflow.
// This matches the native DAC: `while (next > card_table_info_size)`.
while (next != TargetPointer.Null && next > cardTableInfoSize && maxRegions > 0)
{
TargetPointer ctAddr = next - cardTableInfoSize;
Data.CardTableInfo ct = _target.ProcessedData.GetOrAdd<Data.CardTableInfo>(ctAddr);
if (ct.Recount != 0 && ct.Size.Value != 0)
{
regions.Add(new GCMemoryRegionData
{
Start = ctAddr,
Size = ct.Size.Value,
});
}
next = ct.NextCardTable;
if (next == firstNext)
break;
maxRegions--;
}
return regions;
}
IReadOnlyList<GCMemoryRegionData> IGC.GetGCFreeRegions()
{
List<GCMemoryRegionData> regions = new();
// CountFreeRegionKinds and RegionFreeList are only available on regions GC builds.
// On segment GC builds, we still enumerate freeable segments (BACKGROUND_GC).
int countFreeRegionKinds = 0;
uint regionFreeListSize = 0;
if (_target.TryReadGlobal<uint>(Constants.Globals.CountFreeRegionKinds, out uint? freeRegionKindsValue))
{
countFreeRegionKinds = Math.Min((int)freeRegionKindsValue.Value, 16);
regionFreeListSize = _target.GetTypeInfo(DataType.RegionFreeList).Size
?? throw new InvalidOperationException("RegionFreeList type has no size");
}
// Global free huge regions
if (_target.TryReadGlobalPointer(Constants.Globals.GlobalFreeHugeRegions, out TargetPointer? globalFreeHugePtr))
{
AddFreeList(globalFreeHugePtr.Value, FreeRegionKind.FreeGlobalHugeRegion, regions);
}
// Global regions to decommit
if (_target.TryReadGlobalPointer(Constants.Globals.GlobalRegionsToDecommit, out TargetPointer? globalDecommitPtr))
{
for (int i = 0; i < countFreeRegionKinds; i++)
{
TargetPointer listAddr = globalDecommitPtr.Value + (ulong)(i * regionFreeListSize);
AddFreeList(listAddr, FreeRegionKind.FreeGlobalRegion, regions);
}
}
if (GetGCType() == GCType.Server)
{
uint heapCount = ((IGC)this).GetGCHeapCount();
TargetPointer heapTable = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.Heaps));
for (uint i = 0; i < heapCount; i++)
{
TargetPointer heapAddress = _target.ReadPointer(heapTable + (i * (uint)_target.PointerSize));
if (heapAddress == TargetPointer.Null)
continue;
Data.GCHeapSVR heap = _target.ProcessedData.GetOrAdd<Data.GCHeapSVR>(heapAddress);
if (heap.FreeRegions is TargetPointer freeRegionsBase && freeRegionsBase != TargetPointer.Null)
{
for (int j = 0; j < countFreeRegionKinds; j++)
{
TargetPointer listAddr = freeRegionsBase + (ulong)(j * regionFreeListSize);
AddFreeList(listAddr, FreeRegionKind.FreeRegion, regions, (int)i);
}
}
if (heap.FreeableSohSegment is TargetPointer freeableSoh && freeableSoh != TargetPointer.Null)
AddSegmentList(freeableSoh, FreeRegionKind.FreeSohSegment, regions, (int)i);
if (heap.FreeableUohSegment is TargetPointer freeableUoh && freeableUoh != TargetPointer.Null)
AddSegmentList(freeableUoh, FreeRegionKind.FreeUohSegment, regions, (int)i);
}
}
else
{
// Workstation GC: free regions from globals
if (_target.TryReadGlobalPointer(Constants.Globals.GCHeapFreeRegions, out TargetPointer? freeRegionsPtr))
{
for (int i = 0; i < countFreeRegionKinds; i++)
{
TargetPointer listAddr = freeRegionsPtr.Value + (ulong)(i * regionFreeListSize);
AddFreeList(listAddr, FreeRegionKind.FreeRegion, regions);
}
}
if (_target.TryReadGlobalPointer(Constants.Globals.GCHeapFreeableSohSegment, out TargetPointer? freeableSohPtr))
{
TargetPointer segPtr = _target.ReadPointer(freeableSohPtr.Value);
if (segPtr != TargetPointer.Null)
AddSegmentList(segPtr, FreeRegionKind.FreeSohSegment, regions);
}
if (_target.TryReadGlobalPointer(Constants.Globals.GCHeapFreeableUohSegment, out TargetPointer? freeableUohPtr))
{
TargetPointer segPtr = _target.ReadPointer(freeableUohPtr.Value);
if (segPtr != TargetPointer.Null)
AddSegmentList(segPtr, FreeRegionKind.FreeUohSegment, regions);
}
}
return regions;
}
TargetNUInt IGC.GetHandleExtraInfo(TargetPointer handle)
{
// Compute the segment base address by aligning the handle address down to the segment size.
// Segments are always allocated aligned to HandleSegmentSize.
ulong segmentMask = ~((ulong)_handleSegmentSize - 1);
TargetPointer segmentBase = handle & segmentMask;
Data.TableSegment tableSegment = _target.ProcessedData.GetOrAdd<Data.TableSegment>(segmentBase);
// The RgValue offset within the segment equals the header size.
Target.TypeInfo typeInfo = _target.GetTypeInfo(DataType.TableSegment);
uint rgValueOffset = (uint)typeInfo.Fields[nameof(Data.TableSegment.RgValue)].Offset;
// Compute the handle index within the segment's value area.
uint handleIndex = (uint)((ulong)(handle - segmentBase) - rgValueOffset) / (uint)_target.PointerSize;
uint block = handleIndex / _handlesPerBlock;
uint intraBlockIndex = handleIndex % _handlesPerBlock;
byte userDataBlock = tableSegment.RgUserData[block];
if (userDataBlock == _blockInvalid)
return new TargetNUInt(0);
uint offset = userDataBlock * _handlesPerBlock + intraBlockIndex;
TargetPointer extraInfoAddress = tableSegment.RgValue + offset * (uint)_target.PointerSize;
return _target.ReadNUInt(extraInfoAddress);
}
private void AddFreeList(TargetPointer freeListAddr, FreeRegionKind kind, List<GCMemoryRegionData> regions, int heap = 0)
{
Data.RegionFreeList freeList = _target.ProcessedData.GetOrAdd<Data.RegionFreeList>(freeListAddr);
if (freeList.HeadFreeRegion != TargetPointer.Null)
AddSegmentList(freeList.HeadFreeRegion, kind, regions, heap);
}
private void AddSegmentList(TargetPointer start, FreeRegionKind kind, List<GCMemoryRegionData> regions, int heap = 0)
{
int iterationMax = MaxSegmentListIterations;
TargetPointer curr = start;
while (curr != TargetPointer.Null)
{
Data.HeapSegment segment = _target.ProcessedData.GetOrAdd<Data.HeapSegment>(curr);
if (segment.Mem != TargetPointer.Null)
{
ulong size = 0;
if (segment.Mem < segment.Committed)
size = segment.Committed - segment.Mem;
regions.Add(new GCMemoryRegionData
{
Start = segment.Mem,
Size = size,
ExtraData = (ulong)kind,
Heap = heap,
});
}
curr = segment.Next;
if (curr == start)
break;
if (iterationMax-- <= 0)
break;
}
}
}
|