From 1e7c9dd88985e5d6c45213df3b92e4bd529ab6db Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Sat, 29 Aug 2026 21:21:19 -0400 Subject: [PATCH] [cDAC] Tolerate missing WKS card-table slot Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9a58113d-e1d8-4ed2-9113-9f1de69cd49e --- .../Contracts/GC/GCHeapWKS.cs | 5 +- .../SOSDacImpl.cs | 3 +- .../managed/cdac/tests/UnitTests/GCTests.cs | 72 +++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GCHeapWKS.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GCHeapWKS.cs index 20ab0a9fd28290..ad1c56418a3872 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GCHeapWKS.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GCHeapWKS.cs @@ -17,7 +17,10 @@ public GCHeapWKS(Target target) BackgroundMaxSavedAddr = target.ReadPointer(bgMaxPtr.Value); AllocAllocated = target.ReadPointer(target.ReadGlobalPointer(Constants.Globals.GCHeapAllocAllocated)); EphemeralHeapSegment = target.ReadPointer(target.ReadGlobalPointer(Constants.Globals.GCHeapEphemeralHeapSegment)); - CardTable = target.ReadPointer(target.ReadGlobalPointer(Constants.Globals.GCHeapCardTable)); + TargetPointer cardTablePtr = target.ReadGlobalPointer(Constants.Globals.GCHeapCardTable); + CardTable = target.TryReadPointer(cardTablePtr, out TargetPointer cardTable) + ? cardTable + : TargetPointer.Null; FinalizeQueue = target.ReadPointer(target.ReadGlobalPointer(Constants.Globals.GCHeapFinalizeQueue)); GenerationTable = target.ReadGlobalPointer(Constants.Globals.GCHeapGenerationTable); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index c045d545c443f6..6fbd5aec89a7e4 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -1640,7 +1640,8 @@ int ISOSDacInterface.GetGCHeapStaticData(DacpGcHeapDetails* details) 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}"); + // Reduced dumps may omit the cDAC card-table slot while retaining the legacy VM mirror. + Debug.Assert(details->card_table == 0 || details->card_table == detailsLocal.card_table, $"cDAC: {details->card_table:x}, DAC: {detailsLocal.card_table:x}"); } } #endif diff --git a/src/native/managed/cdac/tests/UnitTests/GCTests.cs b/src/native/managed/cdac/tests/UnitTests/GCTests.cs index d5529d134950f4..b485bb1c601cb4 100644 --- a/src/native/managed/cdac/tests/UnitTests/GCTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/GCTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Legacy; using Microsoft.Diagnostics.DataContractReader.TestInfrastructure; using Xunit; @@ -11,6 +12,54 @@ namespace Microsoft.Diagnostics.DataContractReader.Tests; public class GCTests { + private const ulong CardTableAddress = 0x1234_5000; + + private static readonly MockGCBuilder.Generation[] s_cardTableGenerations = + [ + new() { StartSegment = 0x1A00_0000, AllocationStart = 0x1A00_1000, AllocContextPointer = 0x1A00_2000, AllocContextLimit = 0x1A00_3000 }, + new() { StartSegment = 0x1B00_0000, AllocationStart = 0x1B00_1000, AllocContextPointer = 0, AllocContextLimit = 0 }, + new() { StartSegment = 0x1C00_0000, AllocationStart = 0x1C00_1000, AllocContextPointer = 0, AllocContextLimit = 0 }, + new() { StartSegment = 0x1D00_0000, AllocationStart = 0x1D00_1000, AllocContextPointer = 0, AllocContextLimit = 0 }, + ]; + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetHeapData_ReadableCardTableSlot_ReturnsCardTable(MockTarget.Architecture arch) + { + Target target = CreateWksTargetWithCardTable(arch, cardTableSlotReadable: true); + + GCHeapData heapData = target.Contracts.GC.GetHeapData(); + + Assert.Equal(CardTableAddress, (ulong)heapData.CardTable); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetHeapData_UnreadableCardTableSlot_ReturnsHeapDataWithNullCardTable(MockTarget.Architecture arch) + { + Target target = CreateWksTargetWithCardTable(arch, cardTableSlotReadable: false); + + GCHeapData heapData = target.Contracts.GC.GetHeapData(); + + Assert.Equal(TargetPointer.Null, heapData.CardTable); + Assert.Equal(s_cardTableGenerations.Length, heapData.GenerationTable.Count); + Assert.Equal(s_cardTableGenerations[0].StartSegment, (ulong)heapData.GenerationTable[0].StartSegment); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public unsafe void GetGCHeapStaticData_UnreadableCardTableSlot_ReturnsHeapDataWithNullCardTable(MockTarget.Architecture arch) + { + Target target = CreateWksTargetWithCardTable(arch, cardTableSlotReadable: false); + + ISOSDacInterface sosDac = new SOSDacImpl(target, legacyObj: null, new()); + DacpGcHeapDetails details = default; + + Assert.Equal(0, sosDac.GetGCHeapStaticData(&details)); + Assert.Equal(0UL, details.card_table.Value); + Assert.Equal(s_cardTableGenerations[0].StartSegment, details.generation_table[0].start_segment.Value); + } + [Theory] [ClassData(typeof(MockTarget.StdArch))] public void GetHeapData_ReturnsCorrectGenerationTable(MockTarget.Architecture arch) @@ -120,6 +169,29 @@ public void GetHeapData_WithFiveGenerations(MockTarget.Architecture arch) private sealed record CapturedSegment(ulong Start, ulong End, GCSegmentClassification Generation); + private static Target CreateWksTargetWithCardTable(MockTarget.Architecture arch, bool cardTableSlotReadable) + { + ulong cardTableGlobalAddress = 0; + var builder = new TestPlaceholderTarget.Builder(arch); + builder.AddGCHeapWks(gc => + { + gc.Generations = s_cardTableGenerations; + gc.ConfigureMemory = gcBuilder => + { + cardTableGlobalAddress = gcBuilder.CardTableGlobalAddress; + gcBuilder.WritePointerGlobal(cardTableGlobalAddress, CardTableAddress); + }; + }); + + if (!cardTableSlotReadable) + { + TestPlaceholderTarget.ReadFromTargetDelegate reader = builder.MemoryBuilder.GetMemoryContext().ReadFromTarget; + builder.UseReader((address, buffer) => address == cardTableGlobalAddress ? -1 : reader(address, buffer)); + } + + return builder.Build(); + } + private static MockGCBuilder.Generation[] MakeGenerations(ulong gen0Seg, ulong gen0Start, ulong gen1Seg, ulong gen1Start, ulong gen2Seg, ulong lohSeg, ulong pohSeg) => [