From ea289007ea29069bc0589bebebb827121fc3a272 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Tue, 26 Aug 2025 18:08:01 -0400 Subject: [PATCH 01/10] implement GetGCHeapDetails --- .../gc/datadescriptor/datadescriptor.h | 30 +++- .../gc/datadescriptor/datadescriptor.inc | 46 ++++++ src/coreclr/gc/gcpriv.h | 1 + .../vm/datadescriptor/datadescriptor.inc | 3 + .../Contracts/IGC.cs | 37 ++++- .../DataType.cs | 9 ++ .../Constants.cs | 5 + .../Contracts/GC_1.cs | 53 +++++++ .../Data/GC/CFinalize.cs | 25 ++++ .../Data/GC/GCHeap.cs | 56 +++++++ .../Data/GC/Generation.cs | 24 +++ .../Legacy/ISOSDacInterface.cs | 53 ++++++- .../Legacy/SOSDacImpl.cs | 137 +++++++++++++++++- 13 files changed, 471 insertions(+), 8 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/CFinalize.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/GCHeap.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/Generation.cs diff --git a/src/coreclr/gc/datadescriptor/datadescriptor.h b/src/coreclr/gc/datadescriptor/datadescriptor.h index 8eb2474ff71547..33eab6b215a4c2 100644 --- a/src/coreclr/gc/datadescriptor/datadescriptor.h +++ b/src/coreclr/gc/datadescriptor/datadescriptor.h @@ -31,7 +31,33 @@ namespace GC_NAMESPACE { template<> struct cdac_data { -#ifdef MULTIPLE_HEAPS +#ifdef BACKGROUND_GC + static constexpr c_gc_state* CurrentGCState = const_cast(&GC_NAMESPACE::gc_heap::current_c_gc_state); +#endif // BACKGROUND_GC +#ifdef SERVER_GC static constexpr GC_NAMESPACE::gc_heap*** Heaps = &GC_NAMESPACE::gc_heap::g_heaps; -#endif // MULTIPLE_HEAPS + + static constexpr size_t MarkArray = offsetof(GC_NAMESPACE::gc_heap, mark_array); + static constexpr size_t NextSweepObj = offsetof(GC_NAMESPACE::gc_heap, next_sweep_obj); + static constexpr size_t BackgroundMinSavedAddr = offsetof(GC_NAMESPACE::gc_heap, background_saved_lowest_address); + static constexpr size_t BackgroundMaxSavedAddr = offsetof(GC_NAMESPACE::gc_heap, background_saved_highest_address); + static constexpr size_t AllocAllocated = offsetof(GC_NAMESPACE::gc_heap, alloc_allocated); + static constexpr size_t EphemeralHeapSegment = offsetof(GC_NAMESPACE::gc_heap, ephemeral_heap_segment); + static constexpr size_t CardTable = offsetof(GC_NAMESPACE::gc_heap, card_table); + static constexpr size_t FinalizeQueue = offsetof(GC_NAMESPACE::gc_heap, finalize_queue); + + static constexpr size_t GenerationTable = offsetof(GC_NAMESPACE::gc_heap, generation_table); + +#ifndef USE_REGIONS + static constexpr size_t SavedSweepEphemeralSeg = offsetof(GC_NAMESPACE::gc_heap, saved_sweep_ephemeral_seg); + static constexpr size_t SavedSweepEphemeralStart = offsetof(GC_NAMESPACE::gc_heap, saved_sweep_ephemeral_start); +#endif // !USE_REGIONS +#endif // SERVER_GC +}; + +template<> +struct cdac_data +{ + static constexpr size_t FillPointers = offsetof(GC_NAMESPACE::CFinalize, m_FillPointers); + static constexpr size_t FillPointersLength = sizeof(GC_NAMESPACE::CFinalize::m_FillPointers) / sizeof(GC_NAMESPACE::CFinalize::m_FillPointers[0]); }; diff --git a/src/coreclr/gc/datadescriptor/datadescriptor.inc b/src/coreclr/gc/datadescriptor/datadescriptor.inc index 82b93b35dd44d2..45d82e29449dc2 100644 --- a/src/coreclr/gc/datadescriptor/datadescriptor.inc +++ b/src/coreclr/gc/datadescriptor/datadescriptor.inc @@ -10,10 +10,47 @@ CDAC_BASELINE("empty") CDAC_TYPES_BEGIN() +#ifdef SERVER_GC +CDAC_TYPE_BEGIN(GCHeap) +CDAC_TYPE_INDETERMINATE(GCHeap) +CDAC_TYPE_FIELD(GCHeap, /*pointer*/, MarkArray, cdac_data::MarkArray) +CDAC_TYPE_FIELD(GCHeap, /*pointer*/, NextSweepObj, cdac_data::NextSweepObj) +CDAC_TYPE_FIELD(GCHeap, /*pointer*/, BackgroundMinSavedAddr, cdac_data::BackgroundMinSavedAddr) +CDAC_TYPE_FIELD(GCHeap, /*pointer*/, BackgroundMaxSavedAddr, cdac_data::BackgroundMaxSavedAddr) +CDAC_TYPE_FIELD(GCHeap, /*pointer*/, AllocAllocated, cdac_data::AllocAllocated) +CDAC_TYPE_FIELD(GCHeap, /*pointer*/, EphemeralHeapSegment, cdac_data::EphemeralHeapSegment) +CDAC_TYPE_FIELD(GCHeap, /*pointer*/, CardTable, cdac_data::CardTable) +CDAC_TYPE_FIELD(GCHeap, /*pointer*/, FinalizeQueue, cdac_data::FinalizeQueue) + +CDAC_TYPE_FIELD(GCHeap, /*pointer*/, GenerationTable, cdac_data::GenerationTable) + +#ifndef USE_REGIONS +CDAC_TYPE_FIELD(GCHeap, /*pointer*/, SavedSweepEphemeralSeg, cdac_data::SavedSweepEphemeralSeg) +CDAC_TYPE_FIELD(GCHeap, /*pointer*/, SavedSweepEphemeralStart, cdac_data::SavedSweepEphemeralStart) +#endif // !USE_REGIONS +CDAC_TYPE_END(GCHeap) +#endif // SERVER_GC + +CDAC_TYPE_BEGIN(Generation) +CDAC_TYPE_SIZE(sizeof(GC_NAMESPACE::generation)) +CDAC_TYPE_FIELD(Generation, /*AllocContext*/, AllocationContext, offsetof(GC_NAMESPACE::generation, allocation_context)) +CDAC_TYPE_FIELD(Generation, /*pointer*/, StartSegment, offsetof(GC_NAMESPACE::generation, start_segment)) +#ifndef USE_REGIONS +CDAC_TYPE_FIELD(Generation, /*pointer*/, AllocationStart, offsetof(GC_NAMESPACE::generation, allocation_start)) +#endif // !USE_REGIONS +CDAC_TYPE_END(Generation) + +CDAC_TYPE_BEGIN(CFinalize) +CDAC_TYPE_INDETERMINATE(CFinalize) +CDAC_TYPE_FIELD(CFinalize, /*pointer*/, FillPointers, cdac_data::FillPointers) +CDAC_TYPE_END(CFinalize) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() +CDAC_GLOBAL(TotalGenerationCount, /*uint32*/, (uint32_t)total_generation_count) +CDAC_GLOBAL(CFinalizeFillPointersLength, /*uint32*/, (uint32_t)cdac_data::FillPointersLength) #ifdef SERVER_GC #define GC_TYPE server @@ -30,7 +67,12 @@ CDAC_GLOBALS_BEGIN() // CDAC_GLOBAL_STRING takes a single value argument. // To avoid issues with commas in the string we wrap the input string in a macro. #define GC_IDENTIFIER(...) __VA_ARGS__ // GC_IDENTIFIER(gc, heap) expands to: gc, heap + +#ifdef BACKGROUND_GC CDAC_GLOBAL_STRING(GCIdentifiers, GC_IDENTIFIER(GC_TYPE, HEAP_TYPE)) +#else +CDAC_GLOBAL_STRING(GCIdentifiers, GC_IDENTIFIER(GC_TYPE, HEAP_TYPE, background)) +#endif // BACKGROUND_GC CDAC_GLOBAL_POINTER(MaxGeneration, &::g_max_generation) CDAC_GLOBAL_POINTER(StructureInvalidCount, &GCScan::m_GcStructuresInvalidCnt) @@ -40,4 +82,8 @@ CDAC_GLOBAL_POINTER(NumHeaps, &GC_NAMESPACE::gc_heap::n_heaps) CDAC_GLOBAL_POINTER(Heaps, cdac_data::Heaps) #endif // SERVER_GC +#ifdef BACKGROUND_GC +CDAC_GLOBAL_POINTER(CurrentGCState, cdac_data::CurrentGCState) +#endif // BACKGROUND_GC + CDAC_GLOBALS_END() diff --git a/src/coreclr/gc/gcpriv.h b/src/coreclr/gc/gcpriv.h index 49a540bebc74f6..0247a4e5a96de6 100644 --- a/src/coreclr/gc/gcpriv.h +++ b/src/coreclr/gc/gcpriv.h @@ -5680,6 +5680,7 @@ class CFinalize { friend class CFinalizeStaticAsserts; + friend struct ::cdac_data; private: diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 672f1ba6dbc63d..fe45dfa97290fa 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -1005,6 +1005,9 @@ CDAC_GLOBAL_POINTER(PlatformMetadata, &::g_cdacPlatformMetadata) CDAC_GLOBAL_POINTER(ProfilerControlBlock, &::g_profControlBlock) CDAC_GLOBAL_POINTER(MethodDescSizeTable, &MethodDesc::s_ClassificationSizeTable) +CDAC_GLOBAL_POINTER(GCLowestAddress, &g_lowest_address) +CDAC_GLOBAL_POINTER(GCHighestAddress, &g_highest_address) + CDAC_GLOBAL_SUB_DESCRIPTOR(GC, &(g_gc_dac_vars.gc_descriptor)) CDAC_GLOBALS_END() 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 0516eebe95c1a4..942ab0183f3d05 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 @@ -6,13 +6,42 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; -public class GCIdentifiers +public static class GCIdentifiers { public const string Server = "server"; public const string Workstation = "workstation"; public const string Regions = "regions"; public const string Segments = "segments"; + + public const string Background = "background"; +} + +public readonly struct GCHeapData +{ + public TargetPointer MarkArray { get; init; } + public TargetPointer NextSweepObject { get; init; } + public TargetPointer BackGroundSavedMinAddress { get; init; } + public TargetPointer BackGroundSavedMaxAddress { get; init; } + + public TargetPointer AllocAllocated { get; init; } + public TargetPointer EphemeralHeapSegment { get; init; } + public TargetPointer CardTable { get; init; } + public IReadOnlyList GenerationTable { get; init; } + + public IReadOnlyList FillPointers { get; init; } + + // Fields only valid in segment GC builds + public TargetPointer SavedSweepEphemeralSegment { get; init; } + public TargetPointer SavedSweepEphemeralStart { get; init; } +} + +public readonly struct GCGenerationData +{ + public TargetPointer StartSegment { get; init; } + public TargetPointer AllocationStart { get; init; } + public TargetPointer AllocationContextPointer { get; init; } + public TargetPointer AllocationContextLimit { get; init; } } public interface IGC : IContract @@ -20,10 +49,16 @@ public interface IGC : IContract static string IContract.Name { get; } = nameof(GC); string[] GetGCIdentifiers() => throw new NotImplementedException(); + uint GetGCHeapCount() => throw new NotImplementedException(); bool GetGCStructuresValid() => throw new NotImplementedException(); uint GetMaxGeneration() => throw new NotImplementedException(); + void GetGCBounds(out TargetPointer minAddr, out TargetPointer maxAddr) => throw new NotImplementedException(); + TargetPointer GetCurrentGCStateAddress() => throw new NotImplementedException(); IEnumerable GetGCHeaps() => throw new NotImplementedException(); + + /* SVR only APIs */ + GCHeapData SVRGetHeapData(TargetPointer heapAddress) => throw new NotImplementedException(); } public readonly struct GC : IGC diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index 834aad423c4169..5dcb0078bf9854 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -19,6 +19,8 @@ public enum DataType nuint, pointer, + /* VM Data Types */ + GCHandle, CodePointer, Thread, @@ -129,4 +131,11 @@ public enum DataType HijackFrame, TailCallFrame, StubDispatchFrame, + + + /* GC Data Types */ + + GCHeap, + Generation, + CFinalize, } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index 5d184703853b41..4690eed9e58f0d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -74,6 +74,8 @@ public static class Globals public const string OperatingSystem = nameof(OperatingSystem); public const string GCInfoVersion = nameof(GCInfoVersion); + public const string GCLowestAddress = nameof(GCLowestAddress); + public const string GCHighestAddress = nameof(GCHighestAddress); // Globals found on GCDescriptor // see src/coreclr/gc/datadescriptors/datadescriptor.inc @@ -82,6 +84,9 @@ public static class Globals public const string StructureInvalidCount = nameof(StructureInvalidCount); public const string NumHeaps = nameof(NumHeaps); public const string Heaps = nameof(Heaps); + public const string CurrentGCState = nameof(CurrentGCState); + public const string CFinalizeFillPointersLength = nameof(CFinalizeFillPointersLength); + public const string TotalGenerationCount = nameof(TotalGenerationCount); } public static class FieldNames { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs index 3b669a412c3436..682ef9c8ecefcd 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -57,6 +58,19 @@ uint IGC.GetMaxGeneration() return _target.Read(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)); + } + + TargetPointer IGC.GetCurrentGCStateAddress() + { + if (!IsBackgroundGCEnabled()) + return TargetPointer.Null; + return _target.ReadGlobalPointer(Constants.Globals.CurrentGCState); + } + IEnumerable IGC.GetGCHeaps() { if (GetGCType() != GCType.Server) @@ -73,6 +87,39 @@ IEnumerable IGC.GetGCHeaps() } } + GCHeapData IGC.SVRGetHeapData(TargetPointer heapAddress) + { + if (GetGCType() != GCType.Server) + throw new InvalidOperationException("GetHeapData is only valid for Server GC."); + + Data.GCHeap_svr heap = _target.ProcessedData.GetOrAdd(heapAddress); + Data.CFinalize finalize = _target.ProcessedData.GetOrAdd(heap.FinalizeQueue); + + IList generationDataList = heap.GenerationTable.Select(gen => + new GCGenerationData() + { + StartSegment = gen.StartSegment, + AllocationStart = gen.AllocationStart ?? unchecked((ulong)-1), + AllocationContextPointer = gen.AllocationContext.Pointer, + AllocationContextLimit = gen.AllocationContext.Limit, + }).ToList(); + + return new GCHeapData() + { + MarkArray = heap.MarkArray, + NextSweepObject = heap.NextSweepObj, + BackGroundSavedMinAddress = heap.BackgroundMinSavedAddr, + BackGroundSavedMaxAddress = heap.BackgroundMaxSavedAddr, + AllocAllocated = heap.AllocAllocated, + EphemeralHeapSegment = heap.EphemeralHeapSegment, + CardTable = heap.CardTable, + GenerationTable = generationDataList.AsReadOnly(), + FillPointers = finalize.FillPointers, + SavedSweepEphemeralSegment = heap.SavedSweepEphemeralSeg ?? TargetPointer.Null, + SavedSweepEphemeralStart = heap.SavedSweepEphemeralStart ?? TargetPointer.Null, + }; + } + private GCType GetGCType() { string[] identifiers = ((IGC)this).GetGCIdentifiers(); @@ -89,4 +136,10 @@ private GCType GetGCType() return GCType.Unknown; // Unknown or unsupported GC type } } + + private bool IsBackgroundGCEnabled() + { + string[] identifiers = ((IGC)this).GetGCIdentifiers(); + return identifiers.Contains(GCIdentifiers.Background); + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/CFinalize.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/CFinalize.cs new file mode 100644 index 00000000000000..f974eea5dc6977 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/CFinalize.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class CFinalize : IData +{ + static CFinalize IData.Create(Target target, TargetPointer address) => new CFinalize(target, address); + public CFinalize(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.CFinalize); + + uint fillPointersLength = target.ReadGlobal(Constants.Globals.CFinalizeFillPointersLength); + TargetPointer fillPointersArrayStart = address + (ulong)type.Fields[nameof(FillPointers)].Offset; + + TargetPointer[] fillPointers = new TargetPointer[fillPointersLength]; + for (uint i = 0; i < fillPointersLength; i++) + fillPointers[i] = target.ReadPointer(fillPointersArrayStart + i * (ulong)target.PointerSize); + FillPointers = fillPointers.AsReadOnly(); + } + + public IReadOnlyList FillPointers { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/GCHeap.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/GCHeap.cs new file mode 100644 index 00000000000000..27972048a32d0d --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/GCHeap.cs @@ -0,0 +1,56 @@ +// 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; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class GCHeap_svr : IData +{ + static GCHeap_svr IData.Create(Target target, TargetPointer address) => new GCHeap_svr(target, address); + public GCHeap_svr(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.GCHeap); + + MarkArray = target.ReadPointer(address + (ulong)type.Fields[nameof(MarkArray)].Offset); + NextSweepObj = target.ReadPointer(address + (ulong)type.Fields[nameof(NextSweepObj)].Offset); + BackgroundMinSavedAddr = target.ReadPointer(address + (ulong)type.Fields[nameof(BackgroundMinSavedAddr)].Offset); + BackgroundMaxSavedAddr = target.ReadPointer(address + (ulong)type.Fields[nameof(BackgroundMaxSavedAddr)].Offset); + AllocAllocated = target.ReadPointer(address + (ulong)type.Fields[nameof(AllocAllocated)].Offset); + EphemeralHeapSegment = target.ReadPointer(address + (ulong)type.Fields[nameof(EphemeralHeapSegment)].Offset); + CardTable = target.ReadPointer(address + (ulong)type.Fields[nameof(CardTable)].Offset); + FinalizeQueue = target.ReadPointer(address + (ulong)type.Fields[nameof(FinalizeQueue)].Offset); + + uint generationTableLength = target.ReadGlobal(Constants.Globals.TotalGenerationCount); + TargetPointer generationTableArrayStart = address + (ulong)type.Fields[nameof(GenerationTable)].Offset; + uint generationSize = target.GetTypeInfo(DataType.Generation).Size ?? throw new InvalidOperationException("Type Generation has no size"); + + List generationTable = []; + for (uint i = 0; i < generationTableLength; i++) + { + TargetPointer generationAddress = generationTableArrayStart + i * generationSize; + generationTable.Add(target.ProcessedData.GetOrAdd(generationAddress)); + } + GenerationTable = generationTable.AsReadOnly(); + + // Fields only exist segment GC builds + if (type.Fields.ContainsKey(nameof(SavedSweepEphemeralSeg))) + SavedSweepEphemeralSeg = target.ReadPointer(address + (ulong)type.Fields[nameof(SavedSweepEphemeralSeg)].Offset); + if (type.Fields.ContainsKey(nameof(SavedSweepEphemeralStart))) + SavedSweepEphemeralStart = target.ReadPointer(address + (ulong)type.Fields[nameof(SavedSweepEphemeralStart)].Offset); + } + + public TargetPointer MarkArray { get; } + public TargetPointer NextSweepObj { get; } + public TargetPointer BackgroundMinSavedAddr { get; } + public TargetPointer BackgroundMaxSavedAddr { get; } + public TargetPointer AllocAllocated { get; } + public TargetPointer EphemeralHeapSegment { get; } + public TargetPointer CardTable { get; } + public TargetPointer FinalizeQueue { get; } + public IReadOnlyList GenerationTable { get; } + + public TargetPointer? SavedSweepEphemeralSeg { get; } + public TargetPointer? SavedSweepEphemeralStart { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/Generation.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/Generation.cs new file mode 100644 index 00000000000000..347442c12cf70e --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/Generation.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class Generation : IData +{ + static Generation IData.Create(Target target, TargetPointer address) => new Generation(target, address); + public Generation(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.Generation); + + AllocationContext = target.ProcessedData.GetOrAdd(address + (ulong)type.Fields[nameof(AllocationContext)].Offset); + StartSegment = target.ReadPointer(address + (ulong)type.Fields[nameof(StartSegment)].Offset); + + // Fields only exist segment GC builds + if (type.Fields.ContainsKey(nameof(AllocationStart))) + AllocationStart = target.ReadPointer(address + (ulong)type.Fields[nameof(AllocationStart)].Offset); + } + + public GCAllocContext AllocationContext { get; } + public TargetPointer StartSegment { get; } + public TargetPointer? AllocationStart { get; } +} diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs index eabf1d516e7565..33ef8f2fa3c576 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs @@ -278,6 +278,11 @@ internal struct DacpMethodTableTransparencyData public int bIsTreatAsSafe; } +internal static class GCConstants +{ + public const int DAC_NUMBERGENERATIONS = 4; +} + internal struct DacpGcHeapData { public int bServerMode; @@ -286,6 +291,50 @@ internal struct DacpGcHeapData public uint g_max_generation; } +internal struct DacpGenerationData +{ + public ClrDataAddress start_segment; + public ClrDataAddress allocation_start; + + // These are examined only for generation 0, otherwise NULL + public ClrDataAddress allocContextPtr; + public ClrDataAddress allocContextLimit; +} + +internal struct DacpGcHeapDetails +{ + public ClrDataAddress heapAddr; // Only filled in server mode, otherwise NULL + public ClrDataAddress alloc_allocated; + + public ClrDataAddress mark_array; + public ClrDataAddress current_c_gc_state; + public ClrDataAddress next_sweep_obj; + public ClrDataAddress saved_sweep_ephemeral_seg; + public ClrDataAddress saved_sweep_ephemeral_start; + public ClrDataAddress background_saved_lowest_address; + public ClrDataAddress background_saved_highest_address; + + [System.Runtime.CompilerServices.InlineArray(GCConstants.DAC_NUMBERGENERATIONS)] + public struct GenerationDataArray + { + private DacpGenerationData _; + } + public GenerationDataArray generation_table; + + public ClrDataAddress ephemeral_heap_segment; + + [System.Runtime.CompilerServices.InlineArray(GCConstants.DAC_NUMBERGENERATIONS + 3)] + public struct FinalizationDataArray + { + private ClrDataAddress _; + } + public FinalizationDataArray finalization_fill_pointers; + + public ClrDataAddress lowest_address; + public ClrDataAddress highest_address; + public ClrDataAddress card_table; +} + [GeneratedComInterface] [Guid("436f00f2-b42a-4b9f-870c-e73db66ae930")] internal unsafe partial interface ISOSDacInterface @@ -331,7 +380,7 @@ internal unsafe partial interface ISOSDacInterface // Threads [PreserveSig] - int GetThreadData(ClrDataAddress thread, DacpThreadData *data); + int GetThreadData(ClrDataAddress thread, DacpThreadData* data); [PreserveSig] int GetThreadFromThinlockID(uint thinLockId, ClrDataAddress* pThread); [PreserveSig] @@ -413,7 +462,7 @@ internal unsafe partial interface ISOSDacInterface [PreserveSig] int GetGCHeapList(uint count, [In, Out, MarshalUsing(CountElementName = nameof(count))] ClrDataAddress[] heaps, uint* pNeeded); // svr only [PreserveSig] - int GetGCHeapDetails(ClrDataAddress heap, /*struct DacpGcHeapDetails */ void* details); // wks only + int GetGCHeapDetails(ClrDataAddress heap, DacpGcHeapDetails* details); // wks only [PreserveSig] int GetGCHeapStaticData(/*struct DacpGcHeapDetails */ void* data); [PreserveSig] diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs index ea56a52440ee2e..5da8133c8d426d 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs @@ -60,7 +60,7 @@ internal sealed unsafe partial class SOSDacImpl private readonly IXCLRDataProcess2? _legacyProcess2; private readonly ICLRDataEnumMemoryRegions? _legacyEnumMemory; - private enum CorTokenType: uint + private enum CorTokenType : uint { mdtTypeRef = 0x01000000, mdtTypeDef = 0x02000000, @@ -836,8 +836,139 @@ int ISOSDacInterface.GetGCHeapList(uint count, [In, MarshalUsing(CountElementNam #endif return hr; } - int ISOSDacInterface.GetGCHeapDetails(ClrDataAddress heap, void* details) - => _legacyImpl is not null ? _legacyImpl.GetGCHeapDetails(heap, details) : HResults.E_NOTIMPL; + int ISOSDacInterface.GetGCHeapDetails(ClrDataAddress heap, DacpGcHeapDetails* details) + { + int hr = HResults.S_OK; + + try + { + if (heap == 0 || details == null) + throw new ArgumentException(); + + IGC gc = _target.Contracts.GC; + string[] gcIdentifiers = gc.GetGCIdentifiers(); + + // doesn't make sense to call this on WKS mode + if (!gcIdentifiers.Contains(GCIdentifiers.Server)) + throw new ArgumentException(); + + GCHeapData heapData = gc.SVRGetHeapData(heap.ToTargetPointer(_target)); + + details->heapAddr = heap; + + gc.GetGCBounds(out TargetPointer minAddress, out TargetPointer maxAddress); + details->lowest_address = minAddress.ToClrDataAddress(_target); + details->highest_address = maxAddress.ToClrDataAddress(_target); + + if (gcIdentifiers.Contains(GCIdentifiers.Background)) + { + details->current_c_gc_state = gc.GetCurrentGCStateAddress().ToClrDataAddress(_target); + details->mark_array = heapData.MarkArray.ToClrDataAddress(_target); + details->next_sweep_obj = heapData.NextSweepObject.ToClrDataAddress(_target); + details->background_saved_lowest_address = heapData.BackGroundSavedMinAddress.ToClrDataAddress(_target); + details->background_saved_highest_address = heapData.BackGroundSavedMaxAddress.ToClrDataAddress(_target); + } + else + { + details->current_c_gc_state = 0; + details->mark_array = unchecked((ulong)-1); + details->next_sweep_obj = 0; + details->background_saved_lowest_address = 0; + details->background_saved_highest_address = 0; + } + + // now get information specific to this heap (server mode gives us several heaps; we're getting + // information about only one of them. + details->alloc_allocated = heapData.AllocAllocated.ToClrDataAddress(_target); + details->ephemeral_heap_segment = heapData.EphemeralHeapSegment.ToClrDataAddress(_target); + details->card_table = heapData.CardTable.ToClrDataAddress(_target); + + if (gcIdentifiers.Contains(GCIdentifiers.Regions)) + { + // with regions, we don't have these variables anymore + // use special value -1 in saved_sweep_ephemeral_seg to signal the region case + details->saved_sweep_ephemeral_seg = unchecked((ulong)-1); + details->saved_sweep_ephemeral_start = 0; + } + else + { + if (gcIdentifiers.Contains(GCIdentifiers.Background)) + { + details->saved_sweep_ephemeral_seg = heapData.SavedSweepEphemeralSegment.ToClrDataAddress(_target); + details->saved_sweep_ephemeral_start = heapData.SavedSweepEphemeralStart.ToClrDataAddress(_target); + } + else + { + details->saved_sweep_ephemeral_seg = 0; + details->saved_sweep_ephemeral_start = 0; + } + } + + // get bounds for the different generations + for (int i = 0; i < GCConstants.DAC_NUMBERGENERATIONS && i < heapData.GenerationTable.Count; i++) + { + GCGenerationData genData = heapData.GenerationTable[i]; + details->generation_table[i].start_segment = genData.StartSegment.ToClrDataAddress(_target); + details->generation_table[i].allocation_start = genData.AllocationStart.ToClrDataAddress(_target); + details->generation_table[i].allocContextPtr = genData.AllocationContextPointer.ToClrDataAddress(_target); + details->generation_table[i].allocContextLimit = genData.AllocationContextLimit.ToClrDataAddress(_target); + } + + for (int i = 0; i < GCConstants.DAC_NUMBERGENERATIONS + 3 && i < heapData.FillPointers.Count; i++) + { + details->finalization_fill_pointers[i] = heapData.FillPointers[i].ToClrDataAddress(_target); + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyImpl is not null) + { + DacpGcHeapDetails detailsLocal = default; + int hrLocal = _legacyImpl.GetGCHeapDetails(heap, &detailsLocal); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + if (hr == HResults.S_OK) + { + Debug.Assert(details->heapAddr == detailsLocal.heapAddr, $"cDAC: {details->heapAddr:x}, DAC: {detailsLocal.heapAddr:x}"); + Debug.Assert(details->alloc_allocated == detailsLocal.alloc_allocated, $"cDAC: {details->alloc_allocated:x}, DAC: {detailsLocal.alloc_allocated:x}"); + Debug.Assert(details->mark_array == detailsLocal.mark_array, $"cDAC: {details->mark_array:x}, DAC: {detailsLocal.mark_array:x}"); + Debug.Assert(details->current_c_gc_state == detailsLocal.current_c_gc_state, $"cDAC: {details->current_c_gc_state:x}, DAC: {detailsLocal.current_c_gc_state:x}"); + Debug.Assert(details->next_sweep_obj == detailsLocal.next_sweep_obj, $"cDAC: {details->next_sweep_obj:x}, DAC: {detailsLocal.next_sweep_obj:x}"); + Debug.Assert(details->saved_sweep_ephemeral_seg == detailsLocal.saved_sweep_ephemeral_seg, $"cDAC: {details->saved_sweep_ephemeral_seg:x}, DAC: {detailsLocal.saved_sweep_ephemeral_seg:x}"); + Debug.Assert(details->saved_sweep_ephemeral_start == detailsLocal.saved_sweep_ephemeral_start, $"cDAC: {details->saved_sweep_ephemeral_start:x}, DAC: {detailsLocal.saved_sweep_ephemeral_start:x}"); + Debug.Assert(details->background_saved_lowest_address == detailsLocal.background_saved_lowest_address, $"cDAC: {details->background_saved_lowest_address:x}, DAC: {detailsLocal.background_saved_lowest_address:x}"); + Debug.Assert(details->background_saved_highest_address == detailsLocal.background_saved_highest_address, $"cDAC: {details->background_saved_highest_address:x}, DAC: {detailsLocal.background_saved_highest_address:x}"); + + // Verify generation table data + for (int i = 0; i < GCConstants.DAC_NUMBERGENERATIONS; i++) + { + Debug.Assert(details->generation_table[i].start_segment == detailsLocal.generation_table[i].start_segment, $"cDAC gen[{i}].start_segment: {details->generation_table[i].start_segment:x}, DAC: {detailsLocal.generation_table[i].start_segment:x}"); + Debug.Assert(details->generation_table[i].allocation_start == detailsLocal.generation_table[i].allocation_start, $"cDAC gen[{i}].allocation_start: {details->generation_table[i].allocation_start:x}, DAC: {detailsLocal.generation_table[i].allocation_start:x}"); + Debug.Assert(details->generation_table[i].allocContextPtr == detailsLocal.generation_table[i].allocContextPtr, $"cDAC gen[{i}].allocContextPtr: {details->generation_table[i].allocContextPtr:x}, DAC: {detailsLocal.generation_table[i].allocContextPtr:x}"); + Debug.Assert(details->generation_table[i].allocContextLimit == detailsLocal.generation_table[i].allocContextLimit, $"cDAC gen[{i}].allocContextLimit: {details->generation_table[i].allocContextLimit:x}, DAC: {detailsLocal.generation_table[i].allocContextLimit:x}"); + } + + Debug.Assert(details->ephemeral_heap_segment == detailsLocal.ephemeral_heap_segment, $"cDAC: {details->ephemeral_heap_segment:x}, DAC: {detailsLocal.ephemeral_heap_segment:x}"); + + // Verify finalization fill pointers + for (int i = 0; i < GCConstants.DAC_NUMBERGENERATIONS + 3; i++) + { + Debug.Assert(details->finalization_fill_pointers[i] == detailsLocal.finalization_fill_pointers[i], $"cDAC finalization_fill_pointers[{i}]: {details->finalization_fill_pointers[i]:x}, DAC: {detailsLocal.finalization_fill_pointers[i]:x}"); + } + + Debug.Assert(details->lowest_address == detailsLocal.lowest_address, $"cDAC: {details->lowest_address:x}, DAC: {detailsLocal.lowest_address:x}"); + Debug.Assert(details->highest_address == detailsLocal.highest_address, $"cDAC: {details->highest_address:x}, DAC: {detailsLocal.highest_address:x}"); + Debug.Assert(details->card_table == detailsLocal.card_table, $"cDAC: {details->card_table:x}, DAC: {detailsLocal.card_table:x}"); + } + } +#endif + + return hr; + } + int ISOSDacInterface.GetGCHeapStaticData(void* data) => _legacyImpl is not null ? _legacyImpl.GetGCHeapStaticData(data) : HResults.E_NOTIMPL; int ISOSDacInterface.GetHandleEnum(void** ppHandleEnum) From c430e9de4c0691312ae3a3d40fd4e4654bf859eb Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Tue, 26 Aug 2025 18:55:01 -0400 Subject: [PATCH 02/10] implement GetGCHeapStaticData --- .../gc/datadescriptor/datadescriptor.h | 31 ++-- .../gc/datadescriptor/datadescriptor.inc | 18 ++- .../Contracts/IGC.cs | 3 + .../Constants.cs | 12 ++ .../Contracts/GC_1.cs | 59 ++++++++ .../Legacy/ISOSDacInterface.cs | 4 +- .../Legacy/SOSDacImpl.cs | 135 +++++++++++++++++- 7 files changed, 244 insertions(+), 18 deletions(-) diff --git a/src/coreclr/gc/datadescriptor/datadescriptor.h b/src/coreclr/gc/datadescriptor/datadescriptor.h index 33eab6b215a4c2..d21cccb71d8956 100644 --- a/src/coreclr/gc/datadescriptor/datadescriptor.h +++ b/src/coreclr/gc/datadescriptor/datadescriptor.h @@ -28,6 +28,13 @@ namespace GC_NAMESPACE { // On non-MSVC builds explicit specializations must be declared in the namespace the template was defined. // Due to the gc being built into coreclr, cdac_data must be defined in the global scope. + +#ifdef SERVER_GC +#define GC_HEAP_FIELD(cdacName, fieldName) static constexpr size_t cdacName = offsetof(GC_NAMESPACE::gc_heap, fieldName); +#else // !SERVER_GC +#define GC_HEAP_FIELD(cdacName, fieldName) static constexpr decltype(&GC_NAMESPACE::gc_heap::fieldName) cdacName = &GC_NAMESPACE::gc_heap::fieldName; +#endif // !SERVER_GC + template<> struct cdac_data { @@ -36,23 +43,23 @@ struct cdac_data #endif // BACKGROUND_GC #ifdef SERVER_GC static constexpr GC_NAMESPACE::gc_heap*** Heaps = &GC_NAMESPACE::gc_heap::g_heaps; +#endif // SERVER_GC - static constexpr size_t MarkArray = offsetof(GC_NAMESPACE::gc_heap, mark_array); - static constexpr size_t NextSweepObj = offsetof(GC_NAMESPACE::gc_heap, next_sweep_obj); - static constexpr size_t BackgroundMinSavedAddr = offsetof(GC_NAMESPACE::gc_heap, background_saved_lowest_address); - static constexpr size_t BackgroundMaxSavedAddr = offsetof(GC_NAMESPACE::gc_heap, background_saved_highest_address); - static constexpr size_t AllocAllocated = offsetof(GC_NAMESPACE::gc_heap, alloc_allocated); - static constexpr size_t EphemeralHeapSegment = offsetof(GC_NAMESPACE::gc_heap, ephemeral_heap_segment); - static constexpr size_t CardTable = offsetof(GC_NAMESPACE::gc_heap, card_table); - static constexpr size_t FinalizeQueue = offsetof(GC_NAMESPACE::gc_heap, finalize_queue); + GC_HEAP_FIELD(MarkArray, mark_array) + GC_HEAP_FIELD(NextSweepObj, next_sweep_obj) + GC_HEAP_FIELD(BackgroundMinSavedAddr, background_saved_lowest_address) + GC_HEAP_FIELD(BackgroundMaxSavedAddr, background_saved_highest_address) + GC_HEAP_FIELD(AllocAllocated, alloc_allocated) + GC_HEAP_FIELD(EphemeralHeapSegment, ephemeral_heap_segment) + GC_HEAP_FIELD(CardTable, card_table) + GC_HEAP_FIELD(FinalizeQueue, finalize_queue) - static constexpr size_t GenerationTable = offsetof(GC_NAMESPACE::gc_heap, generation_table); + GC_HEAP_FIELD(GenerationTable, generation_table) #ifndef USE_REGIONS - static constexpr size_t SavedSweepEphemeralSeg = offsetof(GC_NAMESPACE::gc_heap, saved_sweep_ephemeral_seg); - static constexpr size_t SavedSweepEphemeralStart = offsetof(GC_NAMESPACE::gc_heap, saved_sweep_ephemeral_start); + GC_HEAP_FIELD(SavedSweepEphemeralSeg, saved_sweep_ephemeral_seg) + GC_HEAP_FIELD(SavedSweepEphemeralStart, saved_sweep_ephemeral_start) #endif // !USE_REGIONS -#endif // SERVER_GC }; template<> diff --git a/src/coreclr/gc/datadescriptor/datadescriptor.inc b/src/coreclr/gc/datadescriptor/datadescriptor.inc index 45d82e29449dc2..c87c66f52a3de4 100644 --- a/src/coreclr/gc/datadescriptor/datadescriptor.inc +++ b/src/coreclr/gc/datadescriptor/datadescriptor.inc @@ -21,9 +21,7 @@ CDAC_TYPE_FIELD(GCHeap, /*pointer*/, AllocAllocated, cdac_data::EphemeralHeapSegment) CDAC_TYPE_FIELD(GCHeap, /*pointer*/, CardTable, cdac_data::CardTable) CDAC_TYPE_FIELD(GCHeap, /*pointer*/, FinalizeQueue, cdac_data::FinalizeQueue) - CDAC_TYPE_FIELD(GCHeap, /*pointer*/, GenerationTable, cdac_data::GenerationTable) - #ifndef USE_REGIONS CDAC_TYPE_FIELD(GCHeap, /*pointer*/, SavedSweepEphemeralSeg, cdac_data::SavedSweepEphemeralSeg) CDAC_TYPE_FIELD(GCHeap, /*pointer*/, SavedSweepEphemeralStart, cdac_data::SavedSweepEphemeralStart) @@ -52,6 +50,22 @@ CDAC_GLOBALS_BEGIN() CDAC_GLOBAL(TotalGenerationCount, /*uint32*/, (uint32_t)total_generation_count) CDAC_GLOBAL(CFinalizeFillPointersLength, /*uint32*/, (uint32_t)cdac_data::FillPointersLength) +#ifndef SERVER_GC +CDAC_GLOBAL_POINTER(GCHeapMarkArray, cdac_data::MarkArray) +CDAC_GLOBAL_POINTER(GCHeapNextSweepObj, cdac_data::NextSweepObj) +CDAC_GLOBAL_POINTER(GCHeapBackgroundMinSavedAddr, cdac_data::BackgroundMinSavedAddr) +CDAC_GLOBAL_POINTER(GCHeapBackgroundMaxSavedAddr, cdac_data::BackgroundMaxSavedAddr) +CDAC_GLOBAL_POINTER(GCHeapAllocAllocated, cdac_data::AllocAllocated) +CDAC_GLOBAL_POINTER(GCHeapEphemeralHeapSegment, cdac_data::EphemeralHeapSegment) +CDAC_GLOBAL_POINTER(GCHeapCardTable, cdac_data::CardTable) +CDAC_GLOBAL_POINTER(GCHeapFinalizeQueue, cdac_data::FinalizeQueue) +CDAC_GLOBAL_POINTER(GCHeapGenerationTable, cdac_data::GenerationTable) +#ifndef USE_REGIONS +CDAC_GLOBAL_POINTER(GCHeapSavedSweepEphemeralSeg, cdac_data::SavedSweepEphemeralSeg) +CDAC_GLOBAL_POINTER(GCHeapSavedSweepEphemeralStart, cdac_data::SavedSweepEphemeralStart) +#endif // !USE_REGIONS +#endif // !SERVER_GC + #ifdef SERVER_GC #define GC_TYPE server #else // SERVER_GC 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 942ab0183f3d05..b90a16fdb50688 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 @@ -59,6 +59,9 @@ public interface IGC : IContract /* SVR only APIs */ GCHeapData SVRGetHeapData(TargetPointer heapAddress) => throw new NotImplementedException(); + + /* WKS only APIs */ + GCHeapData WKSGetHeapData() => throw new NotImplementedException(); } public readonly struct GC : IGC diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index 4690eed9e58f0d..3ea7cdfa963a2f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -87,6 +87,18 @@ public static class Globals public const string CurrentGCState = nameof(CurrentGCState); public const string CFinalizeFillPointersLength = nameof(CFinalizeFillPointersLength); public const string TotalGenerationCount = nameof(TotalGenerationCount); + + public const string GCHeapMarkArray = nameof(GCHeapMarkArray); + public const string GCHeapNextSweepObj = nameof(GCHeapNextSweepObj); + public const string GCHeapBackgroundMinSavedAddr = nameof(GCHeapBackgroundMinSavedAddr); + public const string GCHeapBackgroundMaxSavedAddr = nameof(GCHeapBackgroundMaxSavedAddr); + public const string GCHeapAllocAllocated = nameof(GCHeapAllocAllocated); + public const string GCHeapEphemeralHeapSegment = nameof(GCHeapEphemeralHeapSegment); + public const string GCHeapCardTable = nameof(GCHeapCardTable); + public const string GCHeapFinalizeQueue = nameof(GCHeapFinalizeQueue); + public const string GCHeapGenerationTable = nameof(GCHeapGenerationTable); + public const string GCHeapSavedSweepEphemeralSeg = nameof(GCHeapSavedSweepEphemeralSeg); + public const string GCHeapSavedSweepEphemeralStart = nameof(GCHeapSavedSweepEphemeralStart); } public static class FieldNames { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs index 682ef9c8ecefcd..097e10bc3482e0 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs @@ -120,6 +120,65 @@ GCHeapData IGC.SVRGetHeapData(TargetPointer heapAddress) }; } + GCHeapData IGC.WKSGetHeapData() + { + if (GetGCType() != GCType.Workstation) + throw new InvalidOperationException("WKSGetHeapData is only valid for Workstation GC."); + + TargetPointer markArray = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.GCHeapMarkArray)); + TargetPointer nextSweepObj = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.GCHeapNextSweepObj)); + TargetPointer backgroundMinSavedAddr = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.GCHeapBackgroundMinSavedAddr)); + TargetPointer backgroundMaxSavedAddr = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.GCHeapBackgroundMaxSavedAddr)); + TargetPointer allocAllocated = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.GCHeapAllocAllocated)); + TargetPointer ephemeralHeapSegment = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.GCHeapEphemeralHeapSegment)); + TargetPointer cardTable = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.GCHeapCardTable)); + + TargetPointer finalizeQueue = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.GCHeapFinalizeQueue)); + Data.CFinalize finalize = _target.ProcessedData.GetOrAdd(finalizeQueue); + + uint generationTableLength = _target.ReadGlobal(Constants.Globals.TotalGenerationCount); + uint generationSize = _target.GetTypeInfo(DataType.Generation).Size ?? throw new InvalidOperationException("Type Generation has no size"); + TargetPointer generationTableArrayStart = _target.ReadGlobalPointer(Constants.Globals.GCHeapGenerationTable); + List generationTable = []; + for (uint i = 0; i < generationTableLength; i++) + { + TargetPointer generationAddress = generationTableArrayStart + i * generationSize; + generationTable.Add(_target.ProcessedData.GetOrAdd(generationAddress)); + } + IList generationDataList = generationTable.Select(gen => + new GCGenerationData() + { + StartSegment = gen.StartSegment, + AllocationStart = gen.AllocationStart ?? unchecked((ulong)-1), + AllocationContextPointer = gen.AllocationContext.Pointer, + AllocationContextLimit = gen.AllocationContext.Limit, + }).ToList(); + + TargetPointer? savedSweepEphemeralSeg = null; + TargetPointer? savedSweepEphemeralStart = null; + if (_target.TryReadGlobalPointer(Constants.Globals.GCHeapSavedSweepEphemeralSeg, out TargetPointer? savedSweepEphemeralSegPtr) && + _target.TryReadGlobalPointer(Constants.Globals.GCHeapSavedSweepEphemeralStart, out TargetPointer? savedSweepEphemeralStartPtr)) + { + savedSweepEphemeralSeg = _target.ReadPointer(savedSweepEphemeralSegPtr.Value); + savedSweepEphemeralStart = _target.ReadPointer(savedSweepEphemeralStartPtr.Value); + } + + return new GCHeapData() + { + MarkArray = markArray, + NextSweepObject = nextSweepObj, + BackGroundSavedMinAddress = backgroundMinSavedAddr, + BackGroundSavedMaxAddress = backgroundMaxSavedAddr, + AllocAllocated = allocAllocated, + EphemeralHeapSegment = ephemeralHeapSegment, + CardTable = cardTable, + GenerationTable = generationDataList.AsReadOnly(), + FillPointers = finalize.FillPointers, + SavedSweepEphemeralSegment = savedSweepEphemeralSeg ?? TargetPointer.Null, + SavedSweepEphemeralStart = savedSweepEphemeralStart ?? TargetPointer.Null, + }; + } + private GCType GetGCType() { string[] identifiers = ((IGC)this).GetGCIdentifiers(); diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs index 33ef8f2fa3c576..a216d02279f2f9 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs @@ -462,9 +462,9 @@ internal unsafe partial interface ISOSDacInterface [PreserveSig] int GetGCHeapList(uint count, [In, Out, MarshalUsing(CountElementName = nameof(count))] ClrDataAddress[] heaps, uint* pNeeded); // svr only [PreserveSig] - int GetGCHeapDetails(ClrDataAddress heap, DacpGcHeapDetails* details); // wks only + int GetGCHeapDetails(ClrDataAddress heap, DacpGcHeapDetails* details); [PreserveSig] - int GetGCHeapStaticData(/*struct DacpGcHeapDetails */ void* data); + int GetGCHeapStaticData(DacpGcHeapDetails* details); [PreserveSig] int GetHeapSegmentData(ClrDataAddress seg, /*struct DacpHeapSegmentData */ void* data); [PreserveSig] diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs index 5da8133c8d426d..dfdf69b32ad42d 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs @@ -969,8 +969,139 @@ int ISOSDacInterface.GetGCHeapDetails(ClrDataAddress heap, DacpGcHeapDetails* de return hr; } - int ISOSDacInterface.GetGCHeapStaticData(void* data) - => _legacyImpl is not null ? _legacyImpl.GetGCHeapStaticData(data) : HResults.E_NOTIMPL; + int ISOSDacInterface.GetGCHeapStaticData(DacpGcHeapDetails* details) + { + int hr = HResults.S_OK; + + try + { + if (details == null) + throw new ArgumentException(); + + IGC gc = _target.Contracts.GC; + string[] gcIdentifiers = gc.GetGCIdentifiers(); + + // doesn't make sense to call this on SVR mode + if (!gcIdentifiers.Contains(GCIdentifiers.Workstation)) + throw new ArgumentException(); + + GCHeapData heapData = gc.WKSGetHeapData(); + + details->heapAddr = 0; + + gc.GetGCBounds(out TargetPointer minAddress, out TargetPointer maxAddress); + details->lowest_address = minAddress.ToClrDataAddress(_target); + details->highest_address = maxAddress.ToClrDataAddress(_target); + + if (gcIdentifiers.Contains(GCIdentifiers.Background)) + { + details->current_c_gc_state = gc.GetCurrentGCStateAddress().ToClrDataAddress(_target); + details->mark_array = heapData.MarkArray.ToClrDataAddress(_target); + details->next_sweep_obj = heapData.NextSweepObject.ToClrDataAddress(_target); + details->background_saved_lowest_address = heapData.BackGroundSavedMinAddress.ToClrDataAddress(_target); + details->background_saved_highest_address = heapData.BackGroundSavedMaxAddress.ToClrDataAddress(_target); + } + else + { + details->current_c_gc_state = 0; + details->mark_array = unchecked((ulong)-1); + details->next_sweep_obj = 0; + details->background_saved_lowest_address = 0; + details->background_saved_highest_address = 0; + } + + // now get information specific to this heap (server mode gives us several heaps; we're getting + // information about only one of them. + details->alloc_allocated = heapData.AllocAllocated.ToClrDataAddress(_target); + details->ephemeral_heap_segment = heapData.EphemeralHeapSegment.ToClrDataAddress(_target); + details->card_table = heapData.CardTable.ToClrDataAddress(_target); + + if (gcIdentifiers.Contains(GCIdentifiers.Regions)) + { + // with regions, we don't have these variables anymore + // use special value -1 in saved_sweep_ephemeral_seg to signal the region case + details->saved_sweep_ephemeral_seg = unchecked((ulong)-1); + details->saved_sweep_ephemeral_start = 0; + } + else + { + if (gcIdentifiers.Contains(GCIdentifiers.Background)) + { + details->saved_sweep_ephemeral_seg = heapData.SavedSweepEphemeralSegment.ToClrDataAddress(_target); + details->saved_sweep_ephemeral_start = heapData.SavedSweepEphemeralStart.ToClrDataAddress(_target); + } + else + { + details->saved_sweep_ephemeral_seg = 0; + details->saved_sweep_ephemeral_start = 0; + } + } + + // get bounds for the different generations + for (int i = 0; i < GCConstants.DAC_NUMBERGENERATIONS && i < heapData.GenerationTable.Count; i++) + { + GCGenerationData genData = heapData.GenerationTable[i]; + details->generation_table[i].start_segment = genData.StartSegment.ToClrDataAddress(_target); + details->generation_table[i].allocation_start = genData.AllocationStart.ToClrDataAddress(_target); + details->generation_table[i].allocContextPtr = genData.AllocationContextPointer.ToClrDataAddress(_target); + details->generation_table[i].allocContextLimit = genData.AllocationContextLimit.ToClrDataAddress(_target); + } + + for (int i = 0; i < GCConstants.DAC_NUMBERGENERATIONS + 3 && i < heapData.FillPointers.Count; i++) + { + details->finalization_fill_pointers[i] = heapData.FillPointers[i].ToClrDataAddress(_target); + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyImpl is not null) + { + DacpGcHeapDetails detailsLocal = default; + int hrLocal = _legacyImpl.GetGCHeapStaticData(&detailsLocal); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + if (hr == HResults.S_OK) + { + Debug.Assert(details->heapAddr == detailsLocal.heapAddr, $"cDAC: {details->heapAddr:x}, DAC: {detailsLocal.heapAddr:x}"); + Debug.Assert(details->alloc_allocated == detailsLocal.alloc_allocated, $"cDAC: {details->alloc_allocated:x}, DAC: {detailsLocal.alloc_allocated:x}"); + Debug.Assert(details->mark_array == detailsLocal.mark_array, $"cDAC: {details->mark_array:x}, DAC: {detailsLocal.mark_array:x}"); + Debug.Assert(details->current_c_gc_state == detailsLocal.current_c_gc_state, $"cDAC: {details->current_c_gc_state:x}, DAC: {detailsLocal.current_c_gc_state:x}"); + Debug.Assert(details->next_sweep_obj == detailsLocal.next_sweep_obj, $"cDAC: {details->next_sweep_obj:x}, DAC: {detailsLocal.next_sweep_obj:x}"); + Debug.Assert(details->saved_sweep_ephemeral_seg == detailsLocal.saved_sweep_ephemeral_seg, $"cDAC: {details->saved_sweep_ephemeral_seg:x}, DAC: {detailsLocal.saved_sweep_ephemeral_seg:x}"); + Debug.Assert(details->saved_sweep_ephemeral_start == detailsLocal.saved_sweep_ephemeral_start, $"cDAC: {details->saved_sweep_ephemeral_start:x}, DAC: {detailsLocal.saved_sweep_ephemeral_start:x}"); + Debug.Assert(details->background_saved_lowest_address == detailsLocal.background_saved_lowest_address, $"cDAC: {details->background_saved_lowest_address:x}, DAC: {detailsLocal.background_saved_lowest_address:x}"); + Debug.Assert(details->background_saved_highest_address == detailsLocal.background_saved_highest_address, $"cDAC: {details->background_saved_highest_address:x}, DAC: {detailsLocal.background_saved_highest_address:x}"); + + // Verify generation table data + for (int i = 0; i < GCConstants.DAC_NUMBERGENERATIONS; i++) + { + Debug.Assert(details->generation_table[i].start_segment == detailsLocal.generation_table[i].start_segment, $"cDAC gen[{i}].start_segment: {details->generation_table[i].start_segment:x}, DAC: {detailsLocal.generation_table[i].start_segment:x}"); + Debug.Assert(details->generation_table[i].allocation_start == detailsLocal.generation_table[i].allocation_start, $"cDAC gen[{i}].allocation_start: {details->generation_table[i].allocation_start:x}, DAC: {detailsLocal.generation_table[i].allocation_start:x}"); + Debug.Assert(details->generation_table[i].allocContextPtr == detailsLocal.generation_table[i].allocContextPtr, $"cDAC gen[{i}].allocContextPtr: {details->generation_table[i].allocContextPtr:x}, DAC: {detailsLocal.generation_table[i].allocContextPtr:x}"); + Debug.Assert(details->generation_table[i].allocContextLimit == detailsLocal.generation_table[i].allocContextLimit, $"cDAC gen[{i}].allocContextLimit: {details->generation_table[i].allocContextLimit:x}, DAC: {detailsLocal.generation_table[i].allocContextLimit:x}"); + } + + Debug.Assert(details->ephemeral_heap_segment == detailsLocal.ephemeral_heap_segment, $"cDAC: {details->ephemeral_heap_segment:x}, DAC: {detailsLocal.ephemeral_heap_segment:x}"); + + // Verify finalization fill pointers + for (int i = 0; i < GCConstants.DAC_NUMBERGENERATIONS + 3; i++) + { + Debug.Assert(details->finalization_fill_pointers[i] == detailsLocal.finalization_fill_pointers[i], $"cDAC finalization_fill_pointers[{i}]: {details->finalization_fill_pointers[i]:x}, DAC: {detailsLocal.finalization_fill_pointers[i]:x}"); + } + + Debug.Assert(details->lowest_address == detailsLocal.lowest_address, $"cDAC: {details->lowest_address:x}, DAC: {detailsLocal.lowest_address:x}"); + Debug.Assert(details->highest_address == detailsLocal.highest_address, $"cDAC: {details->highest_address:x}, DAC: {detailsLocal.highest_address:x}"); + Debug.Assert(details->card_table == detailsLocal.card_table, $"cDAC: {details->card_table:x}, DAC: {detailsLocal.card_table:x}"); + } + } +#endif + + return hr; + } + int ISOSDacInterface.GetHandleEnum(void** ppHandleEnum) => _legacyImpl is not null ? _legacyImpl.GetHandleEnum(ppHandleEnum) : HResults.E_NOTIMPL; int ISOSDacInterface.GetHandleEnumForGC(uint gen, void** ppHandleEnum) From 230f17c63f732eb9bb25e1160ff37a4fba7c944a Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Tue, 26 Aug 2025 19:24:56 -0400 Subject: [PATCH 03/10] fix dereference --- .../Contracts/IGC.cs | 2 +- .../Contracts/GC_1.cs | 6 +++--- .../cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) 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 b90a16fdb50688..aff2f75baf0481 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 @@ -54,7 +54,7 @@ public interface IGC : IContract bool GetGCStructuresValid() => throw new NotImplementedException(); uint GetMaxGeneration() => throw new NotImplementedException(); void GetGCBounds(out TargetPointer minAddr, out TargetPointer maxAddr) => throw new NotImplementedException(); - TargetPointer GetCurrentGCStateAddress() => throw new NotImplementedException(); + uint GetCurrentGCState() => throw new NotImplementedException(); IEnumerable GetGCHeaps() => throw new NotImplementedException(); /* SVR only APIs */ diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs index 097e10bc3482e0..ad6a007709b338 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs @@ -64,11 +64,11 @@ void IGC.GetGCBounds(out TargetPointer minAddr, out TargetPointer maxAddr) maxAddr = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.GCHighestAddress)); } - TargetPointer IGC.GetCurrentGCStateAddress() + uint IGC.GetCurrentGCState() { if (!IsBackgroundGCEnabled()) - return TargetPointer.Null; - return _target.ReadGlobalPointer(Constants.Globals.CurrentGCState); + return 0; + return _target.Read(_target.ReadGlobalPointer(Constants.Globals.CurrentGCState)); } IEnumerable IGC.GetGCHeaps() diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs index dfdf69b32ad42d..4514d4526c8744 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs @@ -862,7 +862,7 @@ int ISOSDacInterface.GetGCHeapDetails(ClrDataAddress heap, DacpGcHeapDetails* de if (gcIdentifiers.Contains(GCIdentifiers.Background)) { - details->current_c_gc_state = gc.GetCurrentGCStateAddress().ToClrDataAddress(_target); + details->current_c_gc_state = gc.GetCurrentGCState(); details->mark_array = heapData.MarkArray.ToClrDataAddress(_target); details->next_sweep_obj = heapData.NextSweepObject.ToClrDataAddress(_target); details->background_saved_lowest_address = heapData.BackGroundSavedMinAddress.ToClrDataAddress(_target); @@ -995,7 +995,7 @@ int ISOSDacInterface.GetGCHeapStaticData(DacpGcHeapDetails* details) if (gcIdentifiers.Contains(GCIdentifiers.Background)) { - details->current_c_gc_state = gc.GetCurrentGCStateAddress().ToClrDataAddress(_target); + details->current_c_gc_state = gc.GetCurrentGCState(); details->mark_array = heapData.MarkArray.ToClrDataAddress(_target); details->next_sweep_obj = heapData.NextSweepObject.ToClrDataAddress(_target); details->background_saved_lowest_address = heapData.BackGroundSavedMinAddress.ToClrDataAddress(_target); From fc2ac0bef3803c6772f42307fdb50cf3e64c0cfd Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Tue, 26 Aug 2025 19:25:05 -0400 Subject: [PATCH 04/10] invert condition --- src/coreclr/gc/datadescriptor/datadescriptor.inc | 4 ++-- src/coreclr/gc/gc.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/gc/datadescriptor/datadescriptor.inc b/src/coreclr/gc/datadescriptor/datadescriptor.inc index c87c66f52a3de4..343182ef0822af 100644 --- a/src/coreclr/gc/datadescriptor/datadescriptor.inc +++ b/src/coreclr/gc/datadescriptor/datadescriptor.inc @@ -83,9 +83,9 @@ CDAC_GLOBAL_POINTER(GCHeapSavedSweepEphemeralStart, cdac_dataglobal_free_huge_regions = reinterpret_cast(&gc_heap::global_free_huge_regions); } #endif //USE_REGIONS -#ifndef BACKGROUND_GC +#ifdef BACKGROUND_GC g_build_variant |= build_variant_background_gc; -#endif //!BACKGROUND_GC +#endif //BACKGROUND_GC #ifdef DYNAMIC_HEAP_COUNT g_build_variant |= build_variant_dynamic_heap_count; #endif //DYNAMIC_HEAP_COUNT From 5fa214e876cec74dddcde6d57c62b18b938d794a Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Wed, 27 Aug 2025 20:51:07 -0400 Subject: [PATCH 05/10] add docs --- docs/design/datacontracts/GC.md | 288 +++++++++++++++++- .../Contracts/IGC.cs | 6 +- .../Contracts/GC_1.cs | 73 +++-- 3 files changed, 312 insertions(+), 55 deletions(-) diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index 2dbc60a30f6f9e..967934ff8c59fc 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -4,35 +4,107 @@ This contract is for getting information about the garbage collector configurati ## APIs of contract +```csharp +public readonly struct GCHeapData +{ + public TargetPointer MarkArray { get; init; } + public TargetPointer NextSweepObject { get; init; } + public TargetPointer BackGroundSavedMinAddress { get; init; } + public TargetPointer BackGroundSavedMaxAddress { get; init; } + + public TargetPointer AllocAllocated { get; init; } + public TargetPointer EphemeralHeapSegment { get; init; } + public TargetPointer CardTable { get; init; } + public IReadOnlyList GenerationTable { get; init; } + + public IReadOnlyList FillPointers { get; init; } + + // Fields only valid in segment GC builds + public TargetPointer SavedSweepEphemeralSegment { get; init; } + public TargetPointer SavedSweepEphemeralStart { get; init; } +} + +public readonly struct GCGenerationData +{ + public TargetPointer StartSegment { get; init; } + public TargetPointer AllocationStart { get; init; } + public TargetPointer AllocationContextPointer { get; init; } + public TargetPointer AllocationContextLimit { get; init; } +} +``` ```csharp // Return an array of strings identifying the GC type. // Current return values can include: // "workstation" or "server" // "segments" or "regions" + // "background" string[] GetGCIdentifiers(); + // Return the number of GC heaps uint GetGCHeapCount(); // Return true if the GC structure is valid, otherwise return false bool GetGCStructuresValid(); // Return the maximum generation of the current GC uint GetMaxGeneration(); + // Gets the minimum and maximum GC address + void GetGCBounds(out TargetPointer minAddr, out TargetPointer maxAddr); + // Gets the current GC state enum value + uint GetCurrentGCState(); + // Returns pointers to all GC heaps. + IEnumerable GetGCHeaps(); + + /* WKS only APIs */ + GCHeapData WKSGetHeapData(); + + /* SVR only APIs */ + GCHeapData SVRGetHeapData(TargetPointer heapAddress); ``` ## Version 1 Data descriptors used: -| Data Descriptor Name | Field | Meaning | -| --- | --- | --- | -| _(none)_ | | | +| Data Descriptor Name | Field | Source | Meaning | +| --- | --- | --- | --- | +| `GCHeap` | MarkArray | GC | Pointer to the heap's MarkArray (in sever builds) | +| `GCHeap` | NextSweepObj | GC | Pointer to the heap's NextSweepObj (in sever builds) | +| `GCHeap` | BackgroundMinSavedAddr | GC | Heap's background saved lowest address (in sever builds) | +| `GCHeap` | BackgroundMaxSavedAddr | GC | Heap's background saved highest address (in sever builds) | +| `GCHeap` | AllocAllocated | GC | Heap's highest address allocated by Alloc (in sever builds) | +| `GCHeap` | EphemeralHeapSegment | GC | Pointer to the heap's ephemeral heap segment (in sever builds) | +| `GCHeap` | CardTable | GC | Pointer to the heap's bookkeeping GC data structure (in sever builds) | +| `GCHeap` | FinalizeQueue | GC | Pointer to the heap's CFinalize data structure (in sever builds) | +| `GCHeap` | GenerationTable | GC | Pointer to the start of an array containing `"TotalGenerationCount"` `Generation` structures (in sever builds) | +| `GCHeap` | SavedSweepEphemeralSeg | GC | Pointer to the heap's saved sweep ephemeral segment (only in server builds with segment) | +| `GCHeap` | SavedSweepEphemeralStart | GC | Start of the heap's sweep ephemeral segment (only in server builds with segment) | +| `Generation` | AllocationContext | GC | A `GCAllocContext` struct | +| `Generation` | StartSegment | GC | Pointer to the start heap segment | +| `Generation` | AllocationStart | GC | Pointer to the allocation start | +| `CFinalize` | FillPointers | GC | Pointer to the start of an array containing `"CFinalizeFillPointersLength"` elements | Global variables used: -| Global Name | Type | Purpose | -| --- | --- | --- | -| `GCIdentifiers` | string | CSV string containing identifiers of the GC. Current values are "server", "workstation", "regions", and "segments" | -| `NumHeaps` | TargetPointer | Pointer to the number of heaps for server GC (int) | -| `StructureInvalidCount` | TargetPointer | Pointer to the count of invalid GC structures (int) | -| `MaxGeneration` | TargetPointer | Pointer to the maximum generation number (uint) | +| Global Name | Type | Source | Purpose | +| --- | --- | --- | --- | +| `GCIdentifiers` | string | GC | CSV string containing identifiers of the GC. Current values are "server", "workstation", "regions", and "segments" | +| `NumHeaps` | TargetPointer | GC | Pointer to the number of heaps for server GC (int) | +| `Heaps` | TargetPointer | GC | Pointer to an array of pointers to heaps | +| `StructureInvalidCount` | TargetPointer | GC | Pointer to the count of invalid GC structures (int) | +| `MaxGeneration` | TargetPointer | GC | Pointer to the maximum generation number (uint) | +| `TotalGenerationCount` | uint | GC | The total number of generations in the GC | +| `CFinalizeFillPointersLength` | uint | GC | The number of elements in the `CFinalize::FillPointers` array | +| `GCHeapMarkArray` | TargetPointer | GC | Pointer to the static heap's MarkArray (in workstation builds) | +| `GCHeapNextSweepObj` | TargetPointer | GC | Pointer to the static heap's NextSweepObj (in workstation builds) | +| `GCHeapBackgroundMinSavedAddr` | TargetPointer | GC | Background saved lowest address (in workstation builds) | +| `GCHeapBackgroundMaxSavedAddr` | TargetPointer | GC | Background saved highest address (in workstation builds) | +| `GCHeapAllocAllocated` | TargetPointer | GC | Highest address allocated by Alloc (in workstation builds) | +| `GCHeapEphemeralHeapSegment` | TargetPointer | GC | Pointer to an ephemeral heap segment (in workstation builds) | +| `GCHeapCardTable` | TargetPointer | GC | Pointer to the static heap's bookkeeping GC data structure (in workstation builds) | +| `GCHeapFinalizeQueue` | TargetPointer | GC | Pointer to the static heap's CFinalize data structure (in workstation builds) | +| `GCHeapGenerationTable` | TargetPointer | GC | Pointer to the start of an array containing `"TotalGenerationCount"` `Generation` structures (in workstation builds) | +| `GCHeapSavedSweepEphemeralSeg` | TargetPointer | GC | Pointer to the static heap's saved sweep ephemeral segment (in workstation builds with segment) | +| `GCHeapSavedSweepEphemeralStart` | TargetPointer | GC | Start of the static heap's sweep ephemeral segment (in workstation builds with segment) | +| `GCLowestAddress` | TargetPointer | VM | Lowest GC address as recorded by the VM/GC interface | +| `GCHighestAddress` | TargetPointer | VM | Highest GC address as recorded by the VM/GC interface | Contracts used: | Contract Name | @@ -46,13 +118,13 @@ Constants used: | `WRK_HEAP_COUNT` | uint | The number of heaps in the `workstation` GC type | `1` | ```csharp -GCHeapType GetGCIdentifiers() +GCHeapType IGC.GetGCIdentifiers() { - string gcIdentifiers = _target.ReadGlobalString("GCIdentifiers"); + string gcIdentifiers = target.ReadGlobalString("GCIdentifiers"); return gcIdentifiers.Split(", "); } -uint GetGCHeapCount() +uint IGC.GetGCHeapCount() { string[] gcIdentifiers = GetGCIdentifiers() if (gcType.Contains("workstation")) @@ -68,16 +140,204 @@ uint GetGCHeapCount() throw new NotImplementedException("Unknown GC heap type"); } -bool GetGCStructuresValid() +bool IGC.GetGCStructuresValid() { TargetPointer pInvalidCount = target.ReadGlobalPointer("StructureInvalidCount"); int invalidCount = target.Read(pInvalidCount); return invalidCount == 0; // Structures are valid if the count of invalid structures is zero } -uint GetMaxGeneration() +uint IGC.GetMaxGeneration() { TargetPointer pMaxGeneration = target.ReadGlobalPointer("MaxGeneration"); return target.Read(pMaxGeneration); } + +void IGC.GetGCBounds(out TargetPointer minAddr, out TargetPointer maxAddr) +{ + minAddr = target.ReadPointer(target.ReadGlobalPointer("GCLowestAddress")); + maxAddr = target.ReadPointer(target.ReadGlobalPointer("GCHighestAddress")); +} + +uint IGC.GetCurrentGCState() +{ + string[] gcIdentifiers = GetGCIdentifiers(); + if (gcType.Contains("background")) + { + return target.Read(target.ReadGlobalPointer("CurrentGCState")); + } + + return 0; +} + +IEnumerable IGC.GetGCHeaps() +{ + string[] gcIdentifiers = GetGCIdentifiers(); + if (!gcType.Contains("server")) + yield break; // Only server GC has multiple heaps + + uint heapCount = GetGCHeapCount(); + TargetPointer heapTable = TargetPointer.ReadPointer(target.ReadGlobalPointer("Heaps")); + // heapTable is an array of pointers to heaps + // it must be heapCount in length + for (uint i = 0; i < heapCount; i++) + { + yield return target.ReadPointer(heapTable + (i * target.PointerSize)); + } +} ``` + +workstation GC only APIs +```csharp +GCHeapData IGC.WKSGetHeapData(TargetPointer heapAddress) +{ + string[] gcIdentifiers = GetGCIdentifiers(); + if (!gcType.Contains("workstation")) + throw new InvalidOperationException(); + + GCHeapData data; + + // Read fields directly from globals + data.MarkArray = target.ReadPointer(target.ReadGlobalPointer("GCHeapMarkArray")); + data.NextSweepObj = target.ReadPointer(target.ReadGlobalPointer("GCHeapNextSweepObj")); + data.BackgroundMinSavedAddr = target.ReadPointer(target.ReadGlobalPointer("GCHeapBackgroundMinSavedAddr")); + data.BackgroundMaxSavedAddr = target.ReadPointer(target.ReadGlobalPointer("GCHeapBackgroundMaxSavedAddr")); + data.AllocAllocated = target.ReadPointer(target.ReadGlobalPointer("GCHeapAllocAllocated")); + data.EphemeralHeapSegment = target.ReadPointer(target.ReadGlobalPointer("GCHeapEphemeralHeapSegment")); + data.CardTable = target.ReadPointer(target.ReadGlobalPointer("GCHeapCardTable")); + + if (target.TryReadGlobalPointer("GCHeapSavedSweepEphemeralSeg", out TargetPointer? savedSweepEphemeralSegPtr)) + { + data.SavedSweepEphemeralSeg = target.ReadPointer(savedSweepEphemeralSegPtr.Value); + } + else + { + data.SavedSweepEphemeralSeg = 0; + } + + if (target.TryReadGlobalPointer("GCHeapSavedSweepEphemeralStart", out TargetPointer? savedSweepEphemeralStartPtr)) + { + data.SavedSweepEphemeralStart = target.ReadPointer(savedSweepEphemeralStartPtr.Value); + } + else + { + data.SavedSweepEphemeralStart = 0; + } + + // Read GenerationTable + TargetPointer generationTableArrayStart = target.ReadGlobalPointer("GCHeapGenerationTable"); + uint generationTableLength = target.ReadGlobal("TotalGenerationCount"); + uint generationSize = target.GetTypeInfo(DataType.Generation).Size; + + List generationTable = [] + for (uint i = 0; i < generationTableLength; i++) + { + GCGenerationData generationData; + TargetPointer generationAddress = generationTableArrayStart + (i * generationSize); + generationData.StartSegment = target.ReadPointer(generationAddress + /* Generation::StartSegment offset */); + if (/* Generation::AllocationStart is present */) + generationData.AllocationStart = target.ReadPointer(generationAddress + /* Generation::AllocationStart offset */) + else + generationData.AllocationStart = -1; + + generationData.AllocationContextPointer = + target.ReadPointer(generationAddress + /* Generation::AllocationContext offset */ + /* GCAllocContext::Pointer offset */); + generationData.AllocationContextLimit = + target.ReadPointer(generationAddress + /* Generation::AllocationContext offset */ + /* GCAllocContext::Limit offset */); + + generationTable.Add(generationData); + } + data.GenerationTable = generationTable; + + // Read finalize queue from global and CFinalize offsets + TargetPointer finalizeQueue = target.ReadPointer(target.ReadGlobalPointer("GCHeapFinalizeQueue")); + TargetPointer fillPointersArrayStart = finalizeQueue + /* CFinalize::FillPointers offset */; + uint fillPointersLength = target.ReadGlobal("CFinalizeFillPointersLength"); + + List fillPointers = []; + for (uint i = 0; i < fillPointersLength; i++) + fillPointers[i] = target.ReadPointer(fillPointersArrayStart + (i * target.PointerSize)); + + data.FillPointers = fillPointers; + + return data; +} +``` + +server GC only APIs +```csharp +GCHeapData IGC.SVRGetHeapData(TargetPointer heapAddress) +{ + string[] gcIdentifiers = GetGCIdentifiers(); + if (!gcType.Contains("server")) + throw new InvalidOperationException(); + + GCHeapData data; + + // Read fields directly from heap + data.MarkArray = target.ReadPointer(heapAddress + /* GCHeap::MarkArray offset */); + data.NextSweepObj = target.ReadPointer(heapAddress + /* GCHeap::NextSweepObj offset */); + data.BackgroundMinSavedAddr = target.ReadPointer(heapAddress + /* GCHeap::BackgroundMinSavedAddr offset */); + data.BackgroundMaxSavedAddr = target.ReadPointer(heapAddress + /* GCHeap::BackgroundMaxSavedAddr offset */); + data.AllocAllocated = target.ReadPointer(heapAddress + /* GCHeap::AllocAllocated offset */); + data.EphemeralHeapSegment = target.ReadPointer(heapAddress + /* GCHeap::EphemeralHeapSegment offset */); + data.CardTable = target.ReadPointer(heapAddress + /* GCHeap::CardTable offset */); + + if (/* GCHeap::SavedSweepEphemeralSeg is present */) + { + data.SavedSweepEphemeralSeg = target.ReadPointer(heapAddress + /* GCHeap::SavedSweepEphemeralSeg offset */); + } + else + { + data.SavedSweepEphemeralSeg = 0; + } + + if (/* GCHeap::SavedSweepEphemeralStart is present */) + { + data.SavedSweepEphemeralStart = target.ReadPointer(heapAddress + /* GCHeap::SavedSweepEphemeralStart offset */); + } + else + { + data.SavedSweepEphemeralStart = 0; + } + + // Read GenerationTable + TargetPointer generationTableArrayStart = heapAddress + /* GCHeap::GenerationTable offset */; + uint generationTableLength = target.ReadGlobal("TotalGenerationCount"); + uint generationSize = target.GetTypeInfo(DataType.Generation).Size; + + List generationTable = [] + for (uint i = 0; i < generationTableLength; i++) + { + GCGenerationData generationData; + TargetPointer generationAddress = generationTableArrayStart + (i * generationSize); + generationData.StartSegment = target.ReadPointer(generationAddress + /* Generation::StartSegment offset */); + if (/* Generation::AllocationStart is present */) + generationData.AllocationStart = target.ReadPointer(generationAddress + /* Generation::AllocationStart offset */) + else + generationData.AllocationStart = -1; + + generationData.AllocationContextPointer = + target.ReadPointer(generationAddress + /* Generation::AllocationContext offset */ + /* GCAllocContext::Pointer offset */); + generationData.AllocationContextLimit = + target.ReadPointer(generationAddress + /* Generation::AllocationContext offset */ + /* GCAllocContext::Limit offset */); + + generationTable.Add(generationData); + } + data.GenerationTable = generationTable; + + // Read finalize queue from global and CFinalize offsets + TargetPointer finalizeQueue = target.ReadPointer(heapAddress + /* GCHeap::FinalizeQueue offset */); + TargetPointer fillPointersArrayStart = finalizeQueue + /* CFinalize::FillPointers offset */; + uint fillPointersLength = target.ReadGlobal("CFinalizeFillPointersLength"); + + List fillPointers = []; + for (uint i = 0; i < fillPointersLength; i++) + fillPointers[i] = target.ReadPointer(fillPointersArrayStart + (i * target.PointerSize)); + + data.FillPointers = fillPointers; + + return data; +} +``` + 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 aff2f75baf0481..8a349d9d827e54 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 @@ -57,11 +57,11 @@ public interface IGC : IContract uint GetCurrentGCState() => throw new NotImplementedException(); IEnumerable GetGCHeaps() => throw new NotImplementedException(); - /* SVR only APIs */ - GCHeapData SVRGetHeapData(TargetPointer heapAddress) => throw new NotImplementedException(); - /* WKS only APIs */ GCHeapData WKSGetHeapData() => throw new NotImplementedException(); + + /* SVR only APIs */ + GCHeapData SVRGetHeapData(TargetPointer heapAddress) => throw new NotImplementedException(); } public readonly struct GC : IGC diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs index ad6a007709b338..8ac4db73ff0ba9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs @@ -74,52 +74,16 @@ uint IGC.GetCurrentGCState() IEnumerable IGC.GetGCHeaps() { if (GetGCType() != GCType.Server) - { yield break; // Only server GC has multiple heaps - } uint heapCount = ((IGC)this).GetGCHeapCount(); - TargetPointer ppHeapTable = _target.ReadGlobalPointer(Constants.Globals.Heaps); - TargetPointer pHeapTable = _target.ReadPointer(ppHeapTable); + TargetPointer heapTable = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.Heaps)); for (uint i = 0; i < heapCount; i++) { - yield return _target.ReadPointer(pHeapTable + (i * (uint)_target.PointerSize)); + yield return _target.ReadPointer(heapTable + (i * (uint)_target.PointerSize)); } } - GCHeapData IGC.SVRGetHeapData(TargetPointer heapAddress) - { - if (GetGCType() != GCType.Server) - throw new InvalidOperationException("GetHeapData is only valid for Server GC."); - - Data.GCHeap_svr heap = _target.ProcessedData.GetOrAdd(heapAddress); - Data.CFinalize finalize = _target.ProcessedData.GetOrAdd(heap.FinalizeQueue); - - IList generationDataList = heap.GenerationTable.Select(gen => - new GCGenerationData() - { - StartSegment = gen.StartSegment, - AllocationStart = gen.AllocationStart ?? unchecked((ulong)-1), - AllocationContextPointer = gen.AllocationContext.Pointer, - AllocationContextLimit = gen.AllocationContext.Limit, - }).ToList(); - - return new GCHeapData() - { - MarkArray = heap.MarkArray, - NextSweepObject = heap.NextSweepObj, - BackGroundSavedMinAddress = heap.BackgroundMinSavedAddr, - BackGroundSavedMaxAddress = heap.BackgroundMaxSavedAddr, - AllocAllocated = heap.AllocAllocated, - EphemeralHeapSegment = heap.EphemeralHeapSegment, - CardTable = heap.CardTable, - GenerationTable = generationDataList.AsReadOnly(), - FillPointers = finalize.FillPointers, - SavedSweepEphemeralSegment = heap.SavedSweepEphemeralSeg ?? TargetPointer.Null, - SavedSweepEphemeralStart = heap.SavedSweepEphemeralStart ?? TargetPointer.Null, - }; - } - GCHeapData IGC.WKSGetHeapData() { if (GetGCType() != GCType.Workstation) @@ -179,6 +143,39 @@ GCHeapData IGC.WKSGetHeapData() }; } + GCHeapData IGC.SVRGetHeapData(TargetPointer heapAddress) + { + if (GetGCType() != GCType.Server) + throw new InvalidOperationException("GetHeapData is only valid for Server GC."); + + Data.GCHeap_svr heap = _target.ProcessedData.GetOrAdd(heapAddress); + Data.CFinalize finalize = _target.ProcessedData.GetOrAdd(heap.FinalizeQueue); + + IList generationDataList = heap.GenerationTable.Select(gen => + new GCGenerationData() + { + StartSegment = gen.StartSegment, + AllocationStart = gen.AllocationStart ?? unchecked((ulong)-1), + AllocationContextPointer = gen.AllocationContext.Pointer, + AllocationContextLimit = gen.AllocationContext.Limit, + }).ToList(); + + return new GCHeapData() + { + MarkArray = heap.MarkArray, + NextSweepObject = heap.NextSweepObj, + BackGroundSavedMinAddress = heap.BackgroundMinSavedAddr, + BackGroundSavedMaxAddress = heap.BackgroundMaxSavedAddr, + AllocAllocated = heap.AllocAllocated, + EphemeralHeapSegment = heap.EphemeralHeapSegment, + CardTable = heap.CardTable, + GenerationTable = generationDataList.AsReadOnly(), + FillPointers = finalize.FillPointers, + SavedSweepEphemeralSegment = heap.SavedSweepEphemeralSeg ?? TargetPointer.Null, + SavedSweepEphemeralStart = heap.SavedSweepEphemeralStart ?? TargetPointer.Null, + }; + } + private GCType GetGCType() { string[] identifiers = ((IGC)this).GetGCIdentifiers(); From c956bb47004778eabe25afb86411485677644996 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Wed, 27 Aug 2025 20:52:55 -0400 Subject: [PATCH 06/10] update --- docs/design/datacontracts/GC.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index 967934ff8c59fc..3b855ff1b98844 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -81,6 +81,8 @@ Data descriptors used: | `Generation` | StartSegment | GC | Pointer to the start heap segment | | `Generation` | AllocationStart | GC | Pointer to the allocation start | | `CFinalize` | FillPointers | GC | Pointer to the start of an array containing `"CFinalizeFillPointersLength"` elements | +| `GCAllocContext` | Pointer | VM | Current GCAllocContext pointer | +| `GCAllocContext` | Limit | VM | Pointer to the GCAllocContext limit | Global variables used: | Global Name | Type | Source | Purpose | From ac4bfee10d5ba571db447b035e2acc61dd70327d Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Wed, 27 Aug 2025 20:53:47 -0400 Subject: [PATCH 07/10] md lint --- docs/design/datacontracts/GC.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index 3b855ff1b98844..da5ab2a3a2e87c 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -241,7 +241,7 @@ GCHeapData IGC.WKSGetHeapData(TargetPointer heapAddress) generationData.AllocationStart = target.ReadPointer(generationAddress + /* Generation::AllocationStart offset */) else generationData.AllocationStart = -1; - + generationData.AllocationContextPointer = target.ReadPointer(generationAddress + /* Generation::AllocationContext offset */ + /* GCAllocContext::Pointer offset */); generationData.AllocationContextLimit = @@ -318,7 +318,7 @@ GCHeapData IGC.SVRGetHeapData(TargetPointer heapAddress) generationData.AllocationStart = target.ReadPointer(generationAddress + /* Generation::AllocationStart offset */) else generationData.AllocationStart = -1; - + generationData.AllocationContextPointer = target.ReadPointer(generationAddress + /* Generation::AllocationContext offset */ + /* GCAllocContext::Pointer offset */); generationData.AllocationContextLimit = From 80d6f1f6eb4f316b5e8682c217b2d6e00cd0e527 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Wed, 27 Aug 2025 21:34:38 -0400 Subject: [PATCH 08/10] md lint --- docs/design/datacontracts/GC.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index da5ab2a3a2e87c..01562fafebafd5 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -102,7 +102,7 @@ Global variables used: | `GCHeapEphemeralHeapSegment` | TargetPointer | GC | Pointer to an ephemeral heap segment (in workstation builds) | | `GCHeapCardTable` | TargetPointer | GC | Pointer to the static heap's bookkeeping GC data structure (in workstation builds) | | `GCHeapFinalizeQueue` | TargetPointer | GC | Pointer to the static heap's CFinalize data structure (in workstation builds) | -| `GCHeapGenerationTable` | TargetPointer | GC | Pointer to the start of an array containing `"TotalGenerationCount"` `Generation` structures (in workstation builds) | +| `GCHeapGenerationTable` | TargetPointer | GC | Pointer to the start of an array containing `"TotalGenerationCount"` `Generation` structures (in workstation builds) | | `GCHeapSavedSweepEphemeralSeg` | TargetPointer | GC | Pointer to the static heap's saved sweep ephemeral segment (in workstation builds with segment) | | `GCHeapSavedSweepEphemeralStart` | TargetPointer | GC | Start of the static heap's sweep ephemeral segment (in workstation builds with segment) | | `GCLowestAddress` | TargetPointer | VM | Lowest GC address as recorded by the VM/GC interface | From 9c6e1044e0ef96c156123b15ab9265f2f221ec8a Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Thu, 28 Aug 2025 18:19:25 -0400 Subject: [PATCH 09/10] refactor some changes --- .../Contracts/GC_1.cs | 66 ++++++++++--------- .../Data/GC/CFinalize.cs | 10 +-- .../Data/GC/GCHeap.cs | 15 +---- .../Legacy/SOSDacImpl.cs | 4 +- 4 files changed, 42 insertions(+), 53 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs index 8ac4db73ff0ba9..5459d6f1d082b8 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -99,24 +100,7 @@ GCHeapData IGC.WKSGetHeapData() TargetPointer finalizeQueue = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.GCHeapFinalizeQueue)); Data.CFinalize finalize = _target.ProcessedData.GetOrAdd(finalizeQueue); - - uint generationTableLength = _target.ReadGlobal(Constants.Globals.TotalGenerationCount); - uint generationSize = _target.GetTypeInfo(DataType.Generation).Size ?? throw new InvalidOperationException("Type Generation has no size"); TargetPointer generationTableArrayStart = _target.ReadGlobalPointer(Constants.Globals.GCHeapGenerationTable); - List generationTable = []; - for (uint i = 0; i < generationTableLength; i++) - { - TargetPointer generationAddress = generationTableArrayStart + i * generationSize; - generationTable.Add(_target.ProcessedData.GetOrAdd(generationAddress)); - } - IList generationDataList = generationTable.Select(gen => - new GCGenerationData() - { - StartSegment = gen.StartSegment, - AllocationStart = gen.AllocationStart ?? unchecked((ulong)-1), - AllocationContextPointer = gen.AllocationContext.Pointer, - AllocationContextLimit = gen.AllocationContext.Limit, - }).ToList(); TargetPointer? savedSweepEphemeralSeg = null; TargetPointer? savedSweepEphemeralStart = null; @@ -136,8 +120,8 @@ GCHeapData IGC.WKSGetHeapData() AllocAllocated = allocAllocated, EphemeralHeapSegment = ephemeralHeapSegment, CardTable = cardTable, - GenerationTable = generationDataList.AsReadOnly(), - FillPointers = finalize.FillPointers, + GenerationTable = GetGenerationData(generationTableArrayStart).AsReadOnly(), + FillPointers = GetFillPointers(finalize).AsReadOnly(), SavedSweepEphemeralSegment = savedSweepEphemeralSeg ?? TargetPointer.Null, SavedSweepEphemeralStart = savedSweepEphemeralStart ?? TargetPointer.Null, }; @@ -151,15 +135,6 @@ GCHeapData IGC.SVRGetHeapData(TargetPointer heapAddress) Data.GCHeap_svr heap = _target.ProcessedData.GetOrAdd(heapAddress); Data.CFinalize finalize = _target.ProcessedData.GetOrAdd(heap.FinalizeQueue); - IList generationDataList = heap.GenerationTable.Select(gen => - new GCGenerationData() - { - StartSegment = gen.StartSegment, - AllocationStart = gen.AllocationStart ?? unchecked((ulong)-1), - AllocationContextPointer = gen.AllocationContext.Pointer, - AllocationContextLimit = gen.AllocationContext.Limit, - }).ToList(); - return new GCHeapData() { MarkArray = heap.MarkArray, @@ -169,13 +144,44 @@ GCHeapData IGC.SVRGetHeapData(TargetPointer heapAddress) AllocAllocated = heap.AllocAllocated, EphemeralHeapSegment = heap.EphemeralHeapSegment, CardTable = heap.CardTable, - GenerationTable = generationDataList.AsReadOnly(), - FillPointers = finalize.FillPointers, + GenerationTable = GetGenerationData(heap.GenerationTable).AsReadOnly(), + FillPointers = GetFillPointers(finalize).AsReadOnly(), SavedSweepEphemeralSegment = heap.SavedSweepEphemeralSeg ?? TargetPointer.Null, SavedSweepEphemeralStart = heap.SavedSweepEphemeralStart ?? TargetPointer.Null, }; } + private List GetGenerationData(TargetPointer generationTableArrayStart) + { + uint generationTableLength = _target.ReadGlobal(Constants.Globals.TotalGenerationCount); + uint generationSize = _target.GetTypeInfo(DataType.Generation).Size ?? throw new InvalidOperationException("Type Generation has no size"); + List generationTable = []; + for (uint i = 0; i < generationTableLength; i++) + { + TargetPointer generationAddress = generationTableArrayStart + i * generationSize; + generationTable.Add(_target.ProcessedData.GetOrAdd(generationAddress)); + } + List generationDataList = generationTable.Select(gen => + new GCGenerationData() + { + StartSegment = gen.StartSegment, + AllocationStart = gen.AllocationStart, + AllocationContextPointer = gen.AllocationContext.Pointer, + AllocationContextLimit = gen.AllocationContext.Limit, + }).ToList(); + return generationDataList; + } + + private List GetFillPointers(Data.CFinalize cFinalize) + { + uint fillPointersLength = _target.ReadGlobal(Constants.Globals.CFinalizeFillPointersLength); + TargetPointer fillPointersArrayStart = cFinalize.FillPointers; + List fillPointers = []; + for (uint i = 0; i < fillPointersLength; i++) + fillPointers.Add(_target.ReadPointer(fillPointersArrayStart + i * (ulong)_target.PointerSize)); + return fillPointers; + } + private GCType GetGCType() { string[] identifiers = ((IGC)this).GetGCIdentifiers(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/CFinalize.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/CFinalize.cs index f974eea5dc6977..dac7cbaec509fb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/CFinalize.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/CFinalize.cs @@ -12,14 +12,8 @@ public CFinalize(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.CFinalize); - uint fillPointersLength = target.ReadGlobal(Constants.Globals.CFinalizeFillPointersLength); - TargetPointer fillPointersArrayStart = address + (ulong)type.Fields[nameof(FillPointers)].Offset; - - TargetPointer[] fillPointers = new TargetPointer[fillPointersLength]; - for (uint i = 0; i < fillPointersLength; i++) - fillPointers[i] = target.ReadPointer(fillPointersArrayStart + i * (ulong)target.PointerSize); - FillPointers = fillPointers.AsReadOnly(); + FillPointers = address + (ulong)type.Fields[nameof(FillPointers)].Offset; } - public IReadOnlyList FillPointers { get; } + public TargetPointer FillPointers { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/GCHeap.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/GCHeap.cs index 27972048a32d0d..350026322cb287 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/GCHeap.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/GCHeap.cs @@ -21,18 +21,7 @@ public GCHeap_svr(Target target, TargetPointer address) EphemeralHeapSegment = target.ReadPointer(address + (ulong)type.Fields[nameof(EphemeralHeapSegment)].Offset); CardTable = target.ReadPointer(address + (ulong)type.Fields[nameof(CardTable)].Offset); FinalizeQueue = target.ReadPointer(address + (ulong)type.Fields[nameof(FinalizeQueue)].Offset); - - uint generationTableLength = target.ReadGlobal(Constants.Globals.TotalGenerationCount); - TargetPointer generationTableArrayStart = address + (ulong)type.Fields[nameof(GenerationTable)].Offset; - uint generationSize = target.GetTypeInfo(DataType.Generation).Size ?? throw new InvalidOperationException("Type Generation has no size"); - - List generationTable = []; - for (uint i = 0; i < generationTableLength; i++) - { - TargetPointer generationAddress = generationTableArrayStart + i * generationSize; - generationTable.Add(target.ProcessedData.GetOrAdd(generationAddress)); - } - GenerationTable = generationTable.AsReadOnly(); + GenerationTable = address + (ulong)type.Fields[nameof(GenerationTable)].Offset; // Fields only exist segment GC builds if (type.Fields.ContainsKey(nameof(SavedSweepEphemeralSeg))) @@ -49,7 +38,7 @@ public GCHeap_svr(Target target, TargetPointer address) public TargetPointer EphemeralHeapSegment { get; } public TargetPointer CardTable { get; } public TargetPointer FinalizeQueue { get; } - public IReadOnlyList GenerationTable { get; } + public TargetPointer GenerationTable { get; } public TargetPointer? SavedSweepEphemeralSeg { get; } public TargetPointer? SavedSweepEphemeralStart { get; } diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs index 4514d4526c8744..b00e7135133325 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs @@ -909,7 +909,7 @@ int ISOSDacInterface.GetGCHeapDetails(ClrDataAddress heap, DacpGcHeapDetails* de { GCGenerationData genData = heapData.GenerationTable[i]; details->generation_table[i].start_segment = genData.StartSegment.ToClrDataAddress(_target); - details->generation_table[i].allocation_start = genData.AllocationStart.ToClrDataAddress(_target); + details->generation_table[i].allocation_start = (genData.AllocationStart ?? 0).ToClrDataAddress(_target); details->generation_table[i].allocContextPtr = genData.AllocationContextPointer.ToClrDataAddress(_target); details->generation_table[i].allocContextLimit = genData.AllocationContextLimit.ToClrDataAddress(_target); } @@ -1042,7 +1042,7 @@ int ISOSDacInterface.GetGCHeapStaticData(DacpGcHeapDetails* details) { GCGenerationData genData = heapData.GenerationTable[i]; details->generation_table[i].start_segment = genData.StartSegment.ToClrDataAddress(_target); - details->generation_table[i].allocation_start = genData.AllocationStart.ToClrDataAddress(_target); + details->generation_table[i].allocation_start = (genData.AllocationStart ?? 0).ToClrDataAddress(_target); details->generation_table[i].allocContextPtr = genData.AllocationContextPointer.ToClrDataAddress(_target); details->generation_table[i].allocContextLimit = genData.AllocationContextLimit.ToClrDataAddress(_target); } From 26d783349e077d6b61e8b9d05a517c17a43afbfe Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Thu, 28 Aug 2025 19:41:08 -0400 Subject: [PATCH 10/10] fix nullability --- .../Contracts/GC_1.cs | 2 +- .../managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs index 5459d6f1d082b8..08183448c0c32d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs @@ -165,7 +165,7 @@ private List GetGenerationData(TargetPointer generationTableAr new GCGenerationData() { StartSegment = gen.StartSegment, - AllocationStart = gen.AllocationStart, + AllocationStart = gen.AllocationStart ?? 0, AllocationContextPointer = gen.AllocationContext.Pointer, AllocationContextLimit = gen.AllocationContext.Limit, }).ToList(); diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs index b00e7135133325..4514d4526c8744 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs @@ -909,7 +909,7 @@ int ISOSDacInterface.GetGCHeapDetails(ClrDataAddress heap, DacpGcHeapDetails* de { GCGenerationData genData = heapData.GenerationTable[i]; details->generation_table[i].start_segment = genData.StartSegment.ToClrDataAddress(_target); - details->generation_table[i].allocation_start = (genData.AllocationStart ?? 0).ToClrDataAddress(_target); + details->generation_table[i].allocation_start = genData.AllocationStart.ToClrDataAddress(_target); details->generation_table[i].allocContextPtr = genData.AllocationContextPointer.ToClrDataAddress(_target); details->generation_table[i].allocContextLimit = genData.AllocationContextLimit.ToClrDataAddress(_target); } @@ -1042,7 +1042,7 @@ int ISOSDacInterface.GetGCHeapStaticData(DacpGcHeapDetails* details) { GCGenerationData genData = heapData.GenerationTable[i]; details->generation_table[i].start_segment = genData.StartSegment.ToClrDataAddress(_target); - details->generation_table[i].allocation_start = (genData.AllocationStart ?? 0).ToClrDataAddress(_target); + details->generation_table[i].allocation_start = genData.AllocationStart.ToClrDataAddress(_target); details->generation_table[i].allocContextPtr = genData.AllocationContextPointer.ToClrDataAddress(_target); details->generation_table[i].allocContextLimit = genData.AllocationContextLimit.ToClrDataAddress(_target); }