diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index 2dbc60a30f6f9e..01562fafebafd5 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -4,35 +4,109 @@ 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 | +| `GCAllocContext` | Pointer | VM | Current GCAllocContext pointer | +| `GCAllocContext` | Limit | VM | Pointer to the GCAllocContext limit | 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 +120,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 +142,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/coreclr/gc/datadescriptor/datadescriptor.h b/src/coreclr/gc/datadescriptor/datadescriptor.h index 8eb2474ff71547..d21cccb71d8956 100644 --- a/src/coreclr/gc/datadescriptor/datadescriptor.h +++ b/src/coreclr/gc/datadescriptor/datadescriptor.h @@ -28,10 +28,43 @@ 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 { -#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 +#endif // SERVER_GC + + 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) + + GC_HEAP_FIELD(GenerationTable, generation_table) + +#ifndef USE_REGIONS + GC_HEAP_FIELD(SavedSweepEphemeralSeg, saved_sweep_ephemeral_seg) + GC_HEAP_FIELD(SavedSweepEphemeralStart, saved_sweep_ephemeral_start) +#endif // !USE_REGIONS +}; + +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..343182ef0822af 100644 --- a/src/coreclr/gc/datadescriptor/datadescriptor.inc +++ b/src/coreclr/gc/datadescriptor/datadescriptor.inc @@ -10,10 +10,61 @@ 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) + +#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 @@ -30,7 +81,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, background)) +#else CDAC_GLOBAL_STRING(GCIdentifiers, GC_IDENTIFIER(GC_TYPE, HEAP_TYPE)) +#endif // BACKGROUND_GC CDAC_GLOBAL_POINTER(MaxGeneration, &::g_max_generation) CDAC_GLOBAL_POINTER(StructureInvalidCount, &GCScan::m_GcStructuresInvalidCnt) @@ -40,4 +96,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..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 @@ -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,19 @@ 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(); + uint GetCurrentGCState() => throw new NotImplementedException(); IEnumerable GetGCHeaps() => 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.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..3ea7cdfa963a2f 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,21 @@ 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 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 3b669a412c3436..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 @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -57,20 +59,127 @@ 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)); + } + + uint IGC.GetCurrentGCState() + { + if (!IsBackgroundGCEnabled()) + return 0; + return _target.Read(_target.ReadGlobalPointer(Constants.Globals.CurrentGCState)); + } + 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.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); + TargetPointer generationTableArrayStart = _target.ReadGlobalPointer(Constants.Globals.GCHeapGenerationTable); + + 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 = GetGenerationData(generationTableArrayStart).AsReadOnly(), + FillPointers = GetFillPointers(finalize).AsReadOnly(), + SavedSweepEphemeralSegment = savedSweepEphemeralSeg ?? TargetPointer.Null, + SavedSweepEphemeralStart = savedSweepEphemeralStart ?? TargetPointer.Null, + }; + } + + 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); + + 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 = 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 ?? 0, + 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() @@ -89,4 +198,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..dac7cbaec509fb --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/CFinalize.cs @@ -0,0 +1,19 @@ +// 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); + + FillPointers = address + (ulong)type.Fields[nameof(FillPointers)].Offset; + } + + 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 new file mode 100644 index 00000000000000..350026322cb287 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/GCHeap.cs @@ -0,0 +1,45 @@ +// 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); + GenerationTable = address + (ulong)type.Fields[nameof(GenerationTable)].Offset; + + // 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 TargetPointer 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..a216d02279f2f9 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,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, /*struct DacpGcHeapDetails */ void* 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 ea56a52440ee2e..4514d4526c8744 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,10 +836,272 @@ 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.GetGCHeapStaticData(void* data) - => _legacyImpl is not null ? _legacyImpl.GetGCHeapStaticData(data) : 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.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); + 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(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.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); + 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)