From 9e06fe4ffbb5c47e6e69c31a7361b9a5436babfb Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Wed, 18 Feb 2026 13:33:15 -0500
Subject: [PATCH 01/28] add cdac dump-test infrastructure
---
eng/Versions.props | 1 +
.../cdac/tests/DumpTests/ClrMdDumpHost.cs | 182 +++++++++++++++
.../BasicThreads/BasicThreads.csproj | 9 +
.../Debuggees/BasicThreads/Program.cs | 56 +++++
.../DumpTests/Debuggees/Directory.Build.props | 9 +
.../ExceptionState/ExceptionState.csproj | 9 +
.../Debuggees/ExceptionState/Program.cs | 51 ++++
.../Debuggees/GCRoots/GCRoots.csproj | 9 +
.../DumpTests/Debuggees/GCRoots/Program.cs | 48 ++++
.../Debuggees/MultiModule/MultiModule.csproj | 9 +
.../Debuggees/MultiModule/Program.cs | 43 ++++
.../Debuggees/TypeHierarchy/Program.cs | 93 ++++++++
.../TypeHierarchy/TypeHierarchy.csproj | 9 +
.../cdac/tests/DumpTests/DumpTestBase.cs | 131 +++++++++++
.../cdac/tests/DumpTests/DumpTests.targets | 130 +++++++++++
.../tests/DumpTests/ExceptionDumpTests.cs | 48 ++++
.../cdac/tests/DumpTests/LoaderDumpTests.cs | 68 ++++++
...ostics.DataContractReader.DumpTests.csproj | 28 +++
.../cdac/tests/DumpTests/ObjectDumpTests.cs | 76 ++++++
.../cdac/tests/DumpTests/RunDumpTests.ps1 | 220 ++++++++++++++++++
.../DumpTests/RuntimeTypeSystemDumpTests.cs | 90 +++++++
.../SkipOnRuntimeVersionAttribute.cs | 23 ++
.../cdac/tests/DumpTests/ThreadDumpTests.cs | 114 +++++++++
...iagnostics.DataContractReader.Tests.csproj | 4 +
24 files changed, 1460 insertions(+)
create mode 100644 src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs
create mode 100644 src/native/managed/cdac/tests/DumpTests/Debuggees/BasicThreads/BasicThreads.csproj
create mode 100644 src/native/managed/cdac/tests/DumpTests/Debuggees/BasicThreads/Program.cs
create mode 100644 src/native/managed/cdac/tests/DumpTests/Debuggees/Directory.Build.props
create mode 100644 src/native/managed/cdac/tests/DumpTests/Debuggees/ExceptionState/ExceptionState.csproj
create mode 100644 src/native/managed/cdac/tests/DumpTests/Debuggees/ExceptionState/Program.cs
create mode 100644 src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/GCRoots.csproj
create mode 100644 src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/Program.cs
create mode 100644 src/native/managed/cdac/tests/DumpTests/Debuggees/MultiModule/MultiModule.csproj
create mode 100644 src/native/managed/cdac/tests/DumpTests/Debuggees/MultiModule/Program.cs
create mode 100644 src/native/managed/cdac/tests/DumpTests/Debuggees/TypeHierarchy/Program.cs
create mode 100644 src/native/managed/cdac/tests/DumpTests/Debuggees/TypeHierarchy/TypeHierarchy.csproj
create mode 100644 src/native/managed/cdac/tests/DumpTests/DumpTestBase.cs
create mode 100644 src/native/managed/cdac/tests/DumpTests/DumpTests.targets
create mode 100644 src/native/managed/cdac/tests/DumpTests/ExceptionDumpTests.cs
create mode 100644 src/native/managed/cdac/tests/DumpTests/LoaderDumpTests.cs
create mode 100644 src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj
create mode 100644 src/native/managed/cdac/tests/DumpTests/ObjectDumpTests.cs
create mode 100644 src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1
create mode 100644 src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs
create mode 100644 src/native/managed/cdac/tests/DumpTests/SkipOnRuntimeVersionAttribute.cs
create mode 100644 src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs
diff --git a/eng/Versions.props b/eng/Versions.props
index bfa35f15609e3b..d1955761a0f492 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -155,6 +155,7 @@
13.0.3
1.0.2
4.18.4
+ 3.1.512801
8.0.2
2.14.3
2.9.1
diff --git a/src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs b/src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs
new file mode 100644
index 00000000000000..179c82015bb36f
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs
@@ -0,0 +1,182 @@
+// 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.Runtime.InteropServices;
+using Microsoft.Diagnostics.Runtime;
+
+namespace Microsoft.Diagnostics.DataContractReader.DumpTests;
+
+///
+/// Wraps a ClrMD DataTarget to provide the memory read callback and symbol lookup
+/// needed to create a from a crash dump.
+///
+internal sealed class ClrMdDumpHost : IDisposable
+{
+ private static readonly string[] s_runtimeModuleNames =
+ {
+ "coreclr.dll",
+ "libcoreclr.so",
+ "libcoreclr.dylib",
+ };
+
+ private readonly DataTarget _dataTarget;
+
+ public string DumpPath { get; }
+
+ private ClrMdDumpHost(string dumpPath, DataTarget dataTarget)
+ {
+ DumpPath = dumpPath;
+ _dataTarget = dataTarget;
+ }
+
+ ///
+ /// Open a crash dump and prepare it for cDAC analysis.
+ ///
+ public static ClrMdDumpHost Open(string dumpPath)
+ {
+ DataTarget dataTarget = DataTarget.LoadDump(dumpPath);
+ return new ClrMdDumpHost(dumpPath, dataTarget);
+ }
+
+ ///
+ /// Read memory from the dump at the specified address.
+ /// Returns 0 on success, non-zero on failure.
+ ///
+ public int ReadFromTarget(ulong address, Span buffer)
+ {
+ int bytesRead = _dataTarget.DataReader.Read(address, buffer);
+ return bytesRead == buffer.Length ? 0 : -1;
+ }
+
+ ///
+ /// Locate the DotNetRuntimeContractDescriptor symbol address in the dump.
+ ///
+ public ulong FindContractDescriptorAddress()
+ {
+ // Find the native coreclr module via DataReader (not ClrRuntime, which only lists managed assemblies)
+ foreach (ModuleInfo module in _dataTarget.DataReader.EnumerateModules())
+ {
+ string? fileName = module.FileName;
+ if (fileName is null)
+ continue;
+
+ string name = System.IO.Path.GetFileName(fileName);
+ if (!IsRuntimeModule(name))
+ continue;
+
+ ulong address = FindPEExport(module.ImageBase, "DotNetRuntimeContractDescriptor");
+ if (address != 0)
+ return address;
+ }
+
+ throw new InvalidOperationException("Could not find DotNetRuntimeContractDescriptor export in any runtime module in the dump.");
+ }
+
+ private static bool IsRuntimeModule(string fileName)
+ {
+ foreach (string name in s_runtimeModuleNames)
+ {
+ if (fileName.Equals(name, StringComparison.OrdinalIgnoreCase))
+ return true;
+ }
+
+ return false;
+ }
+
+ private ulong FindPEExport(ulong imageBase, string symbolName)
+ {
+ // Read the DOS header to find the PE header
+ Span dosHeader = stackalloc byte[64];
+ if (ReadFromTarget(imageBase, dosHeader) != 0)
+ return 0;
+
+ // e_lfanew is at offset 0x3C
+ int peOffset = MemoryMarshal.Read(dosHeader.Slice(0x3C));
+
+ // Read PE signature + COFF header
+ Span peHeader = stackalloc byte[4 + 20];
+ if (ReadFromTarget(imageBase + (ulong)peOffset, peHeader) != 0)
+ return 0;
+
+ if (MemoryMarshal.Read(peHeader) != 0x00004550) // "PE\0\0"
+ return 0;
+
+ // Determine PE32 or PE32+ to find export directory offset
+ uint optionalHeaderOffset = (uint)peOffset + 4 + 20;
+ Span magic = stackalloc byte[2];
+ if (ReadFromTarget(imageBase + optionalHeaderOffset, magic) != 0)
+ return 0;
+
+ ushort peMagic = MemoryMarshal.Read(magic);
+ uint exportDirRvaOffset = peMagic switch
+ {
+ 0x10b => optionalHeaderOffset + 96, // PE32
+ 0x20b => optionalHeaderOffset + 112, // PE32+
+ _ => 0,
+ };
+ if (exportDirRvaOffset == 0)
+ return 0;
+
+ // Read export directory RVA
+ Span exportDirEntry = stackalloc byte[8];
+ if (ReadFromTarget(imageBase + exportDirRvaOffset, exportDirEntry) != 0)
+ return 0;
+
+ uint exportRva = MemoryMarshal.Read(exportDirEntry);
+ if (exportRva == 0)
+ return 0;
+
+ // Read export directory header (40 bytes)
+ Span exportDir = stackalloc byte[40];
+ if (ReadFromTarget(imageBase + exportRva, exportDir) != 0)
+ return 0;
+
+ uint numberOfNames = MemoryMarshal.Read(exportDir.Slice(24));
+ uint addressOfFunctions = MemoryMarshal.Read(exportDir.Slice(28));
+ uint addressOfNames = MemoryMarshal.Read(exportDir.Slice(32));
+ uint addressOfNameOrdinals = MemoryMarshal.Read(exportDir.Slice(36));
+
+ // Search the name pointer table for the symbol
+ for (uint i = 0; i < numberOfNames; i++)
+ {
+ Span nameRvaBytes = stackalloc byte[4];
+ if (ReadFromTarget(imageBase + addressOfNames + i * 4, nameRvaBytes) != 0)
+ continue;
+
+ uint nameRva = MemoryMarshal.Read(nameRvaBytes);
+
+ Span nameBytes = stackalloc byte[64];
+ if (ReadFromTarget(imageBase + nameRva, nameBytes) != 0)
+ continue;
+
+ int nullIndex = nameBytes.IndexOf((byte)0);
+ if (nullIndex < 0)
+ continue;
+
+ string name = System.Text.Encoding.ASCII.GetString(nameBytes.Slice(0, nullIndex));
+ if (name != symbolName)
+ continue;
+
+ Span ordinalBytes = stackalloc byte[2];
+ if (ReadFromTarget(imageBase + addressOfNameOrdinals + i * 2, ordinalBytes) != 0)
+ return 0;
+
+ ushort ordinal = MemoryMarshal.Read(ordinalBytes);
+
+ Span funcRvaBytes = stackalloc byte[4];
+ if (ReadFromTarget(imageBase + addressOfFunctions + ordinal * 4u, funcRvaBytes) != 0)
+ return 0;
+
+ uint funcRva = MemoryMarshal.Read(funcRvaBytes);
+ return imageBase + funcRva;
+ }
+
+ return 0;
+ }
+
+ public void Dispose()
+ {
+ _dataTarget?.Dispose();
+ }
+}
diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/BasicThreads/BasicThreads.csproj b/src/native/managed/cdac/tests/DumpTests/Debuggees/BasicThreads/BasicThreads.csproj
new file mode 100644
index 00000000000000..a7a4d85fdf3b69
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/BasicThreads/BasicThreads.csproj
@@ -0,0 +1,9 @@
+
+
+ Exe
+ $(NetCoreAppToolCurrent);net10.0
+ true
+ $(ArtifactsBinDir)DumpTests\$(MSBuildProjectName)\$(Configuration)\
+ true
+
+
diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/BasicThreads/Program.cs b/src/native/managed/cdac/tests/DumpTests/Debuggees/BasicThreads/Program.cs
new file mode 100644
index 00000000000000..4571e8566a1c29
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/BasicThreads/Program.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.Threading;
+
+///
+/// Debuggee app for cDAC dump tests.
+/// Spawns threads with known names, ensures they are all alive, then crashes
+/// so a dump is produced for analysis.
+///
+internal static class Program
+{
+ // These constants are referenced by ThreadDumpTests to assert expected values.
+ public const int SpawnedThreadCount = 5;
+ public static readonly string[] ThreadNames = new[]
+ {
+ "cdac-test-thread-0",
+ "cdac-test-thread-1",
+ "cdac-test-thread-2",
+ "cdac-test-thread-3",
+ "cdac-test-thread-4",
+ };
+
+ private static void Main()
+ {
+ // Barrier ensures all threads are alive and named before we crash.
+ // participantCount = SpawnedThreadCount + 1 (main thread)
+ using Barrier barrier = new(SpawnedThreadCount + 1);
+
+ Thread[] threads = new Thread[SpawnedThreadCount];
+ for (int i = 0; i < SpawnedThreadCount; i++)
+ {
+ int index = i;
+ threads[i] = new Thread(() =>
+ {
+ // Signal that this thread is alive and wait for all others.
+ barrier.SignalAndWait();
+
+ // Keep the thread alive until the process crashes.
+ Thread.Sleep(Timeout.Infinite);
+ })
+ {
+ Name = ThreadNames[index],
+ IsBackground = true,
+ };
+ threads[i].Start();
+ }
+
+ // Wait until all spawned threads have reached the barrier.
+ barrier.SignalAndWait();
+
+ // All threads are alive and named. Crash to produce a dump.
+ Environment.FailFast("cDAC dump test: BasicThreads debuggee intentional crash");
+ }
+}
diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/Directory.Build.props b/src/native/managed/cdac/tests/DumpTests/Debuggees/Directory.Build.props
new file mode 100644
index 00000000000000..b76dd352e98826
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/Directory.Build.props
@@ -0,0 +1,9 @@
+
+
+
+
+ enable
+
+ $(NoWarn);CA1852
+
+
diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/ExceptionState/ExceptionState.csproj b/src/native/managed/cdac/tests/DumpTests/Debuggees/ExceptionState/ExceptionState.csproj
new file mode 100644
index 00000000000000..a7a4d85fdf3b69
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/ExceptionState/ExceptionState.csproj
@@ -0,0 +1,9 @@
+
+
+ Exe
+ $(NetCoreAppToolCurrent);net10.0
+ true
+ $(ArtifactsBinDir)DumpTests\$(MSBuildProjectName)\$(Configuration)\
+ true
+
+
diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/ExceptionState/Program.cs b/src/native/managed/cdac/tests/DumpTests/Debuggees/ExceptionState/Program.cs
new file mode 100644
index 00000000000000..e24cee8ce4829a
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/ExceptionState/Program.cs
@@ -0,0 +1,51 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+///
+/// Debuggee for cDAC dump tests — exercises the Exception contract.
+/// Creates a nested exception chain then crashes with FailFast.
+///
+internal static class Program
+{
+ public class DebuggeeException : Exception
+ {
+ public DebuggeeException(string message) : base(message) { }
+ public DebuggeeException(string message, Exception inner) : base(message, inner) { }
+ }
+
+ private static void Main()
+ {
+ Exception? caughtException;
+
+ // Build a nested exception chain
+ try
+ {
+ try
+ {
+ try
+ {
+ throw new InvalidOperationException("innermost exception");
+ }
+ catch (Exception ex)
+ {
+ throw new DebuggeeException("middle exception", ex);
+ }
+ }
+ catch (Exception ex)
+ {
+ throw new DebuggeeException("outermost exception", ex);
+ }
+ }
+ catch (Exception ex)
+ {
+ caughtException = ex;
+ }
+
+ // Keep the exception chain alive
+ GC.KeepAlive(caughtException);
+
+ Environment.FailFast("cDAC dump test: ExceptionState debuggee intentional crash");
+ }
+}
diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/GCRoots.csproj b/src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/GCRoots.csproj
new file mode 100644
index 00000000000000..a7a4d85fdf3b69
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/GCRoots.csproj
@@ -0,0 +1,9 @@
+
+
+ Exe
+ $(NetCoreAppToolCurrent);net10.0
+ true
+ $(ArtifactsBinDir)DumpTests\$(MSBuildProjectName)\$(Configuration)\
+ true
+
+
diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/Program.cs b/src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/Program.cs
new file mode 100644
index 00000000000000..fabc70ce665962
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/Program.cs
@@ -0,0 +1,48 @@
+// 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.Runtime.InteropServices;
+
+///
+/// Debuggee for cDAC dump tests — exercises the Object and GC contracts.
+/// Pins objects, creates GC handles, then crashes.
+///
+internal static class Program
+{
+ public const int PinnedObjectCount = 5;
+ public const string TestStringValue = "cDAC-GCRoots-test-string";
+
+ private static void Main()
+ {
+ // Allocate objects of various types
+ string testString = TestStringValue;
+ byte[] byteArray = new byte[1024];
+ object boxedInt = 42;
+
+ // Create pinned handles
+ GCHandle[] pinnedHandles = new GCHandle[PinnedObjectCount];
+ byte[][] pinnedArrays = new byte[PinnedObjectCount][];
+
+ for (int i = 0; i < PinnedObjectCount; i++)
+ {
+ pinnedArrays[i] = new byte[64];
+ pinnedHandles[i] = GCHandle.Alloc(pinnedArrays[i], GCHandleType.Pinned);
+ }
+
+ // Create weak and strong handles
+ var weakRef = new WeakReference(new object());
+ var strongHandle = GCHandle.Alloc(testString, GCHandleType.Normal);
+
+ // Keep references alive
+ GC.KeepAlive(testString);
+ GC.KeepAlive(byteArray);
+ GC.KeepAlive(boxedInt);
+ GC.KeepAlive(pinnedHandles);
+ GC.KeepAlive(pinnedArrays);
+ GC.KeepAlive(weakRef);
+ GC.KeepAlive(strongHandle);
+
+ Environment.FailFast("cDAC dump test: GCRoots debuggee intentional crash");
+ }
+}
diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/MultiModule/MultiModule.csproj b/src/native/managed/cdac/tests/DumpTests/Debuggees/MultiModule/MultiModule.csproj
new file mode 100644
index 00000000000000..a7a4d85fdf3b69
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/MultiModule/MultiModule.csproj
@@ -0,0 +1,9 @@
+
+
+ Exe
+ $(NetCoreAppToolCurrent);net10.0
+ true
+ $(ArtifactsBinDir)DumpTests\$(MSBuildProjectName)\$(Configuration)\
+ true
+
+
diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/MultiModule/Program.cs b/src/native/managed/cdac/tests/DumpTests/Debuggees/MultiModule/Program.cs
new file mode 100644
index 00000000000000..d803f40e8d39ea
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/MultiModule/Program.cs
@@ -0,0 +1,43 @@
+// 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.Reflection;
+using System.Runtime.Loader;
+
+///
+/// Debuggee for cDAC dump tests — exercises the Loader contract.
+/// Loads assemblies from multiple AssemblyLoadContexts then crashes.
+///
+internal static class Program
+{
+ public const int CustomAlcCount = 3;
+
+ private static void Main()
+ {
+ // Create multiple AssemblyLoadContexts and load the runtime assembly in each
+ // to exercise the Loader contract's module enumeration.
+ AssemblyLoadContext[] contexts = new AssemblyLoadContext[CustomAlcCount];
+ Assembly[] loadedAssemblies = new Assembly[CustomAlcCount];
+
+ for (int i = 0; i < CustomAlcCount; i++)
+ {
+ contexts[i] = new AssemblyLoadContext($"cdac-test-alc-{i}", isCollectible: true);
+
+ // Each ALC will have the core assembly visible.
+ // We can also load the current assembly's location in a new context,
+ // but for simplicity, just exercise the ALC infrastructure.
+ loadedAssemblies[i] = contexts[i].LoadFromAssemblyName(typeof(object).Assembly.GetName());
+ }
+
+ // Also load System.Xml to have another module present
+ var xmlAssembly = Assembly.Load("System.Private.Xml");
+
+ // Keep references alive
+ GC.KeepAlive(contexts);
+ GC.KeepAlive(loadedAssemblies);
+ GC.KeepAlive(xmlAssembly);
+
+ Environment.FailFast("cDAC dump test: MultiModule debuggee intentional crash");
+ }
+}
diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/TypeHierarchy/Program.cs b/src/native/managed/cdac/tests/DumpTests/Debuggees/TypeHierarchy/Program.cs
new file mode 100644
index 00000000000000..158b43dccc55c9
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/TypeHierarchy/Program.cs
@@ -0,0 +1,93 @@
+// 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;
+
+///
+/// Debuggee for cDAC dump tests — exercises the RuntimeTypeSystem and Loader contracts.
+/// Loads types with inheritance, generics, and arrays, then crashes.
+///
+internal static class Program
+{
+ // Base class hierarchy
+ public class Animal
+ {
+ public virtual string Name => "Animal";
+ }
+
+ public class Dog : Animal
+ {
+ public override string Name => "Dog";
+ public string Breed { get; set; } = "Unknown";
+ }
+
+ public class GuideDog : Dog
+ {
+ public string Handler { get; set; } = "None";
+ }
+
+ // Generic types
+ public class Container
+ {
+ public T? Value { get; set; }
+ }
+
+ public class Pair
+ {
+ public TKey? Key { get; set; }
+ public TValue? Value { get; set; }
+ }
+
+ // Interface hierarchy
+ public interface IIdentifiable
+ {
+ int Id { get; }
+ }
+
+ public class IdentifiableAnimal : Animal, IIdentifiable
+ {
+ public int Id { get; set; }
+ }
+
+ private static void Main()
+ {
+ // Create instances so the runtime loads and lays out these types
+ var dog = new Dog { Breed = "Labrador" };
+ var guideDog = new GuideDog { Handler = "John", Breed = "Shepherd" };
+ var container = new Container { Value = 42 };
+ var pair = new Pair { Key = "Rex", Value = dog };
+ var idAnimal = new IdentifiableAnimal { Id = 1 };
+
+ // Arrays of various types
+ int[] intArray = new[] { 1, 2, 3, 4, 5 };
+ string[] stringArray = new[] { "hello", "world" };
+ Dog[] dogArray = new[] { dog, guideDog };
+
+ // Multi-dimensional array
+ int[,] matrix = new int[3, 3];
+
+ // Generic collections
+ var list = new List { dog, guideDog, idAnimal };
+ var dict = new Dictionary
+ {
+ ["dog"] = dog,
+ ["guide"] = guideDog,
+ };
+
+ // Keep references alive
+ GC.KeepAlive(dog);
+ GC.KeepAlive(guideDog);
+ GC.KeepAlive(container);
+ GC.KeepAlive(pair);
+ GC.KeepAlive(idAnimal);
+ GC.KeepAlive(intArray);
+ GC.KeepAlive(stringArray);
+ GC.KeepAlive(dogArray);
+ GC.KeepAlive(matrix);
+ GC.KeepAlive(list);
+ GC.KeepAlive(dict);
+
+ Environment.FailFast("cDAC dump test: TypeHierarchy debuggee intentional crash");
+ }
+}
diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/TypeHierarchy/TypeHierarchy.csproj b/src/native/managed/cdac/tests/DumpTests/Debuggees/TypeHierarchy/TypeHierarchy.csproj
new file mode 100644
index 00000000000000..a7a4d85fdf3b69
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/TypeHierarchy/TypeHierarchy.csproj
@@ -0,0 +1,9 @@
+
+
+ Exe
+ $(NetCoreAppToolCurrent);net10.0
+ true
+ $(ArtifactsBinDir)DumpTests\$(MSBuildProjectName)\$(Configuration)\
+ true
+
+
diff --git a/src/native/managed/cdac/tests/DumpTests/DumpTestBase.cs b/src/native/managed/cdac/tests/DumpTests/DumpTestBase.cs
new file mode 100644
index 00000000000000..153ae6593011e9
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/DumpTestBase.cs
@@ -0,0 +1,131 @@
+// 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.IO;
+using System.Reflection;
+using System.Threading;
+using Microsoft.DotNet.XUnitExtensions;
+using Xunit;
+using Xunit.Sdk;
+
+namespace Microsoft.Diagnostics.DataContractReader.DumpTests;
+
+///
+/// Automatically checks on test methods
+/// before they execute. Applied at the class level on ,
+/// so all derived test classes get automatic version-aware skipping.
+///
+public sealed class CheckSkipOnRuntimeVersionAttribute : BeforeAfterTestAttribute
+{
+ public override void Before(MethodInfo methodUnderTest)
+ {
+ string? version = DumpTestBase.CurrentRuntimeVersion.Value;
+ if (version is null)
+ return;
+
+ foreach (SkipOnRuntimeVersionAttribute attr in methodUnderTest.GetCustomAttributes())
+ {
+ if (string.Equals(attr.Version, version, StringComparison.OrdinalIgnoreCase))
+ {
+ throw new SkipTestException($"[{version}] {attr.Reason}");
+ }
+ }
+ }
+}
+
+///
+/// Base class for dump-based cDAC integration tests.
+/// Loads a crash dump, creates a , and provides
+/// shared helpers for assertions.
+///
+///
+/// Tests that need version-aware skipping should:
+///
+/// - Use [ConditionalFact] instead of [Fact]
+/// - Apply [SkipOnRuntimeVersion("version", "reason")] to the method
+///
+/// The on this class automatically
+/// evaluates skip conditions — no manual SkipIfVersionExcluded() call required.
+///
+[CheckSkipOnRuntimeVersion]
+public abstract class DumpTestBase : IDisposable
+{
+ internal static readonly AsyncLocal CurrentRuntimeVersion = new();
+
+ private ClrMdDumpHost? _host;
+ private ContractDescriptorTarget? _target;
+
+ ///
+ /// The name of the debuggee that produced the dump (e.g., "BasicThreads").
+ ///
+ protected abstract string DebuggeeName { get; }
+
+ ///
+ /// The runtime version identifier (e.g., "local", "net10.0").
+ ///
+ protected abstract string RuntimeVersion { get; }
+
+ ///
+ /// The cDAC Target created from the dump.
+ ///
+ protected ContractDescriptorTarget Target => _target ?? throw new InvalidOperationException("Dump not loaded. Call LoadDump() first.");
+
+ ///
+ /// Resolves the dump file path for the current debuggee and runtime version.
+ /// Convention: {RepoRoot}/artifacts/dumps/cdac/{RuntimeVersion}/{DebuggeeName}/{DebuggeeName}.dmp
+ ///
+ protected string GetDumpPath()
+ {
+ string? repoRoot = FindRepoRoot();
+ if (repoRoot is null)
+ throw new InvalidOperationException("Could not locate the repository root.");
+
+ return Path.Combine(repoRoot, "artifacts", "dumps", "cdac", RuntimeVersion, DebuggeeName, $"{DebuggeeName}.dmp");
+ }
+
+ ///
+ /// Load the dump and create the cDAC Target.
+ /// Throws if the dump file does not exist.
+ ///
+ protected void LoadDump()
+ {
+ CurrentRuntimeVersion.Value = RuntimeVersion;
+
+ string dumpPath = GetDumpPath();
+ if (!File.Exists(dumpPath))
+ throw new FileNotFoundException($"Dump file not found: {dumpPath}. Run dump generation first.");
+
+ _host = ClrMdDumpHost.Open(dumpPath);
+ ulong contractDescriptor = _host.FindContractDescriptorAddress();
+
+ bool created = ContractDescriptorTarget.TryCreate(
+ contractDescriptor,
+ _host.ReadFromTarget,
+ writeToTarget: null!,
+ getThreadContext: null!,
+ additionalFactories: [],
+ out _target);
+
+ Assert.True(created, $"Failed to create ContractDescriptorTarget from dump: {dumpPath}");
+ }
+
+ private static string? FindRepoRoot()
+ {
+ string? dir = AppContext.BaseDirectory;
+ while (dir is not null)
+ {
+ if (File.Exists(Path.Combine(dir, "global.json")))
+ return dir;
+ dir = Path.GetDirectoryName(dir);
+ }
+
+ return null;
+ }
+
+ public void Dispose()
+ {
+ _host?.Dispose();
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/src/native/managed/cdac/tests/DumpTests/DumpTests.targets b/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
new file mode 100644
index 00000000000000..27fa04f845e4d2
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
@@ -0,0 +1,130 @@
+
+
+
+
+ $(RepoRoot)artifacts\dumps\cdac\
+ Release
+ $(MSBuildThisFileDirectory)Debuggees\
+
+
+
+
+ <_TestHostConfig Condition="'$(TestHostConfiguration)' == ''">Release
+ <_TestHostConfig Condition="'$(TestHostConfiguration)' != ''">$(TestHostConfiguration)
+ <_HostArch>$(TargetArchitecture)
+ <_HostArch Condition="'$(_HostArch)' == ''">x64
+ $(RepoRoot)artifacts\bin\testhost\$(NetCoreAppCurrent)-windows-$(_TestHostConfig)-$(_HostArch)\
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_DebuggeeDir>$(DebuggeesDir)$(DebuggeeName)\
+ <_DumpDir>$(DumpOutputDir)$(DumpRuntimeVersion)\$(DebuggeeName)\
+ <_DumpFile>$(_DumpDir)$(DebuggeeName).dmp
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_DebuggeeBinDir>$(RepoRoot)artifacts\bin\DumpTests\$(DebuggeeName)\$(DebuggeeConfiguration)\$(NetCoreAppCurrent)\
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_PublishDir>$(RepoRoot)artifacts\bin\DumpTests\$(DebuggeeName)\$(DebuggeeConfiguration)\$(DumpRuntimeVersion)-publish\
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(DumpOutputDir)local\BasicThreads\BasicThreads.dmp
+ $(DumpOutputDir)net10.0\BasicThreads\BasicThreads.dmp
+
+
diff --git a/src/native/managed/cdac/tests/DumpTests/ExceptionDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/ExceptionDumpTests.cs
new file mode 100644
index 00000000000000..636852b756d7d1
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/ExceptionDumpTests.cs
@@ -0,0 +1,48 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Diagnostics.DataContractReader.Contracts;
+using Xunit;
+
+namespace Microsoft.Diagnostics.DataContractReader.DumpTests;
+
+///
+/// Dump-based integration tests for the Exception contract.
+/// Uses the ExceptionState debuggee dump, which crashes with a nested exception chain.
+///
+public abstract class ExceptionDumpTestsBase : DumpTestBase
+{
+ protected ExceptionDumpTestsBase()
+ {
+ LoadDump();
+ }
+
+ protected override string DebuggeeName => "ExceptionState";
+
+ [Fact]
+ public void Exception_ThreadHasCurrentException()
+ {
+ IThread threadContract = Target.Contracts.Thread;
+ Assert.NotNull(threadContract);
+
+ ThreadStoreData storeData = threadContract.GetThreadStoreData();
+ Assert.True(storeData.ThreadCount > 0);
+ }
+
+ [Fact]
+ public void Exception_ContractIsAvailable()
+ {
+ IException exceptionContract = Target.Contracts.Exception;
+ Assert.NotNull(exceptionContract);
+ }
+}
+
+public class ExceptionDumpTests_Local : ExceptionDumpTestsBase
+{
+ protected override string RuntimeVersion => "local";
+}
+
+public class ExceptionDumpTests_Net10 : ExceptionDumpTestsBase
+{
+ protected override string RuntimeVersion => "net10.0";
+}
diff --git a/src/native/managed/cdac/tests/DumpTests/LoaderDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/LoaderDumpTests.cs
new file mode 100644
index 00000000000000..5be3ccd8416040
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/LoaderDumpTests.cs
@@ -0,0 +1,68 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Diagnostics.DataContractReader.Contracts;
+using Xunit;
+
+namespace Microsoft.Diagnostics.DataContractReader.DumpTests;
+
+///
+/// Dump-based integration tests for the Loader contract.
+/// Uses the MultiModule debuggee dump, which loads assemblies from multiple ALCs.
+///
+public abstract class LoaderDumpTestsBase : DumpTestBase
+{
+ protected LoaderDumpTestsBase()
+ {
+ LoadDump();
+ }
+
+ protected override string DebuggeeName => "MultiModule";
+
+ [Fact]
+ public void Loader_CanGetRootAssembly()
+ {
+ ILoader loader = Target.Contracts.Loader;
+ Assert.NotNull(loader);
+
+ TargetPointer rootAssembly = loader.GetRootAssembly();
+ Assert.NotEqual(TargetPointer.Null, rootAssembly);
+ }
+
+ [ConditionalFact]
+ [SkipOnRuntimeVersion("net10.0", "Assembly type does not include IsDynamic/IsLoaded fields in .NET 10")]
+ [SkipOnRuntimeVersion("local", "Assembly type does not include IsLoaded field in current contract descriptor")]
+ public void Loader_RootAssemblyHasModule()
+ {
+ ILoader loader = Target.Contracts.Loader;
+ TargetPointer rootAssembly = loader.GetRootAssembly();
+
+ ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(rootAssembly);
+ TargetPointer modulePtr = loader.GetModule(moduleHandle);
+ Assert.NotEqual(TargetPointer.Null, modulePtr);
+ }
+
+ [ConditionalFact]
+ [SkipOnRuntimeVersion("net10.0", "Assembly type does not include IsDynamic/IsLoaded fields in .NET 10")]
+ [SkipOnRuntimeVersion("local", "Assembly type does not include IsLoaded field in current contract descriptor")]
+ public void Loader_CanGetModulePath()
+ {
+ ILoader loader = Target.Contracts.Loader;
+ TargetPointer rootAssembly = loader.GetRootAssembly();
+
+ ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(rootAssembly);
+ string path = loader.GetPath(moduleHandle);
+ Assert.NotNull(path);
+ Assert.NotEmpty(path);
+ }
+}
+
+public class LoaderDumpTests_Local : LoaderDumpTestsBase
+{
+ protected override string RuntimeVersion => "local";
+}
+
+public class LoaderDumpTests_Net10 : LoaderDumpTestsBase
+{
+ protected override string RuntimeVersion => "net10.0";
+}
diff --git a/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj b/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj
new file mode 100644
index 00000000000000..999b951fa8cd9a
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj
@@ -0,0 +1,28 @@
+
+
+ true
+ $(NetCoreAppToolCurrent)
+ enable
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/native/managed/cdac/tests/DumpTests/ObjectDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/ObjectDumpTests.cs
new file mode 100644
index 00000000000000..3d86abd210ca9c
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/ObjectDumpTests.cs
@@ -0,0 +1,76 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Diagnostics.DataContractReader.Contracts;
+using Xunit;
+
+namespace Microsoft.Diagnostics.DataContractReader.DumpTests;
+
+///
+/// Dump-based integration tests for the Object and GC contracts.
+/// Uses the GCRoots debuggee dump, which pins objects and creates GC handles.
+///
+public abstract class ObjectDumpTestsBase : DumpTestBase
+{
+ protected ObjectDumpTestsBase()
+ {
+ LoadDump();
+ }
+
+ protected override string DebuggeeName => "GCRoots";
+
+ [Fact]
+ public void Object_ContractIsAvailable()
+ {
+ IObject objectContract = Target.Contracts.Object;
+ Assert.NotNull(objectContract);
+ }
+
+ [ConditionalFact]
+ [SkipOnRuntimeVersion("net10.0", "GC contract is not available in .NET 10 dumps")]
+ public void GC_ContractIsAvailable()
+ {
+ IGC gcContract = Target.Contracts.GC;
+ Assert.NotNull(gcContract);
+ }
+
+ [ConditionalFact]
+ [SkipOnRuntimeVersion("net10.0", "GC contract is not available in .NET 10 dumps")]
+ public void GC_HeapCountIsNonZero()
+ {
+ IGC gcContract = Target.Contracts.GC;
+ uint heapCount = gcContract.GetGCHeapCount();
+ Assert.True(heapCount > 0, "Expected at least one GC heap");
+ }
+
+ [ConditionalFact]
+ [SkipOnRuntimeVersion("net10.0", "GC contract is not available in .NET 10 dumps")]
+ public void GC_MaxGenerationIsReasonable()
+ {
+ IGC gcContract = Target.Contracts.GC;
+ uint maxGen = gcContract.GetMaxGeneration();
+ // .NET typically has gen0, gen1, gen2 (maxGen = 2)
+ Assert.True(maxGen >= 1 && maxGen <= 4,
+ $"Expected max generation between 1 and 4, got {maxGen}");
+ }
+
+ [ConditionalFact]
+ [SkipOnRuntimeVersion("net10.0", "GC contract is not available in .NET 10 dumps")]
+ public void GC_CanGetHeapData()
+ {
+ IGC gcContract = Target.Contracts.GC;
+ GCHeapData heapData = gcContract.GetHeapData();
+ Assert.NotNull(heapData.GenerationTable);
+ Assert.True(heapData.GenerationTable.Count > 0, "Expected at least one generation");
+ }
+}
+
+public class ObjectDumpTests_Local : ObjectDumpTestsBase
+{
+ protected override string RuntimeVersion => "local";
+}
+
+public class ObjectDumpTests_Net10 : ObjectDumpTestsBase
+{
+ protected override string RuntimeVersion => "net10.0";
+}
diff --git a/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1 b/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1
new file mode 100644
index 00000000000000..1451699f9c9751
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1
@@ -0,0 +1,220 @@
+<#
+.SYNOPSIS
+ Generates crash dumps and/or runs cDAC dump-based integration tests.
+
+.DESCRIPTION
+ This script orchestrates the cDAC dump test workflow:
+ 1. Build debuggee apps for the selected runtime version(s)
+ 2. Run them to produce crash dumps
+ 3. Build and run the dump analysis tests
+
+ Dumps are written to: artifacts\dumps\cdac\{version}\{debuggee}\
+ The script must be run from the DumpTests directory.
+
+.PARAMETER Action
+ What to do: "dumps" (generate only), "test" (run tests only), or "all" (both).
+ Default: "all"
+
+.PARAMETER Versions
+ Comma-separated list of runtime versions to target.
+ Supported values: "local", "net10.0", or "all" for both.
+ Default: "all"
+
+.PARAMETER Force
+ When set, deletes existing dumps before regenerating.
+
+.PARAMETER TestHostConfiguration
+ Configuration of the testhost used for the "local" runtime version.
+ Default: "Release"
+
+.EXAMPLE
+ .\RunDumpTests.ps1
+
+.EXAMPLE
+ .\RunDumpTests.ps1 -Action dumps -Versions net10.0
+
+.EXAMPLE
+ .\RunDumpTests.ps1 -Force
+
+.EXAMPLE
+ .\RunDumpTests.ps1 -Action test -Versions local
+#>
+
+[CmdletBinding()]
+param(
+ [ValidateSet("dumps", "test", "all")]
+ [string]$Action = "all",
+
+ [string]$Versions = "all",
+
+ [switch]$Force,
+
+ [string]$TestHostConfiguration = "Release"
+)
+
+Set-StrictMode -Version Latest
+$ErrorActionPreference = "Stop"
+
+# --- Resolve paths ---
+$repoRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent (Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $PSScriptRoot)))))
+$dotnet = Join-Path $repoRoot ".dotnet\dotnet.exe"
+$dumpTestsProj = Join-Path $PSScriptRoot "Microsoft.Diagnostics.DataContractReader.DumpTests.csproj"
+$dumpOutputDir = Join-Path $repoRoot "artifacts\dumps\cdac"
+$debuggeesDir = Join-Path $PSScriptRoot "Debuggees"
+
+if (-not (Test-Path $dotnet)) {
+ Write-Error "Repo dotnet not found at $dotnet. Run build.cmd first."
+ exit 1
+}
+
+# --- Debuggees and versions ---
+$allDebugees = @("BasicThreads", "TypeHierarchy", "ExceptionState", "MultiModule", "GCRoots")
+$allVersions = @("local", "net10.0")
+
+if ($Versions -eq "all") {
+ $selectedVersions = $allVersions
+}
+else {
+ $selectedVersions = $Versions -split "," | ForEach-Object { $_.Trim() }
+ foreach ($v in $selectedVersions) {
+ if ($v -notin $allVersions) {
+ Write-Error "Unknown version '$v'. Supported: $($allVersions -join ', '), all"
+ exit 1
+ }
+ }
+}
+
+Write-Host ""
+Write-Host "=== cDAC Dump Tests ===" -ForegroundColor Cyan
+Write-Host " Action: $Action"
+Write-Host " Versions: $($selectedVersions -join ', ')"
+Write-Host " Debuggees: $($allDebugees -join ', ')"
+Write-Host " Force: $Force"
+Write-Host ""
+
+# --- Force: delete existing dumps ---
+if ($Force -and $Action -in @("dumps", "all")) {
+ foreach ($version in $selectedVersions) {
+ $versionDumpDir = Join-Path $dumpOutputDir $version
+ if (Test-Path $versionDumpDir) {
+ Write-Host "Deleting existing dumps: $versionDumpDir" -ForegroundColor Yellow
+ Remove-Item $versionDumpDir -Recurse -Force
+ }
+ }
+}
+
+# --- Helper: resolve TFM for local builds ---
+$localTfm = $null
+function Get-LocalTfm {
+ if ($null -eq $script:localTfm) {
+ $script:localTfm = & $dotnet msbuild $dumpTestsProj /nologo /t:PrintNothing /v:m "/getProperty:NetCoreAppCurrent" 2>$null
+ if ([string]::IsNullOrWhiteSpace($script:localTfm)) { $script:localTfm = "net11.0" }
+ }
+ return $script:localTfm
+}
+
+# --- Helper: set dump env vars ---
+function Set-DumpEnvVars($dumpFilePath) {
+ $env:DOTNET_DbgEnableMiniDump = "1"
+ $env:DOTNET_DbgMiniDumpType = "4"
+ $env:DOTNET_DbgMiniDumpName = $dumpFilePath
+}
+
+function Clear-DumpEnvVars {
+ Remove-Item Env:\DOTNET_DbgEnableMiniDump -ErrorAction SilentlyContinue
+ Remove-Item Env:\DOTNET_DbgMiniDumpType -ErrorAction SilentlyContinue
+ Remove-Item Env:\DOTNET_DbgMiniDumpName -ErrorAction SilentlyContinue
+}
+
+# --- Generate dumps ---
+if ($Action -in @("dumps", "all")) {
+ Write-Host "--- Generating dumps ---" -ForegroundColor Cyan
+
+ foreach ($version in $selectedVersions) {
+ foreach ($debuggee in $allDebugees) {
+ $dumpFile = Join-Path $dumpOutputDir "$version\$debuggee\$debuggee.dmp"
+
+ if (Test-Path $dumpFile) {
+ $size = [math]::Round((Get-Item $dumpFile).Length / 1MB, 1)
+ Write-Host " [$version/$debuggee] Already exists (${size} MB). Use -Force to regenerate." -ForegroundColor DarkGray
+ continue
+ }
+
+ Write-Host " [$version/$debuggee] Generating dump..." -ForegroundColor Green
+ $debuggeeCsproj = Join-Path $debuggeesDir "$debuggee\$debuggee.csproj"
+ $dumpDir = Join-Path $dumpOutputDir "$version\$debuggee"
+ New-Item -ItemType Directory -Path $dumpDir -Force | Out-Null
+
+ if ($version -eq "local") {
+ $tfm = Get-LocalTfm
+ $binDir = Join-Path $repoRoot "artifacts\bin\DumpTests\$debuggee\Release\$tfm"
+ $testHostDir = Join-Path $repoRoot "artifacts\bin\testhost\$tfm-windows-$TestHostConfiguration-x64"
+
+ if (-not (Test-Path "$testHostDir\dotnet.exe")) {
+ Write-Error " [$version/$debuggee] Testhost not found at $testHostDir. Run 'build.cmd clr+libs -rc release' first."
+ exit 1
+ }
+
+ & $dotnet build $debuggeeCsproj -c Release -f $tfm --nologo -v q 2>&1 | Out-Null
+ if ($LASTEXITCODE -ne 0) { Write-Error " [$version/$debuggee] Build failed."; exit 1 }
+
+ Set-DumpEnvVars (Join-Path $dumpDir "$debuggee.dmp")
+ & "$testHostDir\dotnet.exe" exec "$binDir\$debuggee.dll" 2>&1 | ForEach-Object { Write-Host " $_" }
+ Clear-DumpEnvVars
+ }
+ else {
+ $publishDir = Join-Path $repoRoot "artifacts\bin\DumpTests\$debuggee\Release\$version-publish"
+
+ & $dotnet publish $debuggeeCsproj -c Release -f $version -r win-x64 --self-contained -o $publishDir --nologo -v q 2>&1 | Out-Null
+ if ($LASTEXITCODE -ne 0) { Write-Error " [$version/$debuggee] Publish failed."; exit 1 }
+
+ Set-DumpEnvVars (Join-Path $dumpDir "$debuggee.dmp")
+ & "$publishDir\$debuggee.exe" 2>&1 | ForEach-Object { Write-Host " $_" }
+ Clear-DumpEnvVars
+ }
+
+ $dumpFile = Join-Path $dumpDir "$debuggee.dmp"
+ if (Test-Path $dumpFile) {
+ $size = [math]::Round((Get-Item $dumpFile).Length / 1MB, 1)
+ Write-Host " [$version/$debuggee] Dump created (${size} MB)" -ForegroundColor Green
+ }
+ else {
+ Write-Error " [$version/$debuggee] Dump was not created!"
+ exit 1
+ }
+ }
+ }
+}
+
+# --- Run tests ---
+if ($Action -in @("test", "all")) {
+ Write-Host ""
+ Write-Host "--- Building test project ---" -ForegroundColor Cyan
+ & $dotnet build $dumpTestsProj --nologo -v q 2>&1 | ForEach-Object { Write-Host " $_" }
+ if ($LASTEXITCODE -ne 0) { Write-Error "Test project build failed."; exit 1 }
+
+ Write-Host ""
+ Write-Host "--- Running tests ---" -ForegroundColor Cyan
+
+ # Build a filter for the selected versions
+ $filters = @()
+ foreach ($version in $selectedVersions) {
+ $suffix = switch ($version) {
+ "local" { "_Local" }
+ "net10.0" { "_Net10" }
+ }
+ $filters += "FullyQualifiedName~$suffix"
+ }
+ $filterExpr = $filters -join " | "
+
+ & $dotnet test $dumpTestsProj --no-build --filter $filterExpr --logger "console;verbosity=detailed" 2>&1 | ForEach-Object { Write-Host " $_" }
+
+ if ($LASTEXITCODE -ne 0) {
+ Write-Host ""
+ Write-Host "TESTS FAILED" -ForegroundColor Red
+ exit 1
+ }
+
+ Write-Host ""
+ Write-Host "ALL TESTS PASSED" -ForegroundColor Green
+}
diff --git a/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs
new file mode 100644
index 00000000000000..63927e3dc41363
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs
@@ -0,0 +1,90 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Diagnostics.DataContractReader.Contracts;
+using Xunit;
+
+namespace Microsoft.Diagnostics.DataContractReader.DumpTests;
+
+///
+/// Dump-based integration tests for the RuntimeTypeSystem contract.
+/// Uses the TypeHierarchy debuggee dump, which loads types with inheritance,
+/// generics, and arrays.
+///
+public abstract class RuntimeTypeSystemDumpTestsBase : DumpTestBase
+{
+ protected RuntimeTypeSystemDumpTestsBase()
+ {
+ LoadDump();
+ }
+
+ protected override string DebuggeeName => "TypeHierarchy";
+
+ [ConditionalFact]
+ [SkipOnRuntimeVersion("net10.0", "Assembly type does not include IsDynamic/IsLoaded fields in .NET 10")]
+ [SkipOnRuntimeVersion("local", "Assembly type does not include IsLoaded field in current contract descriptor")]
+ public void RuntimeTypeSystem_CanGetMethodTableFromModule()
+ {
+ ILoader loader = Target.Contracts.Loader;
+ Assert.NotNull(loader);
+ IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem;
+ Assert.NotNull(rts);
+
+ TargetPointer rootAssembly = loader.GetRootAssembly();
+ ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(rootAssembly);
+ TargetPointer modulePtr = loader.GetModule(moduleHandle);
+ Assert.NotEqual(TargetPointer.Null, modulePtr);
+ }
+
+ [Fact]
+ public void RuntimeTypeSystem_ObjectMethodTableIsValid()
+ {
+ IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem;
+ Assert.NotNull(rts);
+
+ TargetPointer objectMTGlobal = Target.ReadGlobalPointer("ObjectMethodTable");
+ TargetPointer objectMT = Target.ReadPointer(objectMTGlobal);
+ Assert.NotEqual(TargetPointer.Null, objectMT);
+
+ TypeHandle handle = rts.GetTypeHandle(objectMT);
+ Assert.True(rts.IsFreeObjectMethodTable(handle) == false);
+ }
+
+ [Fact]
+ public void RuntimeTypeSystem_FreeObjectMethodTableIsValid()
+ {
+ IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem;
+ Assert.NotNull(rts);
+
+ TargetPointer freeObjMTGlobal = Target.ReadGlobalPointer("FreeObjectMethodTable");
+ TargetPointer freeObjMT = Target.ReadPointer(freeObjMTGlobal);
+ Assert.NotEqual(TargetPointer.Null, freeObjMT);
+
+ TypeHandle handle = rts.GetTypeHandle(freeObjMT);
+ Assert.True(rts.IsFreeObjectMethodTable(handle));
+ }
+
+ [Fact]
+ public void RuntimeTypeSystem_StringMethodTableIsString()
+ {
+ IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem;
+ Assert.NotNull(rts);
+
+ TargetPointer stringMTGlobal = Target.ReadGlobalPointer("StringMethodTable");
+ TargetPointer stringMT = Target.ReadPointer(stringMTGlobal);
+ Assert.NotEqual(TargetPointer.Null, stringMT);
+
+ TypeHandle handle = rts.GetTypeHandle(stringMT);
+ Assert.True(rts.IsString(handle));
+ }
+}
+
+public class RuntimeTypeSystemDumpTests_Local : RuntimeTypeSystemDumpTestsBase
+{
+ protected override string RuntimeVersion => "local";
+}
+
+public class RuntimeTypeSystemDumpTests_Net10 : RuntimeTypeSystemDumpTestsBase
+{
+ protected override string RuntimeVersion => "net10.0";
+}
diff --git a/src/native/managed/cdac/tests/DumpTests/SkipOnRuntimeVersionAttribute.cs b/src/native/managed/cdac/tests/DumpTests/SkipOnRuntimeVersionAttribute.cs
new file mode 100644
index 00000000000000..9d81b13566320e
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/SkipOnRuntimeVersionAttribute.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.Diagnostics.DataContractReader.DumpTests;
+
+///
+/// Apply to a test method to skip it for specific runtime versions.
+/// Throw at runtime when the condition matches.
+///
+[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
+public sealed class SkipOnRuntimeVersionAttribute : Attribute
+{
+ public string Version { get; }
+ public string Reason { get; }
+
+ public SkipOnRuntimeVersionAttribute(string version, string reason)
+ {
+ Version = version;
+ Reason = reason;
+ }
+}
diff --git a/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs
new file mode 100644
index 00000000000000..1b6cb8eb8c4d25
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs
@@ -0,0 +1,114 @@
+// 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;
+using Microsoft.Diagnostics.DataContractReader.Contracts;
+using Xunit;
+
+namespace Microsoft.Diagnostics.DataContractReader.DumpTests;
+
+///
+/// Shared dump-based integration tests for the Thread contract.
+/// Uses the BasicThreads debuggee dump, which spawns 5 named threads then crashes.
+/// Subclasses specify which runtime version's dump to test against.
+///
+public abstract class ThreadDumpTestsBase : DumpTestBase
+{
+ private const int SpawnedThreadCount = 5;
+
+ protected ThreadDumpTestsBase()
+ {
+ LoadDump();
+ }
+
+ protected override string DebuggeeName => "BasicThreads";
+
+ [Fact]
+ public void ThreadStoreData_HasExpectedThreadCount()
+ {
+ IThread threadContract = Target.Contracts.Thread;
+ Assert.NotNull(threadContract);
+
+ ThreadStoreData storeData = threadContract.GetThreadStoreData();
+
+ Assert.True(storeData.ThreadCount >= SpawnedThreadCount + 1,
+ $"Expected at least {SpawnedThreadCount + 1} threads, got {storeData.ThreadCount}");
+ }
+
+ [ConditionalFact]
+ [SkipOnRuntimeVersion("net10.0", "Thread.UEWatsonBucketTrackerBuckets field not present in .NET 10 contract descriptor")]
+ public void EnumerateThreads_CanWalkThreadList()
+ {
+ IThread threadContract = Target.Contracts.Thread;
+ Assert.NotNull(threadContract);
+
+ ThreadStoreData storeData = threadContract.GetThreadStoreData();
+
+ int count = 0;
+ TargetPointer currentThread = storeData.FirstThread;
+ while (currentThread != TargetPointer.Null)
+ {
+ ThreadData threadData = threadContract.GetThreadData(currentThread);
+ count++;
+ currentThread = threadData.NextThread;
+
+ Assert.NotEqual(new TargetNUInt(0), threadData.OSId);
+ }
+
+ Assert.Equal(storeData.ThreadCount, count);
+ }
+
+ [Fact]
+ public void ThreadStoreData_HasFinalizerThread()
+ {
+ IThread threadContract = Target.Contracts.Thread;
+ ThreadStoreData storeData = threadContract.GetThreadStoreData();
+
+ Assert.NotEqual(TargetPointer.Null, storeData.FinalizerThread);
+ }
+
+ [Fact]
+ public void ThreadStoreData_HasGCThread()
+ {
+ IThread threadContract = Target.Contracts.Thread;
+ ThreadStoreData storeData = threadContract.GetThreadStoreData();
+
+ // GC thread may or may not be set depending on runtime state at crash time,
+ // but the field should be readable without throwing.
+ _ = storeData.GCThread;
+ }
+
+ [ConditionalFact]
+ [SkipOnRuntimeVersion("net10.0", "Thread.UEWatsonBucketTrackerBuckets field not present in .NET 10 contract descriptor")]
+ public void Threads_HaveValidIds()
+ {
+ IThread threadContract = Target.Contracts.Thread;
+ ThreadStoreData storeData = threadContract.GetThreadStoreData();
+
+ TargetPointer currentThread = storeData.FirstThread;
+ HashSet seenIds = new();
+
+ while (currentThread != TargetPointer.Null)
+ {
+ ThreadData threadData = threadContract.GetThreadData(currentThread);
+ Assert.True(seenIds.Add(threadData.Id), $"Duplicate thread ID: {threadData.Id}");
+ currentThread = threadData.NextThread;
+ }
+ }
+}
+
+///
+/// Thread contract tests against a dump from the local (in-repo) runtime build.
+///
+public class ThreadDumpTests_Local : ThreadDumpTestsBase
+{
+ protected override string RuntimeVersion => "local";
+}
+
+///
+/// Thread contract tests against a dump from the .NET 10 release runtime.
+///
+public class ThreadDumpTests_Net10 : ThreadDumpTestsBase
+{
+ protected override string RuntimeVersion => "net10.0";
+}
diff --git a/src/native/managed/cdac/tests/Microsoft.Diagnostics.DataContractReader.Tests.csproj b/src/native/managed/cdac/tests/Microsoft.Diagnostics.DataContractReader.Tests.csproj
index deab69ce582e52..c9de2a1bac2da7 100644
--- a/src/native/managed/cdac/tests/Microsoft.Diagnostics.DataContractReader.Tests.csproj
+++ b/src/native/managed/cdac/tests/Microsoft.Diagnostics.DataContractReader.Tests.csproj
@@ -5,6 +5,10 @@
false
+
+
+
+
From 20a18aeac2c1574cc56d6675ad0a743d32befa3d Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Wed, 18 Feb 2026 14:09:49 -0500
Subject: [PATCH 02/28] centralize debuggee props
---
.../DumpTests/Debuggees/BasicThreads/BasicThreads.csproj | 7 -------
.../cdac/tests/DumpTests/Debuggees/Directory.Build.props | 5 +++++
.../Debuggees/ExceptionState/ExceptionState.csproj | 7 -------
.../cdac/tests/DumpTests/Debuggees/GCRoots/GCRoots.csproj | 7 -------
.../DumpTests/Debuggees/MultiModule/MultiModule.csproj | 7 -------
.../DumpTests/Debuggees/TypeHierarchy/TypeHierarchy.csproj | 7 -------
6 files changed, 5 insertions(+), 35 deletions(-)
diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/BasicThreads/BasicThreads.csproj b/src/native/managed/cdac/tests/DumpTests/Debuggees/BasicThreads/BasicThreads.csproj
index a7a4d85fdf3b69..35e3d8428b7cfc 100644
--- a/src/native/managed/cdac/tests/DumpTests/Debuggees/BasicThreads/BasicThreads.csproj
+++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/BasicThreads/BasicThreads.csproj
@@ -1,9 +1,2 @@
-
- Exe
- $(NetCoreAppToolCurrent);net10.0
- true
- $(ArtifactsBinDir)DumpTests\$(MSBuildProjectName)\$(Configuration)\
- true
-
diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/Directory.Build.props b/src/native/managed/cdac/tests/DumpTests/Debuggees/Directory.Build.props
index b76dd352e98826..3bb2049cfe904d 100644
--- a/src/native/managed/cdac/tests/DumpTests/Debuggees/Directory.Build.props
+++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/Directory.Build.props
@@ -2,7 +2,12 @@
+ Exe
+ $(NetCoreAppToolCurrent);net10.0
+ true
enable
+ $(ArtifactsBinDir)DumpTests\$(MSBuildProjectName)\$(Configuration)\
+ true
$(NoWarn);CA1852
diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/ExceptionState/ExceptionState.csproj b/src/native/managed/cdac/tests/DumpTests/Debuggees/ExceptionState/ExceptionState.csproj
index a7a4d85fdf3b69..35e3d8428b7cfc 100644
--- a/src/native/managed/cdac/tests/DumpTests/Debuggees/ExceptionState/ExceptionState.csproj
+++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/ExceptionState/ExceptionState.csproj
@@ -1,9 +1,2 @@
-
- Exe
- $(NetCoreAppToolCurrent);net10.0
- true
- $(ArtifactsBinDir)DumpTests\$(MSBuildProjectName)\$(Configuration)\
- true
-
diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/GCRoots.csproj b/src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/GCRoots.csproj
index a7a4d85fdf3b69..35e3d8428b7cfc 100644
--- a/src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/GCRoots.csproj
+++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/GCRoots.csproj
@@ -1,9 +1,2 @@
-
- Exe
- $(NetCoreAppToolCurrent);net10.0
- true
- $(ArtifactsBinDir)DumpTests\$(MSBuildProjectName)\$(Configuration)\
- true
-
diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/MultiModule/MultiModule.csproj b/src/native/managed/cdac/tests/DumpTests/Debuggees/MultiModule/MultiModule.csproj
index a7a4d85fdf3b69..35e3d8428b7cfc 100644
--- a/src/native/managed/cdac/tests/DumpTests/Debuggees/MultiModule/MultiModule.csproj
+++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/MultiModule/MultiModule.csproj
@@ -1,9 +1,2 @@
-
- Exe
- $(NetCoreAppToolCurrent);net10.0
- true
- $(ArtifactsBinDir)DumpTests\$(MSBuildProjectName)\$(Configuration)\
- true
-
diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/TypeHierarchy/TypeHierarchy.csproj b/src/native/managed/cdac/tests/DumpTests/Debuggees/TypeHierarchy/TypeHierarchy.csproj
index a7a4d85fdf3b69..35e3d8428b7cfc 100644
--- a/src/native/managed/cdac/tests/DumpTests/Debuggees/TypeHierarchy/TypeHierarchy.csproj
+++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/TypeHierarchy/TypeHierarchy.csproj
@@ -1,9 +1,2 @@
-
- Exe
- $(NetCoreAppToolCurrent);net10.0
- true
- $(ArtifactsBinDir)DumpTests\$(MSBuildProjectName)\$(Configuration)\
- true
-
From 03d470d87909f7a964e6452abd27bb17d16ff1c9 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Wed, 18 Feb 2026 14:10:00 -0500
Subject: [PATCH 03/28] add CI pipeline
---
eng/Subsets.props | 7 +-
eng/pipelines/cdac-dump-tests.yml | 71 +++++++++++++++++++
.../cdac/tests/DumpTests/DumpTests.targets | 45 +++++++-----
3 files changed, 104 insertions(+), 19 deletions(-)
create mode 100644 eng/pipelines/cdac-dump-tests.yml
diff --git a/eng/Subsets.props b/eng/Subsets.props
index 2b9028d5df1d61..ccc00152a2bb07 100644
--- a/eng/Subsets.props
+++ b/eng/Subsets.props
@@ -165,7 +165,7 @@
$(AllSubsetsExpansion)+clr.paltests+clr.paltestlist+clr.hosts+clr.jit+clr.alljits+clr.alljitscommunity+clr.spmi+clr.corelib+clr.nativecorelib+clr.tools+clr.toolstests+clr.packages
$(AllSubsetsExpansion)+linuxdac+alpinedac
$(AllSubsetsExpansion)+mono.runtime+provision.emsdk+mono.aotcross+mono.corelib+mono.manifests+mono.packages+mono.tools+mono.wasmruntime+mono.wasiruntime+mono.wasmworkload+mono.mscordbi+mono.workloads
- $(AllSubsetsExpansion)+tools.illink+tools.cdac+tools.illinktests+tools.cdactests
+ $(AllSubsetsExpansion)+tools.illink+tools.cdac+tools.illinktests+tools.cdactests+tools.cdacdumptests
$(AllSubsetsExpansion)+host.native+host.pkg+host.tools+host.pretest+host.tests
$(AllSubsetsExpansion)+libs.native+libs.sfx+libs.oob+libs.pretest+libs.tests
$(AllSubsetsExpansion)+packs.product+packs.installers+packs.tests
@@ -253,6 +253,7 @@
+
@@ -523,6 +524,10 @@
+
+
+
+
diff --git a/eng/pipelines/cdac-dump-tests.yml b/eng/pipelines/cdac-dump-tests.yml
new file mode 100644
index 00000000000000..20d323abbc6de3
--- /dev/null
+++ b/eng/pipelines/cdac-dump-tests.yml
@@ -0,0 +1,71 @@
+trigger: none
+
+schedules:
+- cron: "0 4 * * *"
+ displayName: Nightly cDAC dump tests
+ branches:
+ include:
+ - main
+ always: false
+
+pr:
+ branches:
+ include:
+ - main
+ paths:
+ include:
+ - src/native/managed/cdac/**
+ - src/coreclr/debug/runtimeinfo/**
+
+variables:
+ - template: /eng/pipelines/common/variables.yml
+
+extends:
+ template: /eng/pipelines/common/templates/pipeline-with-resources.yml
+ parameters:
+ stages:
+ - stage: Build
+ jobs:
+
+ #
+ # Build runtime + cDAC, create dumps, and run dump tests
+ #
+ - template: /eng/pipelines/common/platform-matrix.yml
+ parameters:
+ jobTemplate: /eng/pipelines/common/global-build-job.yml
+ buildConfig: release
+ platforms:
+ - windows_x64
+ - linux_x64
+ jobParameters:
+ buildArgs: -s clr+libs+tools.cdac -c $(_BuildConfig) -rc $(_BuildConfig) -lc $(_BuildConfig)
+ nameSuffix: CdacDumpTests
+ timeoutInMinutes: 120
+ postBuildSteps:
+ # Step 1: Build the dump test project (triggers GenerateAllDumps which builds
+ # debuggees, crashes them, and creates dump files; then compiles tests).
+ - script: $(Build.SourcesDirectory)$(dir).dotnet$(dir)dotnet$(exeExt) build
+ $(Build.SourcesDirectory)/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj
+ /p:CIDumpVersionsOnly=true
+ displayName: 'Build Dump Tests and Generate Dumps'
+
+ # Step 2: Run the tests (no-build since we just built above).
+ - script: $(Build.SourcesDirectory)$(dir).dotnet$(dir)dotnet$(exeExt) test
+ $(Build.SourcesDirectory)/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj
+ --no-build
+ --logger "trx;LogFileName=CdacDumpTests.trx"
+ --results-directory $(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)
+ displayName: 'Run cDAC Dump Tests'
+
+ - task: PublishTestResults@2
+ inputs:
+ testResultsFormat: VSTest
+ testResultsFiles: '**/*.trx'
+ searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults'
+ testRunTitle: 'cDAC Dump Tests $(osGroup)-$(archType)'
+ failTaskOnFailedTests: true
+ publishRunAttachments: true
+ mergeTestResults: true
+ buildConfiguration: $(_BuildConfig)
+ continueOnError: true
+ condition: always()
diff --git a/src/native/managed/cdac/tests/DumpTests/DumpTests.targets b/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
index 27fa04f845e4d2..a618a8bd4ec337 100644
--- a/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
+++ b/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
@@ -3,14 +3,15 @@
DumpTests.targets — Builds debuggee apps, runs them to produce crash dumps,
and makes dump paths available to the test project.
- Dump output: $(DumpOutputDir)\{RuntimeVersion}\{DebugeeName}\{DebugeeName}.dmp
+ Dump output: $(DumpOutputDir)/{RuntimeVersion}/{DebugeeName}/{DebugeeName}.dmp
Each Debuggee × RuntimeVersion combination produces one dump.
Properties:
- DumpOutputDir — Where dumps are written (default: artifacts\dumps\cdac\)
+ DumpOutputDir — Where dumps are written (default: artifacts/dumps/cdac/)
TestHostDir — Path to the local-build testhost runtime (auto-detected)
DebuggeeConfiguration — Build configuration for debuggees (default: Release)
+ CIDumpVersionsOnly — When 'true', only generate dumps for "local" version (CI mode)
RuntimeVersion values:
"local" — framework-dependent build, run with the repo's testhost
@@ -18,9 +19,11 @@
-->
- $(RepoRoot)artifacts\dumps\cdac\
+ $([MSBuild]::NormalizeDirectory('$(RepoRoot)', 'artifacts', 'dumps', 'cdac'))
Release
- $(MSBuildThisFileDirectory)Debuggees\
+ $([MSBuild]::NormalizeDirectory('$(MSBuildThisFileDirectory)', 'Debuggees'))
+
+ <_DotNetExe>$([MSBuild]::NormalizePath('$(RepoRoot)', '.dotnet', 'dotnet$(ExeSuffix)'))
@@ -29,7 +32,7 @@
<_TestHostConfig Condition="'$(TestHostConfiguration)' != ''">$(TestHostConfiguration)
<_HostArch>$(TargetArchitecture)
<_HostArch Condition="'$(_HostArch)' == ''">x64
- $(RepoRoot)artifacts\bin\testhost\$(NetCoreAppCurrent)-windows-$(_TestHostConfig)-$(_HostArch)\
+ $([MSBuild]::NormalizeDirectory('$(RepoRoot)', 'artifacts', 'bin', 'testhost', '$(NetCoreAppCurrent)-$(HostOS)-$(_TestHostConfig)-$(_HostArch)'))
@@ -42,10 +45,11 @@
+ "local" uses the repo's testhost; others publish self-contained.
+ Set CIDumpVersionsOnly=true to only generate "local" dumps (for CI). -->
-
+
@@ -62,9 +66,9 @@
- <_DebuggeeDir>$(DebuggeesDir)$(DebuggeeName)\
- <_DumpDir>$(DumpOutputDir)$(DumpRuntimeVersion)\$(DebuggeeName)\
- <_DumpFile>$(_DumpDir)$(DebuggeeName).dmp
+ <_DebuggeeDir>$([MSBuild]::NormalizeDirectory('$(DebuggeesDir)', '$(DebuggeeName)'))
+ <_DumpDir>$([MSBuild]::NormalizeDirectory('$(DumpOutputDir)', '$(DumpRuntimeVersion)', '$(DebuggeeName)'))
+ <_DumpFile>$([MSBuild]::NormalizePath('$(_DumpDir)', '$(DebuggeeName).dmp'))
@@ -87,15 +91,18 @@
- <_DebuggeeBinDir>$(RepoRoot)artifacts\bin\DumpTests\$(DebuggeeName)\$(DebuggeeConfiguration)\$(NetCoreAppCurrent)\
+ <_DebuggeeBinDir>$([MSBuild]::NormalizeDirectory('$(RepoRoot)', 'artifacts', 'bin', 'DumpTests', '$(DebuggeeName)', '$(DebuggeeConfiguration)', '$(NetCoreAppCurrent)'))
+ <_TestHostDotNet>$([MSBuild]::NormalizePath('$(TestHostDir)', 'dotnet$(ExeSuffix)'))
+ <_DebuggeeDll>$([MSBuild]::NormalizePath('$(_DebuggeeBinDir)', '$(DebuggeeName).dll'))
+ <_DebuggeeCsproj>$([MSBuild]::NormalizePath('$(_DebuggeeDir)', '$(DebuggeeName).csproj'))
-
-
@@ -106,15 +113,17 @@
- <_PublishDir>$(RepoRoot)artifacts\bin\DumpTests\$(DebuggeeName)\$(DebuggeeConfiguration)\$(DumpRuntimeVersion)-publish\
+ <_PublishDir>$([MSBuild]::NormalizeDirectory('$(RepoRoot)', 'artifacts', 'bin', 'DumpTests', '$(DebuggeeName)', '$(DebuggeeConfiguration)', '$(DumpRuntimeVersion)-publish'))
+ <_DebuggeeCsproj>$([MSBuild]::NormalizePath('$(_DebuggeeDir)', '$(DebuggeeName).csproj'))
+ <_PublishedExe>$([MSBuild]::NormalizePath('$(_PublishDir)', '$(DebuggeeName)$(ExeSuffix)'))
-
-
@@ -124,7 +133,7 @@
- $(DumpOutputDir)local\BasicThreads\BasicThreads.dmp
- $(DumpOutputDir)net10.0\BasicThreads\BasicThreads.dmp
+ $([MSBuild]::NormalizePath('$(DumpOutputDir)', 'local', 'BasicThreads', 'BasicThreads.dmp'))
+ $([MSBuild]::NormalizePath('$(DumpOutputDir)', 'net10.0', 'BasicThreads', 'BasicThreads.dmp'))
From d06c90f7a80fa3606fef2bcf15a545a96afc8703 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Wed, 18 Feb 2026 14:12:06 -0500
Subject: [PATCH 04/28] address comments
---
src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs | 1 +
src/native/managed/cdac/tests/DumpTests/DumpTests.targets | 2 +-
src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1 | 2 +-
.../managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs | 2 +-
4 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs b/src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs
index 179c82015bb36f..8d18babfa33732 100644
--- a/src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs
+++ b/src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs
@@ -178,5 +178,6 @@ private ulong FindPEExport(ulong imageBase, string symbolName)
public void Dispose()
{
_dataTarget?.Dispose();
+ GC.SuppressFinalize(this);
}
}
diff --git a/src/native/managed/cdac/tests/DumpTests/DumpTests.targets b/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
index a618a8bd4ec337..158219eafb9969 100644
--- a/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
+++ b/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
@@ -3,7 +3,7 @@
DumpTests.targets — Builds debuggee apps, runs them to produce crash dumps,
and makes dump paths available to the test project.
- Dump output: $(DumpOutputDir)/{RuntimeVersion}/{DebugeeName}/{DebugeeName}.dmp
+ Dump output: $(DumpOutputDir)/{RuntimeVersion}/{DebuggeeName}/{DebuggeeName}.dmp
Each Debuggee × RuntimeVersion combination produces one dump.
diff --git a/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1 b/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1
index 1451699f9c9751..ce56177a423714 100644
--- a/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1
+++ b/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1
@@ -107,7 +107,7 @@ if ($Force -and $Action -in @("dumps", "all")) {
$localTfm = $null
function Get-LocalTfm {
if ($null -eq $script:localTfm) {
- $script:localTfm = & $dotnet msbuild $dumpTestsProj /nologo /t:PrintNothing /v:m "/getProperty:NetCoreAppCurrent" 2>$null
+ $script:localTfm = & $dotnet msbuild $dumpTestsProj /nologo /v:m "/getProperty:NetCoreAppCurrent" 2>$null
if ([string]::IsNullOrWhiteSpace($script:localTfm)) { $script:localTfm = "net11.0" }
}
return $script:localTfm
diff --git a/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs
index 63927e3dc41363..a59830408649cf 100644
--- a/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs
+++ b/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs
@@ -47,7 +47,7 @@ public void RuntimeTypeSystem_ObjectMethodTableIsValid()
Assert.NotEqual(TargetPointer.Null, objectMT);
TypeHandle handle = rts.GetTypeHandle(objectMT);
- Assert.True(rts.IsFreeObjectMethodTable(handle) == false);
+ Assert.False(rts.IsFreeObjectMethodTable(handle));
}
[Fact]
From 7bf223f5bedc681bdf50680327031e8cf479e274 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Wed, 18 Feb 2026 14:17:08 -0500
Subject: [PATCH 05/28] move pipeline inside of runtime-diagnostics
---
eng/pipelines/cdac-dump-tests.yml | 71 ---------------------------
eng/pipelines/runtime-diagnostics.yml | 38 ++++++++++++++
2 files changed, 38 insertions(+), 71 deletions(-)
delete mode 100644 eng/pipelines/cdac-dump-tests.yml
diff --git a/eng/pipelines/cdac-dump-tests.yml b/eng/pipelines/cdac-dump-tests.yml
deleted file mode 100644
index 20d323abbc6de3..00000000000000
--- a/eng/pipelines/cdac-dump-tests.yml
+++ /dev/null
@@ -1,71 +0,0 @@
-trigger: none
-
-schedules:
-- cron: "0 4 * * *"
- displayName: Nightly cDAC dump tests
- branches:
- include:
- - main
- always: false
-
-pr:
- branches:
- include:
- - main
- paths:
- include:
- - src/native/managed/cdac/**
- - src/coreclr/debug/runtimeinfo/**
-
-variables:
- - template: /eng/pipelines/common/variables.yml
-
-extends:
- template: /eng/pipelines/common/templates/pipeline-with-resources.yml
- parameters:
- stages:
- - stage: Build
- jobs:
-
- #
- # Build runtime + cDAC, create dumps, and run dump tests
- #
- - template: /eng/pipelines/common/platform-matrix.yml
- parameters:
- jobTemplate: /eng/pipelines/common/global-build-job.yml
- buildConfig: release
- platforms:
- - windows_x64
- - linux_x64
- jobParameters:
- buildArgs: -s clr+libs+tools.cdac -c $(_BuildConfig) -rc $(_BuildConfig) -lc $(_BuildConfig)
- nameSuffix: CdacDumpTests
- timeoutInMinutes: 120
- postBuildSteps:
- # Step 1: Build the dump test project (triggers GenerateAllDumps which builds
- # debuggees, crashes them, and creates dump files; then compiles tests).
- - script: $(Build.SourcesDirectory)$(dir).dotnet$(dir)dotnet$(exeExt) build
- $(Build.SourcesDirectory)/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj
- /p:CIDumpVersionsOnly=true
- displayName: 'Build Dump Tests and Generate Dumps'
-
- # Step 2: Run the tests (no-build since we just built above).
- - script: $(Build.SourcesDirectory)$(dir).dotnet$(dir)dotnet$(exeExt) test
- $(Build.SourcesDirectory)/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj
- --no-build
- --logger "trx;LogFileName=CdacDumpTests.trx"
- --results-directory $(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)
- displayName: 'Run cDAC Dump Tests'
-
- - task: PublishTestResults@2
- inputs:
- testResultsFormat: VSTest
- testResultsFiles: '**/*.trx'
- searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults'
- testRunTitle: 'cDAC Dump Tests $(osGroup)-$(archType)'
- failTaskOnFailedTests: true
- publishRunAttachments: true
- mergeTestResults: true
- buildConfiguration: $(_BuildConfig)
- continueOnError: true
- condition: always()
diff --git a/eng/pipelines/runtime-diagnostics.yml b/eng/pipelines/runtime-diagnostics.yml
index 3fde402abd2009..db064051946e55 100644
--- a/eng/pipelines/runtime-diagnostics.yml
+++ b/eng/pipelines/runtime-diagnostics.yml
@@ -134,3 +134,41 @@ extends:
buildConfiguration: $(_BuildConfig)
continueOnError: true
condition: always()
+
+ #
+ # cDAC Dump Tests — build runtime, create crash dumps, and run dump-based integration tests
+ #
+ - template: /eng/pipelines/common/platform-matrix.yml
+ parameters:
+ jobTemplate: /eng/pipelines/common/global-build-job.yml
+ buildConfig: release
+ platforms:
+ - windows_x64
+ - linux_x64
+ jobParameters:
+ buildArgs: -s clr+libs+tools.cdac -c $(_BuildConfig) -rc $(_BuildConfig) -lc $(_BuildConfig)
+ nameSuffix: CdacDumpTests
+ timeoutInMinutes: 120
+ postBuildSteps:
+ - script: $(Build.SourcesDirectory)$(dir).dotnet$(dir)dotnet$(exeExt) build
+ $(Build.SourcesDirectory)/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj
+ /p:CIDumpVersionsOnly=true
+ displayName: 'Build Dump Tests and Generate Dumps'
+ - script: $(Build.SourcesDirectory)$(dir).dotnet$(dir)dotnet$(exeExt) test
+ $(Build.SourcesDirectory)/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj
+ --no-build
+ --logger "trx;LogFileName=CdacDumpTests.trx"
+ --results-directory $(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)
+ displayName: 'Run cDAC Dump Tests'
+ - task: PublishTestResults@2
+ inputs:
+ testResultsFormat: VSTest
+ testResultsFiles: '**/*.trx'
+ searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults'
+ testRunTitle: 'cDAC Dump Tests $(osGroup)-$(archType)'
+ failTaskOnFailedTests: true
+ publishRunAttachments: true
+ mergeTestResults: true
+ buildConfiguration: $(_BuildConfig)
+ continueOnError: true
+ condition: always()
From 8295470c3a1eea32fee18aab28fcd79621adf8a9 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Wed, 18 Feb 2026 14:26:40 -0500
Subject: [PATCH 06/28] comments
---
.../cdac/tests/DumpTests/ClrMdDumpHost.cs | 96 +------------------
.../cdac/tests/DumpTests/DumpTests.targets | 5 -
.../cdac/tests/DumpTests/RunDumpTests.ps1 | 6 +-
3 files changed, 7 insertions(+), 100 deletions(-)
diff --git a/src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs b/src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs
index 8d18babfa33732..7dd0d1c036adde 100644
--- a/src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs
+++ b/src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs
@@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
-using System.Runtime.InteropServices;
using Microsoft.Diagnostics.Runtime;
namespace Microsoft.Diagnostics.DataContractReader.DumpTests;
@@ -51,10 +50,10 @@ public int ReadFromTarget(ulong address, Span buffer)
///
/// Locate the DotNetRuntimeContractDescriptor symbol address in the dump.
+ /// Uses ClrMD's built-in export resolution which handles PE, ELF, and Mach-O formats.
///
public ulong FindContractDescriptorAddress()
{
- // Find the native coreclr module via DataReader (not ClrRuntime, which only lists managed assemblies)
foreach (ModuleInfo module in _dataTarget.DataReader.EnumerateModules())
{
string? fileName = module.FileName;
@@ -65,7 +64,7 @@ public ulong FindContractDescriptorAddress()
if (!IsRuntimeModule(name))
continue;
- ulong address = FindPEExport(module.ImageBase, "DotNetRuntimeContractDescriptor");
+ ulong address = module.GetExportSymbolAddress("DotNetRuntimeContractDescriptor");
if (address != 0)
return address;
}
@@ -84,97 +83,6 @@ private static bool IsRuntimeModule(string fileName)
return false;
}
- private ulong FindPEExport(ulong imageBase, string symbolName)
- {
- // Read the DOS header to find the PE header
- Span dosHeader = stackalloc byte[64];
- if (ReadFromTarget(imageBase, dosHeader) != 0)
- return 0;
-
- // e_lfanew is at offset 0x3C
- int peOffset = MemoryMarshal.Read(dosHeader.Slice(0x3C));
-
- // Read PE signature + COFF header
- Span peHeader = stackalloc byte[4 + 20];
- if (ReadFromTarget(imageBase + (ulong)peOffset, peHeader) != 0)
- return 0;
-
- if (MemoryMarshal.Read(peHeader) != 0x00004550) // "PE\0\0"
- return 0;
-
- // Determine PE32 or PE32+ to find export directory offset
- uint optionalHeaderOffset = (uint)peOffset + 4 + 20;
- Span magic = stackalloc byte[2];
- if (ReadFromTarget(imageBase + optionalHeaderOffset, magic) != 0)
- return 0;
-
- ushort peMagic = MemoryMarshal.Read(magic);
- uint exportDirRvaOffset = peMagic switch
- {
- 0x10b => optionalHeaderOffset + 96, // PE32
- 0x20b => optionalHeaderOffset + 112, // PE32+
- _ => 0,
- };
- if (exportDirRvaOffset == 0)
- return 0;
-
- // Read export directory RVA
- Span exportDirEntry = stackalloc byte[8];
- if (ReadFromTarget(imageBase + exportDirRvaOffset, exportDirEntry) != 0)
- return 0;
-
- uint exportRva = MemoryMarshal.Read(exportDirEntry);
- if (exportRva == 0)
- return 0;
-
- // Read export directory header (40 bytes)
- Span exportDir = stackalloc byte[40];
- if (ReadFromTarget(imageBase + exportRva, exportDir) != 0)
- return 0;
-
- uint numberOfNames = MemoryMarshal.Read(exportDir.Slice(24));
- uint addressOfFunctions = MemoryMarshal.Read(exportDir.Slice(28));
- uint addressOfNames = MemoryMarshal.Read(exportDir.Slice(32));
- uint addressOfNameOrdinals = MemoryMarshal.Read(exportDir.Slice(36));
-
- // Search the name pointer table for the symbol
- for (uint i = 0; i < numberOfNames; i++)
- {
- Span nameRvaBytes = stackalloc byte[4];
- if (ReadFromTarget(imageBase + addressOfNames + i * 4, nameRvaBytes) != 0)
- continue;
-
- uint nameRva = MemoryMarshal.Read(nameRvaBytes);
-
- Span nameBytes = stackalloc byte[64];
- if (ReadFromTarget(imageBase + nameRva, nameBytes) != 0)
- continue;
-
- int nullIndex = nameBytes.IndexOf((byte)0);
- if (nullIndex < 0)
- continue;
-
- string name = System.Text.Encoding.ASCII.GetString(nameBytes.Slice(0, nullIndex));
- if (name != symbolName)
- continue;
-
- Span ordinalBytes = stackalloc byte[2];
- if (ReadFromTarget(imageBase + addressOfNameOrdinals + i * 2, ordinalBytes) != 0)
- return 0;
-
- ushort ordinal = MemoryMarshal.Read(ordinalBytes);
-
- Span funcRvaBytes = stackalloc byte[4];
- if (ReadFromTarget(imageBase + addressOfFunctions + ordinal * 4u, funcRvaBytes) != 0)
- return 0;
-
- uint funcRva = MemoryMarshal.Read(funcRvaBytes);
- return imageBase + funcRva;
- }
-
- return 0;
- }
-
public void Dispose()
{
_dataTarget?.Dispose();
diff --git a/src/native/managed/cdac/tests/DumpTests/DumpTests.targets b/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
index 158219eafb9969..957de72da48dd8 100644
--- a/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
+++ b/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
@@ -131,9 +131,4 @@
-
-
- $([MSBuild]::NormalizePath('$(DumpOutputDir)', 'local', 'BasicThreads', 'BasicThreads.dmp'))
- $([MSBuild]::NormalizePath('$(DumpOutputDir)', 'net10.0', 'BasicThreads', 'BasicThreads.dmp'))
-
diff --git a/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1 b/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1
index ce56177a423714..3a9c0b5134cde7 100644
--- a/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1
+++ b/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1
@@ -3,11 +3,15 @@
Generates crash dumps and/or runs cDAC dump-based integration tests.
.DESCRIPTION
- This script orchestrates the cDAC dump test workflow:
+ This script orchestrates the cDAC dump test workflow on Windows:
1. Build debuggee apps for the selected runtime version(s)
2. Run them to produce crash dumps
3. Build and run the dump analysis tests
+ NOTE: This script is Windows-only. For cross-platform CI builds,
+ the MSBuild-based DumpTests.targets handles platform detection
+ automatically via $(HostOS), $(ExeSuffix), and $(PortableTargetRid).
+
Dumps are written to: artifacts\dumps\cdac\{version}\{debuggee}\
The script must be run from the DumpTests directory.
From eb8ad7d332224fde23f041df8b53b77a1504903f Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Wed, 18 Feb 2026 15:26:49 -0500
Subject: [PATCH 07/28] update yaml for cross-platform dump testing
---
eng/pipelines/runtime-diagnostics.yml | 61 ++++++++++++++--
.../cdac/tests/DumpTests/DumpTestBase.cs | 71 ++++++++++++++-----
.../cdac/tests/DumpTests/DumpTests.targets | 4 +-
.../SkipOnRuntimeVersionAttribute.cs | 22 +++++-
4 files changed, 134 insertions(+), 24 deletions(-)
diff --git a/eng/pipelines/runtime-diagnostics.yml b/eng/pipelines/runtime-diagnostics.yml
index db064051946e55..b7179b04791bfd 100644
--- a/eng/pipelines/runtime-diagnostics.yml
+++ b/eng/pipelines/runtime-diagnostics.yml
@@ -136,7 +136,7 @@ extends:
condition: always()
#
- # cDAC Dump Tests — build runtime, create crash dumps, and run dump-based integration tests
+ # cDAC Dump Tests — Stage 1: Build runtime, create crash dumps, publish dump artifacts
#
- template: /eng/pipelines/common/platform-matrix.yml
parameters:
@@ -147,19 +147,72 @@ extends:
- linux_x64
jobParameters:
buildArgs: -s clr+libs+tools.cdac -c $(_BuildConfig) -rc $(_BuildConfig) -lc $(_BuildConfig)
- nameSuffix: CdacDumpTests
+ nameSuffix: CdacDumpGeneration
timeoutInMinutes: 120
postBuildSteps:
- script: $(Build.SourcesDirectory)$(dir).dotnet$(dir)dotnet$(exeExt) build
$(Build.SourcesDirectory)/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj
/p:CIDumpVersionsOnly=true
displayName: 'Build Dump Tests and Generate Dumps'
+ - template: /eng/pipelines/common/upload-artifact-step.yml
+ parameters:
+ rootFolder: $(Build.SourcesDirectory)/artifacts/dumps/cdac
+ includeRootFolder: false
+ archiveType: $(archiveType)
+ archiveExtension: $(archiveExtension)
+ tarCompression: $(tarCompression)
+ artifactName: CdacDumps_$(osGroup)_$(archType)
+ displayName: cDAC Dump Artifacts
+
+ #
+ # cDAC Dump Tests — Stage 2: Download dumps from all platforms, run tests cross-platform
+ #
+ - template: /eng/pipelines/common/platform-matrix.yml
+ parameters:
+ jobTemplate: /eng/pipelines/common/global-build-job.yml
+ buildConfig: release
+ platforms:
+ - windows_x64
+ - linux_x64
+ jobParameters:
+ buildArgs: -s tools.cdacdumptests /p:SkipDumpGeneration=true
+ nameSuffix: CdacDumpTests
+ timeoutInMinutes: 60
+ dependsOn:
+ - build_windows_x64_release_CdacDumpGeneration
+ - build_linux_x64_release_CdacDumpGeneration
+ postBuildSteps:
+ # Download dumps from all platforms
+ - template: /eng/pipelines/common/download-artifact-step.yml
+ parameters:
+ artifactName: CdacDumps_windows_x64
+ artifactFileName: CdacDumps_windows_x64$(archiveExtension)
+ unpackFolder: $(Build.SourcesDirectory)/artifacts/dumps/windows_x64
+ displayName: 'Windows x64 Dumps'
+ - template: /eng/pipelines/common/download-artifact-step.yml
+ parameters:
+ artifactName: CdacDumps_linux_x64
+ artifactFileName: CdacDumps_linux_x64$(archiveExtension)
+ unpackFolder: $(Build.SourcesDirectory)/artifacts/dumps/linux_x64
+ displayName: 'Linux x64 Dumps'
+ # Run tests against Windows dumps
+ - script: $(Build.SourcesDirectory)$(dir).dotnet$(dir)dotnet$(exeExt) test
+ $(Build.SourcesDirectory)/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj
+ --no-build
+ --logger "trx;LogFileName=CdacDumpTests_windows_x64.trx"
+ --results-directory $(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)
+ displayName: 'Run cDAC Dump Tests (windows_x64 dumps)'
+ env:
+ CDAC_DUMP_ROOT: $(Build.SourcesDirectory)/artifacts/dumps/windows_x64
+ # Run tests against Linux dumps
- script: $(Build.SourcesDirectory)$(dir).dotnet$(dir)dotnet$(exeExt) test
$(Build.SourcesDirectory)/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj
--no-build
- --logger "trx;LogFileName=CdacDumpTests.trx"
+ --logger "trx;LogFileName=CdacDumpTests_linux_x64.trx"
--results-directory $(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)
- displayName: 'Run cDAC Dump Tests'
+ displayName: 'Run cDAC Dump Tests (linux_x64 dumps)'
+ env:
+ CDAC_DUMP_ROOT: $(Build.SourcesDirectory)/artifacts/dumps/linux_x64
- task: PublishTestResults@2
inputs:
testResultsFormat: VSTest
diff --git a/src/native/managed/cdac/tests/DumpTests/DumpTestBase.cs b/src/native/managed/cdac/tests/DumpTests/DumpTestBase.cs
index 153ae6593011e9..692204e467741f 100644
--- a/src/native/managed/cdac/tests/DumpTests/DumpTestBase.cs
+++ b/src/native/managed/cdac/tests/DumpTests/DumpTestBase.cs
@@ -5,6 +5,7 @@
using System.IO;
using System.Reflection;
using System.Threading;
+using Microsoft.Diagnostics.DataContractReader.Contracts;
using Microsoft.DotNet.XUnitExtensions;
using Xunit;
using Xunit.Sdk;
@@ -12,23 +13,36 @@
namespace Microsoft.Diagnostics.DataContractReader.DumpTests;
///
-/// Automatically checks on test methods
-/// before they execute. Applied at the class level on ,
-/// so all derived test classes get automatic version-aware skipping.
+/// Automatically checks and
+/// on test methods before they execute.
+/// Applied at the class level on , so all derived
+/// test classes get automatic version-aware and OS-aware skipping.
///
public sealed class CheckSkipOnRuntimeVersionAttribute : BeforeAfterTestAttribute
{
public override void Before(MethodInfo methodUnderTest)
{
string? version = DumpTestBase.CurrentRuntimeVersion.Value;
- if (version is null)
- return;
+ if (version is not null)
+ {
+ foreach (SkipOnRuntimeVersionAttribute attr in methodUnderTest.GetCustomAttributes())
+ {
+ if (string.Equals(attr.Version, version, StringComparison.OrdinalIgnoreCase))
+ {
+ throw new SkipTestException($"[{version}] {attr.Reason}");
+ }
+ }
+ }
- foreach (SkipOnRuntimeVersionAttribute attr in methodUnderTest.GetCustomAttributes())
+ string? targetOS = DumpTestBase.CurrentTargetOS.Value;
+ if (targetOS is not null)
{
- if (string.Equals(attr.Version, version, StringComparison.OrdinalIgnoreCase))
+ foreach (SkipOnTargetOSAttribute attr in methodUnderTest.GetCustomAttributes())
{
- throw new SkipTestException($"[{version}] {attr.Reason}");
+ if (string.Equals(attr.OperatingSystem, targetOS, StringComparison.OrdinalIgnoreCase))
+ {
+ throw new SkipTestException($"[TargetOS={targetOS}] {attr.Reason}");
+ }
}
}
}
@@ -40,18 +54,20 @@ public override void Before(MethodInfo methodUnderTest)
/// shared helpers for assertions.
///
///
-/// Tests that need version-aware skipping should:
+/// Tests that need version-aware or OS-aware skipping should:
///
/// - Use [ConditionalFact] instead of [Fact]
-/// - Apply [SkipOnRuntimeVersion("version", "reason")] to the method
+/// - Apply [SkipOnRuntimeVersion("version", "reason")] or
+/// [SkipOnTargetOS("Windows", "reason")] to the method
///
/// The on this class automatically
-/// evaluates skip conditions — no manual SkipIfVersionExcluded() call required.
+/// evaluates skip conditions — no manual skip call required.
///
[CheckSkipOnRuntimeVersion]
public abstract class DumpTestBase : IDisposable
{
internal static readonly AsyncLocal CurrentRuntimeVersion = new();
+ internal static readonly AsyncLocal CurrentTargetOS = new();
private ClrMdDumpHost? _host;
private ContractDescriptorTarget? _target;
@@ -73,15 +89,23 @@ public abstract class DumpTestBase : IDisposable
///
/// Resolves the dump file path for the current debuggee and runtime version.
- /// Convention: {RepoRoot}/artifacts/dumps/cdac/{RuntimeVersion}/{DebuggeeName}/{DebuggeeName}.dmp
+ /// If the CDAC_DUMP_ROOT environment variable is set, uses that as the base directory.
+ /// Otherwise falls back to {RepoRoot}/artifacts/dumps/cdac/.
+ /// Convention: {root}/{RuntimeVersion}/{DebuggeeName}/{DebuggeeName}.dmp
///
protected string GetDumpPath()
{
- string? repoRoot = FindRepoRoot();
- if (repoRoot is null)
- throw new InvalidOperationException("Could not locate the repository root.");
+ string? dumpRoot = Environment.GetEnvironmentVariable("CDAC_DUMP_ROOT");
+ if (string.IsNullOrEmpty(dumpRoot))
+ {
+ string? repoRoot = FindRepoRoot();
+ if (repoRoot is null)
+ throw new InvalidOperationException("Could not locate the repository root.");
+
+ dumpRoot = Path.Combine(repoRoot, "artifacts", "dumps", "cdac");
+ }
- return Path.Combine(repoRoot, "artifacts", "dumps", "cdac", RuntimeVersion, DebuggeeName, $"{DebuggeeName}.dmp");
+ return Path.Combine(dumpRoot, RuntimeVersion, DebuggeeName, $"{DebuggeeName}.dmp");
}
///
@@ -103,11 +127,22 @@ protected void LoadDump()
contractDescriptor,
_host.ReadFromTarget,
writeToTarget: null!,
- getThreadContext: null!,
+ _host.GetThreadContext,
additionalFactories: [],
out _target);
Assert.True(created, $"Failed to create ContractDescriptorTarget from dump: {dumpPath}");
+
+ // Set the target OS for SkipOnTargetOS attribute evaluation
+ try
+ {
+ RuntimeInfoOperatingSystem os = _target!.Contracts.RuntimeInfo.GetTargetOperatingSystem();
+ CurrentTargetOS.Value = os.ToString();
+ }
+ catch
+ {
+ // RuntimeInfo contract may not be available; leave CurrentTargetOS null
+ }
}
private static string? FindRepoRoot()
@@ -126,6 +161,6 @@ protected void LoadDump()
public void Dispose()
{
_host?.Dispose();
- GC.SuppressFinalize(this);
+ System.GC.SuppressFinalize(this);
}
}
diff --git a/src/native/managed/cdac/tests/DumpTests/DumpTests.targets b/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
index 957de72da48dd8..24118b04bcbcd6 100644
--- a/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
+++ b/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
@@ -12,6 +12,7 @@
TestHostDir — Path to the local-build testhost runtime (auto-detected)
DebuggeeConfiguration — Build configuration for debuggees (default: Release)
CIDumpVersionsOnly — When 'true', only generate dumps for "local" version (CI mode)
+ SkipDumpGeneration — When 'true', skip dump generation entirely (CI test-only phase)
RuntimeVersion values:
"local" — framework-dependent build, run with the repo's testhost
@@ -42,6 +43,7 @@
+
+
+
+
+
diff --git a/src/native/managed/cdac/tests/DumpTests/ObjectDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/ObjectDumpTests.cs
index 1fc54f70c1d265..4902404a406bbd 100644
--- a/src/native/managed/cdac/tests/DumpTests/ObjectDumpTests.cs
+++ b/src/native/managed/cdac/tests/DumpTests/ObjectDumpTests.cs
@@ -15,7 +15,7 @@ public abstract class ObjectDumpTestsBase : DumpTestBase
{
protected override string DebuggeeName => "GCRoots";
- [Fact]
+ [ConditionalFact]
public void Object_ContractIsAvailable()
{
IObject objectContract = Target.Contracts.Object;
@@ -101,7 +101,7 @@ public void GC_BoundsAreReasonable()
$"Expected GC min address (0x{minAddr:X}) < max address (0x{maxAddr:X})");
}
- [Fact]
+ [ConditionalFact]
public void Object_StringMethodTableHasCorrectComponentSize()
{
IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem;
diff --git a/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1 b/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1
index ab132106ca16dd..3ec50c189a6495 100644
--- a/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1
+++ b/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1
@@ -31,6 +31,11 @@
Configuration of the testhost used for the "local" runtime version.
Default: "Release"
+.PARAMETER Filter
+ Glob-style filter for test names. Uses substring matching.
+ Examples: "*StackWalk*", "*Thread*", "*GC_Heap*"
+ Default: "" (run all tests)
+
.EXAMPLE
.\RunDumpTests.ps1
@@ -42,6 +47,9 @@
.EXAMPLE
.\RunDumpTests.ps1 -Action test -Versions local
+
+.EXAMPLE
+ .\RunDumpTests.ps1 -Filter "*StackWalk*"
#>
[CmdletBinding()]
@@ -53,7 +61,9 @@ param(
[switch]$Force,
- [string]$TestHostConfiguration = "Release"
+ [string]$TestHostConfiguration = "Release",
+
+ [string]$Filter = ""
)
Set-StrictMode -Version Latest
@@ -94,6 +104,7 @@ Write-Host " Action: $Action"
Write-Host " Versions: $($selectedVersions -join ', ')"
Write-Host " Debuggees: $($allDebugees -join ', ')"
Write-Host " Force: $Force"
+if ($Filter) { Write-Host " Filter: $Filter" }
Write-Host ""
# --- Force: delete existing dumps ---
@@ -219,6 +230,13 @@ if ($Action -in @("test", "all")) {
}
$filterExpr = $filters -join " | "
+ # Apply user-supplied name filter (glob-style: * maps to dotnet test's ~ operator)
+ if ($Filter) {
+ # Convert glob wildcards to dotnet test FullyQualifiedName contains filter
+ $namePattern = $Filter.Replace("*", "")
+ $filterExpr = "($filterExpr) & FullyQualifiedName~$namePattern"
+ }
+
# dotnet test writes failure details to stderr; suppress termination so we see full results.
$saved = $ErrorActionPreference
$ErrorActionPreference = "Continue"
diff --git a/src/native/managed/cdac/tests/DumpTests/RuntimeInfoDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/RuntimeInfoDumpTests.cs
index 4477820108cb0f..b0002843659a43 100644
--- a/src/native/managed/cdac/tests/DumpTests/RuntimeInfoDumpTests.cs
+++ b/src/native/managed/cdac/tests/DumpTests/RuntimeInfoDumpTests.cs
@@ -15,14 +15,14 @@ public abstract class RuntimeInfoDumpTestsBase : DumpTestBase
{
protected override string DebuggeeName => "BasicThreads";
- [Fact]
+ [ConditionalFact]
public void RuntimeInfo_ContractIsAvailable()
{
IRuntimeInfo runtimeInfo = Target.Contracts.RuntimeInfo;
Assert.NotNull(runtimeInfo);
}
- [Fact]
+ [ConditionalFact]
public void RuntimeInfo_ArchitectureIsValid()
{
IRuntimeInfo runtimeInfo = Target.Contracts.RuntimeInfo;
@@ -32,7 +32,7 @@ public void RuntimeInfo_ArchitectureIsValid()
$"Expected a valid RuntimeInfoArchitecture enum value, got {arch}");
}
- [Fact]
+ [ConditionalFact]
public void RuntimeInfo_OperatingSystemIsValid()
{
IRuntimeInfo runtimeInfo = Target.Contracts.RuntimeInfo;
diff --git a/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs
index 813bfb4f5d606d..14acb4a04ff6e6 100644
--- a/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs
+++ b/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs
@@ -34,7 +34,7 @@ public void RuntimeTypeSystem_CanGetMethodTableFromModule()
Assert.NotEqual(TargetPointer.Null, modulePtr);
}
- [Fact]
+ [ConditionalFact]
public void RuntimeTypeSystem_ObjectMethodTableIsValid()
{
IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem;
@@ -48,7 +48,7 @@ public void RuntimeTypeSystem_ObjectMethodTableIsValid()
Assert.False(rts.IsFreeObjectMethodTable(handle));
}
- [Fact]
+ [ConditionalFact]
public void RuntimeTypeSystem_FreeObjectMethodTableIsValid()
{
IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem;
@@ -62,7 +62,7 @@ public void RuntimeTypeSystem_FreeObjectMethodTableIsValid()
Assert.True(rts.IsFreeObjectMethodTable(handle));
}
- [Fact]
+ [ConditionalFact]
public void RuntimeTypeSystem_StringMethodTableIsString()
{
IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem;
@@ -76,7 +76,7 @@ public void RuntimeTypeSystem_StringMethodTableIsString()
Assert.True(rts.IsString(handle));
}
- [Fact]
+ [ConditionalFact]
public void RuntimeTypeSystem_ObjectMethodTableHasParent()
{
IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem;
@@ -90,7 +90,7 @@ public void RuntimeTypeSystem_ObjectMethodTableHasParent()
Assert.Equal(TargetPointer.Null, parent);
}
- [Fact]
+ [ConditionalFact]
public void RuntimeTypeSystem_StringHasObjectParent()
{
IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem;
@@ -107,7 +107,7 @@ public void RuntimeTypeSystem_StringHasObjectParent()
Assert.Equal(objectMT, parent);
}
- [Fact]
+ [ConditionalFact]
public void RuntimeTypeSystem_ObjectMethodTableHasReasonableBaseSize()
{
IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem;
@@ -121,7 +121,7 @@ public void RuntimeTypeSystem_ObjectMethodTableHasReasonableBaseSize()
$"Expected System.Object base size between 1 and 1024, got {baseSize}");
}
- [Fact]
+ [ConditionalFact]
public void RuntimeTypeSystem_StringHasNonZeroComponentSize()
{
IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem;
@@ -135,7 +135,7 @@ public void RuntimeTypeSystem_StringHasNonZeroComponentSize()
Assert.Equal(2u, componentSize);
}
- [Fact]
+ [ConditionalFact]
public void RuntimeTypeSystem_ObjectMethodTableContainsNoGCPointers()
{
IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem;
@@ -148,7 +148,7 @@ public void RuntimeTypeSystem_ObjectMethodTableContainsNoGCPointers()
Assert.False(rts.ContainsGCPointers(handle));
}
- [Fact]
+ [ConditionalFact]
public void RuntimeTypeSystem_ObjectMethodTableHasValidToken()
{
IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem;
@@ -162,7 +162,7 @@ public void RuntimeTypeSystem_ObjectMethodTableHasValidToken()
Assert.Equal(0x02000000u, token & 0xFF000000u);
}
- [Fact]
+ [ConditionalFact]
public void RuntimeTypeSystem_ObjectMethodTableHasMethods()
{
IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem;
@@ -176,7 +176,7 @@ public void RuntimeTypeSystem_ObjectMethodTableHasMethods()
Assert.True(numMethods >= 4, $"Expected System.Object to have at least 4 methods, got {numMethods}");
}
- [Fact]
+ [ConditionalFact]
public void RuntimeTypeSystem_StringIsNotGenericTypeDefinition()
{
IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem;
@@ -188,7 +188,7 @@ public void RuntimeTypeSystem_StringIsNotGenericTypeDefinition()
Assert.False(rts.IsGenericTypeDefinition(handle));
}
- [Fact]
+ [ConditionalFact]
public void RuntimeTypeSystem_StringCorElementTypeIsClass()
{
IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem;
@@ -203,7 +203,7 @@ public void RuntimeTypeSystem_StringCorElementTypeIsClass()
Assert.Equal(CorElementType.Class, corType);
}
- [Fact]
+ [ConditionalFact]
public void RuntimeTypeSystem_ObjectMethodTableHasIntroducedMethods()
{
IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem;
diff --git a/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs
index 71898ac2115a23..3de122e9e11c27 100644
--- a/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs
+++ b/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs
@@ -18,7 +18,7 @@ public abstract class ThreadDumpTestsBase : DumpTestBase
protected override string DebuggeeName => "BasicThreads";
- [Fact]
+ [ConditionalFact]
public void ThreadStoreData_HasExpectedThreadCount()
{
IThread threadContract = Target.Contracts.Thread;
@@ -54,7 +54,7 @@ public void EnumerateThreads_CanWalkThreadList()
Assert.Equal(storeData.ThreadCount, count);
}
- [Fact]
+ [ConditionalFact]
public void ThreadStoreData_HasFinalizerThread()
{
IThread threadContract = Target.Contracts.Thread;
@@ -63,7 +63,7 @@ public void ThreadStoreData_HasFinalizerThread()
Assert.NotEqual(TargetPointer.Null, storeData.FinalizerThread);
}
- [Fact]
+ [ConditionalFact]
public void ThreadStoreData_HasGCThread()
{
IThread threadContract = Target.Contracts.Thread;
@@ -93,7 +93,7 @@ public void Threads_HaveValidIds()
}
}
- [Fact]
+ [ConditionalFact]
public void ThreadCounts_AreNonNegative()
{
IThread threadContract = Target.Contracts.Thread;
From 7686c17eea8ff9164239c62c281d0ab3870fc9f6 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Wed, 18 Feb 2026 16:58:20 -0500
Subject: [PATCH 12/28] refactor
---
eng/pipelines/runtime-diagnostics.yml | 25 +++++++++++++++----------
1 file changed, 15 insertions(+), 10 deletions(-)
diff --git a/eng/pipelines/runtime-diagnostics.yml b/eng/pipelines/runtime-diagnostics.yml
index b7179b04791bfd..4ceb1da01d84d0 100644
--- a/eng/pipelines/runtime-diagnostics.yml
+++ b/eng/pipelines/runtime-diagnostics.yml
@@ -135,9 +135,11 @@ extends:
continueOnError: true
condition: always()
- #
- # cDAC Dump Tests — Stage 1: Build runtime, create crash dumps, publish dump artifacts
- #
+ #
+ # cDAC Dump Creation — Build runtime, create crash dumps, publish dump artifacts
+ #
+ - stage: DumpCreation
+ jobs:
- template: /eng/pipelines/common/platform-matrix.yml
parameters:
jobTemplate: /eng/pipelines/common/global-build-job.yml
@@ -164,9 +166,13 @@ extends:
artifactName: CdacDumps_$(osGroup)_$(archType)
displayName: cDAC Dump Artifacts
- #
- # cDAC Dump Tests — Stage 2: Download dumps from all platforms, run tests cross-platform
- #
+ #
+ # cDAC Dump Tests — Download dumps from all platforms, run tests cross-platform
+ #
+ - stage: DumpTest
+ dependsOn:
+ - DumpCreation
+ jobs:
- template: /eng/pipelines/common/platform-matrix.yml
parameters:
jobTemplate: /eng/pipelines/common/global-build-job.yml
@@ -175,12 +181,9 @@ extends:
- windows_x64
- linux_x64
jobParameters:
- buildArgs: -s tools.cdacdumptests /p:SkipDumpGeneration=true
+ buildArgs: -s tools.cdacdumptests /p:SkipDumpGeneration=true /p:SkipDumpVersions=net10.0
nameSuffix: CdacDumpTests
timeoutInMinutes: 60
- dependsOn:
- - build_windows_x64_release_CdacDumpGeneration
- - build_linux_x64_release_CdacDumpGeneration
postBuildSteps:
# Download dumps from all platforms
- template: /eng/pipelines/common/download-artifact-step.yml
@@ -202,6 +205,7 @@ extends:
--logger "trx;LogFileName=CdacDumpTests_windows_x64.trx"
--results-directory $(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)
displayName: 'Run cDAC Dump Tests (windows_x64 dumps)'
+ continueOnError: true
env:
CDAC_DUMP_ROOT: $(Build.SourcesDirectory)/artifacts/dumps/windows_x64
# Run tests against Linux dumps
@@ -211,6 +215,7 @@ extends:
--logger "trx;LogFileName=CdacDumpTests_linux_x64.trx"
--results-directory $(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)
displayName: 'Run cDAC Dump Tests (linux_x64 dumps)'
+ continueOnError: true
env:
CDAC_DUMP_ROOT: $(Build.SourcesDirectory)/artifacts/dumps/linux_x64
- task: PublishTestResults@2
From b4ad6eea709431a5ca0b0b6ef4b219b20ea6ffd1 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Wed, 18 Feb 2026 17:02:02 -0500
Subject: [PATCH 13/28] run stages in parallel
---
eng/pipelines/runtime-diagnostics.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/eng/pipelines/runtime-diagnostics.yml b/eng/pipelines/runtime-diagnostics.yml
index 4ceb1da01d84d0..5c5e04f4723157 100644
--- a/eng/pipelines/runtime-diagnostics.yml
+++ b/eng/pipelines/runtime-diagnostics.yml
@@ -139,6 +139,7 @@ extends:
# cDAC Dump Creation — Build runtime, create crash dumps, publish dump artifacts
#
- stage: DumpCreation
+ dependsOn: []
jobs:
- template: /eng/pipelines/common/platform-matrix.yml
parameters:
From d02012e9b8159cd053759edd393c3d2cac2e08b1 Mon Sep 17 00:00:00 2001
From: Steve Pfister
Date: Wed, 18 Feb 2026 19:04:43 -0500
Subject: [PATCH 14/28] Fix cross-platform cDAC dump test artifact download
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The artifactFileName used $(archiveExtension) which resolves to the
downloading platform's archive format (.zip on Windows, .tar.gz on
Linux). When downloading cross-platform dumps, this produces the wrong
filename — e.g., a Linux agent looks for CdacDumps_windows_x64.tar.gz
but the artifact was uploaded from Windows as CdacDumps_windows_x64.zip.
Hardcode the correct extension for each platform's artifact so the
ExtractFiles glob matches regardless of which platform downloads it.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
eng/pipelines/runtime-diagnostics.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/eng/pipelines/runtime-diagnostics.yml b/eng/pipelines/runtime-diagnostics.yml
index 5c5e04f4723157..388f9503413e75 100644
--- a/eng/pipelines/runtime-diagnostics.yml
+++ b/eng/pipelines/runtime-diagnostics.yml
@@ -190,13 +190,13 @@ extends:
- template: /eng/pipelines/common/download-artifact-step.yml
parameters:
artifactName: CdacDumps_windows_x64
- artifactFileName: CdacDumps_windows_x64$(archiveExtension)
+ artifactFileName: CdacDumps_windows_x64.zip
unpackFolder: $(Build.SourcesDirectory)/artifacts/dumps/windows_x64
displayName: 'Windows x64 Dumps'
- template: /eng/pipelines/common/download-artifact-step.yml
parameters:
artifactName: CdacDumps_linux_x64
- artifactFileName: CdacDumps_linux_x64$(archiveExtension)
+ artifactFileName: CdacDumps_linux_x64.tar.gz
unpackFolder: $(Build.SourcesDirectory)/artifacts/dumps/linux_x64
displayName: 'Linux x64 Dumps'
# Run tests against Windows dumps
From b5ba4455da54331d18b8a53e06429928665510ea Mon Sep 17 00:00:00 2001
From: Steve Pfister
Date: Wed, 18 Feb 2026 21:21:03 -0500
Subject: [PATCH 15/28] Standardize cDAC dump artifacts on tar.gz format
The dump artifacts are consumed cross-platform, but the upload used
the platform-specific archive format (zip on Windows, tar.gz on Linux).
This caused the Linux agent to fail extracting Windows .zip artifacts
because unzip is not installed on the build image.
Force tar.gz for all dump artifact uploads so both platforms can
extract them without additional tooling.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
eng/pipelines/runtime-diagnostics.yml | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/eng/pipelines/runtime-diagnostics.yml b/eng/pipelines/runtime-diagnostics.yml
index 388f9503413e75..26e2a3afa10b04 100644
--- a/eng/pipelines/runtime-diagnostics.yml
+++ b/eng/pipelines/runtime-diagnostics.yml
@@ -161,9 +161,9 @@ extends:
parameters:
rootFolder: $(Build.SourcesDirectory)/artifacts/dumps/cdac
includeRootFolder: false
- archiveType: $(archiveType)
- archiveExtension: $(archiveExtension)
- tarCompression: $(tarCompression)
+ archiveType: tar
+ archiveExtension: .tar.gz
+ tarCompression: gz
artifactName: CdacDumps_$(osGroup)_$(archType)
displayName: cDAC Dump Artifacts
@@ -190,7 +190,7 @@ extends:
- template: /eng/pipelines/common/download-artifact-step.yml
parameters:
artifactName: CdacDumps_windows_x64
- artifactFileName: CdacDumps_windows_x64.zip
+ artifactFileName: CdacDumps_windows_x64.tar.gz
unpackFolder: $(Build.SourcesDirectory)/artifacts/dumps/windows_x64
displayName: 'Windows x64 Dumps'
- template: /eng/pipelines/common/download-artifact-step.yml
From 3dc06d1e17c61a99ba3c089b0783131419c11074 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Thu, 19 Feb 2026 10:59:56 -0500
Subject: [PATCH 16/28] publish test results seperately
---
eng/pipelines/runtime-diagnostics.yml | 22 +++++++++++++++++-----
1 file changed, 17 insertions(+), 5 deletions(-)
diff --git a/eng/pipelines/runtime-diagnostics.yml b/eng/pipelines/runtime-diagnostics.yml
index 5c5e04f4723157..8b12b5c192df10 100644
--- a/eng/pipelines/runtime-diagnostics.yml
+++ b/eng/pipelines/runtime-diagnostics.yml
@@ -204,30 +204,42 @@ extends:
$(Build.SourcesDirectory)/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj
--no-build
--logger "trx;LogFileName=CdacDumpTests_windows_x64.trx"
- --results-directory $(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)
+ --results-directory $(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/windows_x64
displayName: 'Run cDAC Dump Tests (windows_x64 dumps)'
continueOnError: true
env:
CDAC_DUMP_ROOT: $(Build.SourcesDirectory)/artifacts/dumps/windows_x64
+ - task: PublishTestResults@2
+ displayName: 'Publish Results ($(osGroup)-$(archType) → windows_x64)'
+ inputs:
+ testResultsFormat: VSTest
+ testResultsFiles: '**/*.trx'
+ searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/windows_x64'
+ testRunTitle: 'cDAC Dump Tests $(osGroup)-$(archType) → windows_x64'
+ failTaskOnFailedTests: true
+ publishRunAttachments: true
+ buildConfiguration: $(_BuildConfig)
+ continueOnError: true
+ condition: always()
# Run tests against Linux dumps
- script: $(Build.SourcesDirectory)$(dir).dotnet$(dir)dotnet$(exeExt) test
$(Build.SourcesDirectory)/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj
--no-build
--logger "trx;LogFileName=CdacDumpTests_linux_x64.trx"
- --results-directory $(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)
+ --results-directory $(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/linux_x64
displayName: 'Run cDAC Dump Tests (linux_x64 dumps)'
continueOnError: true
env:
CDAC_DUMP_ROOT: $(Build.SourcesDirectory)/artifacts/dumps/linux_x64
- task: PublishTestResults@2
+ displayName: 'Publish Results ($(osGroup)-$(archType) → linux_x64)'
inputs:
testResultsFormat: VSTest
testResultsFiles: '**/*.trx'
- searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults'
- testRunTitle: 'cDAC Dump Tests $(osGroup)-$(archType)'
+ searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/linux_x64'
+ testRunTitle: 'cDAC Dump Tests $(osGroup)-$(archType) → linux_x64'
failTaskOnFailedTests: true
publishRunAttachments: true
- mergeTestResults: true
buildConfiguration: $(_BuildConfig)
continueOnError: true
condition: always()
From ab806182bb5b64fb0692ef75afff8d68ddbe4638 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Thu, 19 Feb 2026 11:21:00 -0500
Subject: [PATCH 17/28] add support for running from CI dumps
---
.../cdac/tests/DumpTests/RunDumpTests.ps1 | 108 +++++++++++++++++-
1 file changed, 107 insertions(+), 1 deletion(-)
diff --git a/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1 b/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1
index 3ec50c189a6495..fdffe61622d05e 100644
--- a/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1
+++ b/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1
@@ -8,6 +8,9 @@
2. Run them to produce crash dumps
3. Build and run the dump analysis tests
+ Alternatively, use -DumpArchive to import a tar.gz archive of dumps
+ downloaded from CI and run the tests against those dumps.
+
NOTE: This script is Windows-only. For cross-platform CI builds,
the MSBuild-based DumpTests.targets handles platform detection
automatically via $(HostOS), $(ExeSuffix), and $(PortableTargetRid).
@@ -36,6 +39,12 @@
Examples: "*StackWalk*", "*Thread*", "*GC_Heap*"
Default: "" (run all tests)
+.PARAMETER DumpArchive
+ Path to a tar.gz archive of dumps downloaded from CI.
+ When specified, the archive is extracted and tests are run against
+ the extracted dumps. Skips dump generation entirely.
+ The archive should contain: {version}/{debuggee}/{debuggee}.dmp
+
.EXAMPLE
.\RunDumpTests.ps1
@@ -50,6 +59,9 @@
.EXAMPLE
.\RunDumpTests.ps1 -Filter "*StackWalk*"
+
+.EXAMPLE
+ .\RunDumpTests.ps1 -DumpArchive C:\Downloads\CdacDumps_linux_x64.tar.gz
#>
[CmdletBinding()]
@@ -63,7 +75,9 @@ param(
[string]$TestHostConfiguration = "Release",
- [string]$Filter = ""
+ [string]$Filter = "",
+
+ [string]$DumpArchive = ""
)
Set-StrictMode -Version Latest
@@ -85,6 +99,98 @@ if (-not (Test-Path $dotnet)) {
$allDebugees = @("BasicThreads", "TypeHierarchy", "ExceptionState", "MultiModule", "GCRoots", "StackWalk")
$allVersions = @("local", "net10.0")
+# --- DumpArchive mode: extract and test CI dumps ---
+if ($DumpArchive) {
+ if (-not (Test-Path $DumpArchive)) {
+ Write-Error "Dump archive not found: $DumpArchive"
+ exit 1
+ }
+
+ $extractDir = Join-Path $repoRoot "artifacts\dumps\ci-imported"
+ if (Test-Path $extractDir) {
+ Write-Host "Cleaning previous import: $extractDir" -ForegroundColor Yellow
+ Remove-Item $extractDir -Recurse -Force
+ }
+ New-Item -ItemType Directory -Path $extractDir -Force | Out-Null
+
+ Write-Host ""
+ Write-Host "=== cDAC Dump Tests (CI Archive) ===" -ForegroundColor Cyan
+ Write-Host " Archive: $DumpArchive"
+ Write-Host " Extract: $extractDir"
+ if ($Filter) { Write-Host " Filter: $Filter" }
+ Write-Host ""
+
+ Write-Host "--- Extracting archive ---" -ForegroundColor Cyan
+ tar -xzf $DumpArchive -C $extractDir
+ if ($LASTEXITCODE -ne 0) { Write-Error "Failed to extract archive."; exit 1 }
+
+ # List extracted dumps
+ $dumpFiles = Get-ChildItem -Path $extractDir -Recurse -Filter "*.dmp"
+ if ($dumpFiles.Count -eq 0) {
+ Write-Error "No .dmp files found in archive."
+ exit 1
+ }
+ foreach ($f in $dumpFiles) {
+ $rel = $f.FullName.Substring($extractDir.Length + 1)
+ $size = [math]::Round($f.Length / 1MB, 1)
+ Write-Host " Found: $rel (${size} MB)" -ForegroundColor Green
+ }
+
+ # Detect which versions are present in the archive and skip the rest
+ $presentVersions = Get-ChildItem -Path $extractDir -Directory | Select-Object -ExpandProperty Name
+ $skipVersions = $allVersions | Where-Object { $_ -notin $presentVersions }
+ if ($skipVersions) {
+ $skipVersionsStr = $skipVersions -join ";"
+ Write-Host " Versions in archive: $($presentVersions -join ', ')" -ForegroundColor Green
+ Write-Host " Skipping versions: $($skipVersions -join ', ')" -ForegroundColor Yellow
+ }
+ else {
+ $skipVersionsStr = ""
+ Write-Host " Versions in archive: $($presentVersions -join ', ')" -ForegroundColor Green
+ }
+
+ Write-Host ""
+ Write-Host "--- Building test project ---" -ForegroundColor Cyan
+ $buildArgs = @($dumpTestsProj, "--nologo", "-v", "q")
+ if ($skipVersionsStr) {
+ $buildArgs += "/p:SkipDumpVersions=$skipVersionsStr"
+ }
+ & $dotnet build @buildArgs 2>&1 | ForEach-Object { Write-Host " $_" }
+ if ($LASTEXITCODE -ne 0) { Write-Error "Test project build failed."; exit 1 }
+
+ Write-Host ""
+ Write-Host "--- Running tests against CI dumps ---" -ForegroundColor Cyan
+
+ $filterExpr = ""
+ if ($Filter) {
+ $namePattern = $Filter.Replace("*", "")
+ $filterExpr = "FullyQualifiedName~$namePattern"
+ }
+
+ $env:CDAC_DUMP_ROOT = $extractDir
+ $saved = $ErrorActionPreference
+ $ErrorActionPreference = "Continue"
+ if ($filterExpr) {
+ & $dotnet test $dumpTestsProj --no-build --filter $filterExpr --logger "console;verbosity=detailed" 2>&1 | ForEach-Object { Write-Host " $_" }
+ }
+ else {
+ & $dotnet test $dumpTestsProj --no-build --logger "console;verbosity=detailed" 2>&1 | ForEach-Object { Write-Host " $_" }
+ }
+ $testExitCode = $LASTEXITCODE
+ $ErrorActionPreference = $saved
+ Remove-Item Env:\CDAC_DUMP_ROOT -ErrorAction SilentlyContinue
+
+ if ($testExitCode -ne 0) {
+ Write-Host ""
+ Write-Host "TESTS FAILED" -ForegroundColor Red
+ exit 1
+ }
+
+ Write-Host ""
+ Write-Host "ALL TESTS PASSED" -ForegroundColor Green
+ exit 0
+}
+
if ($Versions -eq "all") {
$selectedVersions = $allVersions
}
From 3f169426b794ca7e8421d89cd134ff280fc19921 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Thu, 19 Feb 2026 11:29:15 -0500
Subject: [PATCH 18/28] fix windows paths on unix machines
---
src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs b/src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs
index e29e787ff83801..d5fd6f45a5d221 100644
--- a/src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs
+++ b/src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs
@@ -69,7 +69,11 @@ public ulong FindContractDescriptorAddress()
if (fileName is null)
continue;
- string name = System.IO.Path.GetFileName(fileName);
+ // Path.GetFileName doesn't handle Windows paths on a Linux/macOS host,
+ // so split on both separators to extract the file name correctly when
+ // analyzing cross-platform dumps.
+ int lastSep = Math.Max(fileName.LastIndexOf('/'), fileName.LastIndexOf('\\'));
+ string name = lastSep >= 0 ? fileName.Substring(lastSep + 1) : fileName;
if (!IsRuntimeModule(name))
continue;
From 06cc2bd8555ac1093fbf51b6fab0e6023e55a3ce Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Thu, 19 Feb 2026 11:41:21 -0500
Subject: [PATCH 19/28] add more platforms
---
eng/pipelines/runtime-diagnostics.yml | 104 ++++++++++----------------
1 file changed, 41 insertions(+), 63 deletions(-)
diff --git a/eng/pipelines/runtime-diagnostics.yml b/eng/pipelines/runtime-diagnostics.yml
index fca5ceeed41863..3c6ac583de9a6d 100644
--- a/eng/pipelines/runtime-diagnostics.yml
+++ b/eng/pipelines/runtime-diagnostics.yml
@@ -5,6 +5,16 @@ parameters:
displayName: Diagnostics Branch
type: string
default: main
+- name: cdacDumpPlatforms
+ displayName: cDAC Dump Platforms
+ type: object
+ default:
+ - windows_x64
+ - windows_arm64
+ - linux_x64
+ - linux_arm64
+ - osx_x64
+ - osx_arm64
resources:
repositories:
@@ -145,9 +155,7 @@ extends:
parameters:
jobTemplate: /eng/pipelines/common/global-build-job.yml
buildConfig: release
- platforms:
- - windows_x64
- - linux_x64
+ platforms: ${{ parameters.cdacDumpPlatforms }}
jobParameters:
buildArgs: -s clr+libs+tools.cdac -c $(_BuildConfig) -rc $(_BuildConfig) -lc $(_BuildConfig)
nameSuffix: CdacDumpGeneration
@@ -178,68 +186,38 @@ extends:
parameters:
jobTemplate: /eng/pipelines/common/global-build-job.yml
buildConfig: release
- platforms:
- - windows_x64
- - linux_x64
+ platforms: ${{ parameters.cdacDumpPlatforms }}
jobParameters:
buildArgs: -s tools.cdacdumptests /p:SkipDumpGeneration=true /p:SkipDumpVersions=net10.0
nameSuffix: CdacDumpTests
timeoutInMinutes: 60
postBuildSteps:
- # Download dumps from all platforms
- - template: /eng/pipelines/common/download-artifact-step.yml
- parameters:
- artifactName: CdacDumps_windows_x64
- artifactFileName: CdacDumps_windows_x64.tar.gz
- unpackFolder: $(Build.SourcesDirectory)/artifacts/dumps/windows_x64
- displayName: 'Windows x64 Dumps'
- - template: /eng/pipelines/common/download-artifact-step.yml
- parameters:
- artifactName: CdacDumps_linux_x64
- artifactFileName: CdacDumps_linux_x64.tar.gz
- unpackFolder: $(Build.SourcesDirectory)/artifacts/dumps/linux_x64
- displayName: 'Linux x64 Dumps'
- # Run tests against Windows dumps
- - script: $(Build.SourcesDirectory)$(dir).dotnet$(dir)dotnet$(exeExt) test
- $(Build.SourcesDirectory)/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj
- --no-build
- --logger "trx;LogFileName=CdacDumpTests_windows_x64.trx"
- --results-directory $(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/windows_x64
- displayName: 'Run cDAC Dump Tests (windows_x64 dumps)'
- continueOnError: true
- env:
- CDAC_DUMP_ROOT: $(Build.SourcesDirectory)/artifacts/dumps/windows_x64
- - task: PublishTestResults@2
- displayName: 'Publish Results ($(osGroup)-$(archType) → windows_x64)'
- inputs:
- testResultsFormat: VSTest
- testResultsFiles: '**/*.trx'
- searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/windows_x64'
- testRunTitle: 'cDAC Dump Tests $(osGroup)-$(archType) → windows_x64'
- failTaskOnFailedTests: true
- publishRunAttachments: true
- buildConfiguration: $(_BuildConfig)
- continueOnError: true
- condition: always()
- # Run tests against Linux dumps
- - script: $(Build.SourcesDirectory)$(dir).dotnet$(dir)dotnet$(exeExt) test
- $(Build.SourcesDirectory)/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj
- --no-build
- --logger "trx;LogFileName=CdacDumpTests_linux_x64.trx"
- --results-directory $(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/linux_x64
- displayName: 'Run cDAC Dump Tests (linux_x64 dumps)'
- continueOnError: true
- env:
- CDAC_DUMP_ROOT: $(Build.SourcesDirectory)/artifacts/dumps/linux_x64
- - task: PublishTestResults@2
- displayName: 'Publish Results ($(osGroup)-$(archType) → linux_x64)'
- inputs:
- testResultsFormat: VSTest
- testResultsFiles: '**/*.trx'
- searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/linux_x64'
- testRunTitle: 'cDAC Dump Tests $(osGroup)-$(archType) → linux_x64'
- failTaskOnFailedTests: true
- publishRunAttachments: true
- buildConfiguration: $(_BuildConfig)
- continueOnError: true
- condition: always()
+ # Download and test against dumps from each platform
+ - ${{ each dumpPlatform in parameters.cdacDumpPlatforms }}:
+ - template: /eng/pipelines/common/download-artifact-step.yml
+ parameters:
+ artifactName: CdacDumps_${{ dumpPlatform }}
+ artifactFileName: CdacDumps_${{ dumpPlatform }}.tar.gz
+ unpackFolder: $(Build.SourcesDirectory)/artifacts/dumps/${{ dumpPlatform }}
+ displayName: '${{ dumpPlatform }} Dumps'
+ - script: $(Build.SourcesDirectory)$(dir).dotnet$(dir)dotnet$(exeExt) test
+ $(Build.SourcesDirectory)/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj
+ --no-build
+ --logger "trx;LogFileName=CdacDumpTests_${{ dumpPlatform }}.trx"
+ --results-directory $(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/${{ dumpPlatform }}
+ displayName: 'Run cDAC Dump Tests (${{ dumpPlatform }} dumps)'
+ continueOnError: true
+ env:
+ CDAC_DUMP_ROOT: $(Build.SourcesDirectory)/artifacts/dumps/${{ dumpPlatform }}
+ - task: PublishTestResults@2
+ displayName: 'Publish Results ($(osGroup)-$(archType) → ${{ dumpPlatform }})'
+ inputs:
+ testResultsFormat: VSTest
+ testResultsFiles: '**/*.trx'
+ searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/${{ dumpPlatform }}'
+ testRunTitle: 'cDAC Dump Tests $(osGroup)-$(archType) → ${{ dumpPlatform }}'
+ failTaskOnFailedTests: true
+ publishRunAttachments: true
+ buildConfiguration: $(_BuildConfig)
+ continueOnError: true
+ condition: always()
From ef9ff5facfd0e1ab41057427bbb42278efbcc491 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Thu, 19 Feb 2026 11:54:51 -0500
Subject: [PATCH 20/28] address comments
---
.../cdac/tests/DumpTests/ClrMdDumpHost.cs | 2 +-
.../cdac/tests/DumpTests/DumpTestBase.cs | 16 ++++---
.../cdac/tests/DumpTests/DumpTests.targets | 4 +-
.../cdac/tests/DumpTests/RunDumpTests.ps1 | 15 ++++++-
.../SkipOnRuntimeVersionAttribute.cs | 44 -------------------
.../tests/DumpTests/StackWalkDumpTests.cs | 2 +-
6 files changed, 27 insertions(+), 56 deletions(-)
delete mode 100644 src/native/managed/cdac/tests/DumpTests/SkipOnRuntimeVersionAttribute.cs
diff --git a/src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs b/src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs
index d5fd6f45a5d221..ead5a6ad29e4ac 100644
--- a/src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs
+++ b/src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs
@@ -98,7 +98,7 @@ private static bool IsRuntimeModule(string fileName)
public void Dispose()
{
- _dataTarget?.Dispose();
+ _dataTarget.Dispose();
GC.SuppressFinalize(this);
}
}
diff --git a/src/native/managed/cdac/tests/DumpTests/DumpTestBase.cs b/src/native/managed/cdac/tests/DumpTests/DumpTestBase.cs
index e0e75188a28e75..fc02de1e1f14ab 100644
--- a/src/native/managed/cdac/tests/DumpTests/DumpTestBase.cs
+++ b/src/native/managed/cdac/tests/DumpTests/DumpTestBase.cs
@@ -24,7 +24,7 @@ namespace Microsoft.Diagnostics.DataContractReader.DumpTests;
/// at the start of the test method
///
///
-public abstract class DumpTestBase : IAsyncLifetime, IDisposable
+public abstract class DumpTestBase : IAsyncLifetime
{
private ClrMdDumpHost? _host;
private ContractDescriptorTarget? _target;
@@ -80,13 +80,20 @@ public Task InitializeAsync()
}
catch
{
+ // Resolving the target OS is best-effort. The RuntimeInfo contract may be
+ // unavailable in older dumps or throw for unexpected reasons. Treat the OS
+ // as unknown and allow tests to continue — they can handle a missing TargetOS.
_targetOS = null;
}
return Task.CompletedTask;
}
- public Task DisposeAsync() => Task.CompletedTask;
+ public Task DisposeAsync()
+ {
+ _host?.Dispose();
+ return Task.CompletedTask;
+ }
///
/// Skips the current test if matches .
@@ -154,9 +161,4 @@ private static bool IsVersionSkipped(string version)
return null;
}
- public void Dispose()
- {
- _host?.Dispose();
- System.GC.SuppressFinalize(this);
- }
}
diff --git a/src/native/managed/cdac/tests/DumpTests/DumpTests.targets b/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
index 24118b04bcbcd6..7226183209b785 100644
--- a/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
+++ b/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
@@ -109,7 +109,7 @@
IgnoreExitCode="true"
EnvironmentVariables="DOTNET_DbgEnableMiniDump=1;DOTNET_DbgMiniDumpType=4;DOTNET_DbgMiniDumpName=$(_DumpFile)" />
-
+
@@ -130,7 +130,7 @@
IgnoreExitCode="true"
EnvironmentVariables="DOTNET_DbgEnableMiniDump=1;DOTNET_DbgMiniDumpType=4;DOTNET_DbgMiniDumpName=$(_DumpFile)" />
-
+
diff --git a/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1 b/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1
index fdffe61622d05e..9c5a2f5f1d3038 100644
--- a/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1
+++ b/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1
@@ -84,7 +84,20 @@ Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
# --- Resolve paths ---
-$repoRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent (Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $PSScriptRoot)))))
+function Find-RepoRoot([string]$startDir) {
+ $dir = $startDir
+ while ($dir) {
+ if (Test-Path (Join-Path $dir "global.json")) {
+ return $dir
+ }
+ $parent = Split-Path -Parent $dir
+ if ($parent -eq $dir) { break }
+ $dir = $parent
+ }
+ Write-Error "Could not find repository root (no global.json found above $startDir)"
+ exit 1
+}
+$repoRoot = Find-RepoRoot $PSScriptRoot
$dotnet = Join-Path $repoRoot ".dotnet\dotnet.exe"
$dumpTestsProj = Join-Path $PSScriptRoot "Microsoft.Diagnostics.DataContractReader.DumpTests.csproj"
$dumpOutputDir = Join-Path $repoRoot "artifacts\dumps\cdac"
diff --git a/src/native/managed/cdac/tests/DumpTests/SkipOnRuntimeVersionAttribute.cs b/src/native/managed/cdac/tests/DumpTests/SkipOnRuntimeVersionAttribute.cs
deleted file mode 100644
index f6fe84e314df81..00000000000000
--- a/src/native/managed/cdac/tests/DumpTests/SkipOnRuntimeVersionAttribute.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-
-namespace Microsoft.Diagnostics.DataContractReader.DumpTests;
-
-///
-/// Marker attribute for documenting which runtime versions a test should skip.
-/// Skip logic is evaluated by calling
-/// at the start of the test method.
-///
-[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
-public sealed class SkipOnRuntimeVersionAttribute : Attribute
-{
- public string Version { get; }
- public string Reason { get; }
-
- public SkipOnRuntimeVersionAttribute(string version, string reason)
- {
- Version = version;
- Reason = reason;
- }
-}
-
-///
-/// Marker attribute for documenting which target OS a test should skip.
-/// Skip logic is evaluated by calling
-/// at the start of the test method.
-///
-[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
-public sealed class SkipOnTargetOSAttribute : Attribute
-{
- public string OperatingSystem { get; }
- public string Reason { get; }
-
- /// The target OS name to skip: "Windows", "Unix", or "Browser".
- /// Explanation for why the test is skipped on this OS.
- public SkipOnTargetOSAttribute(string operatingSystem, string reason)
- {
- OperatingSystem = operatingSystem;
- Reason = reason;
- }
-}
diff --git a/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs
index 4a1e883513720a..bcf8ec6bbd946f 100644
--- a/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs
+++ b/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs
@@ -61,7 +61,7 @@ public void StackWalk_HasMultipleFrames()
// but the stack walk may include runtime helper frames and native transitions.
// We just assert there are multiple frames visible.
Assert.True(frameList.Count >= 1,
- $"Expected multiple stack frames from the crashing thread, got {frameList.Count}");
+ $"Expected at least 1 stack frame from the crashing thread, got {frameList.Count}");
}
[ConditionalFact]
From dd69e3c62bbf5a211f4d463cc6a9ea6214cbbe93 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Thu, 19 Feb 2026 11:55:08 -0500
Subject: [PATCH 21/28] only build on avaialble ADO queues
---
eng/pipelines/runtime-diagnostics.yml | 2 --
1 file changed, 2 deletions(-)
diff --git a/eng/pipelines/runtime-diagnostics.yml b/eng/pipelines/runtime-diagnostics.yml
index 3c6ac583de9a6d..6e1b42a62dc5b3 100644
--- a/eng/pipelines/runtime-diagnostics.yml
+++ b/eng/pipelines/runtime-diagnostics.yml
@@ -10,9 +10,7 @@ parameters:
type: object
default:
- windows_x64
- - windows_arm64
- linux_x64
- - linux_arm64
- osx_x64
- osx_arm64
From 6056ed86cbc432a4b782a42bfe99032410393d7f Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Thu, 19 Feb 2026 12:01:18 -0500
Subject: [PATCH 22/28] fix cDAC Thread object from requiring the
UEWatsonBucketTrackerBuckets which does not exist on all platforms
---
.../Data/Thread.cs | 5 ++++-
.../managed/cdac/tests/DumpTests/ExceptionDumpTests.cs | 4 ----
.../managed/cdac/tests/DumpTests/StackWalkDumpTests.cs | 10 ----------
.../managed/cdac/tests/DumpTests/ThreadDumpTests.cs | 4 ----
4 files changed, 4 insertions(+), 19 deletions(-)
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Thread.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Thread.cs
index 05fba0fc545ff8..ccdc5017b5d5b8 100644
--- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Thread.cs
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Thread.cs
@@ -33,7 +33,10 @@ public Thread(Target target, TargetPointer address)
// Address of the exception tracker
ExceptionTracker = address + (ulong)type.Fields[nameof(ExceptionTracker)].Offset;
- UEWatsonBucketTrackerBuckets = target.ReadPointer(address + (ulong)type.Fields[nameof(UEWatsonBucketTrackerBuckets)].Offset);
+ // UEWatsonBucketTrackerBuckets does not exist on certain platforms
+ UEWatsonBucketTrackerBuckets = type.Fields.TryGetValue(nameof(UEWatsonBucketTrackerBuckets), out Target.FieldInfo watsonFieldInfo)
+ ? target.ReadPointer(address + (ulong)watsonFieldInfo.Offset)
+ : TargetPointer.Null;
ThreadLocalDataPtr = target.ReadPointer(address + (ulong)type.Fields[nameof(ThreadLocalDataPtr)].Offset);
}
diff --git a/src/native/managed/cdac/tests/DumpTests/ExceptionDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/ExceptionDumpTests.cs
index 40265e7f2cfb0e..8110928266283c 100644
--- a/src/native/managed/cdac/tests/DumpTests/ExceptionDumpTests.cs
+++ b/src/native/managed/cdac/tests/DumpTests/ExceptionDumpTests.cs
@@ -34,8 +34,6 @@ public void Exception_ContractIsAvailable()
[ConditionalFact]
public void Exception_CrashingThreadHasLastThrownObject()
{
- SkipIfTargetOS("Unix", "Thread.UEWatsonBucketTrackerBuckets is only supported on Windows");
- SkipIfVersion("net10.0", "Thread.UEWatsonBucketTrackerBuckets field not present in .NET 10 contract descriptor");
IThread threadContract = Target.Contracts.Thread;
ThreadStoreData storeData = threadContract.GetThreadStoreData();
@@ -60,8 +58,6 @@ public void Exception_CrashingThreadHasLastThrownObject()
[ConditionalFact]
public void Exception_CanGetExceptionDataFromFirstNestedException()
{
- SkipIfTargetOS("Unix", "Thread.UEWatsonBucketTrackerBuckets is only supported on Windows");
- SkipIfVersion("net10.0", "Thread.UEWatsonBucketTrackerBuckets field not present in .NET 10 contract descriptor");
IThread threadContract = Target.Contracts.Thread;
IException exceptionContract = Target.Contracts.Exception;
ThreadStoreData storeData = threadContract.GetThreadStoreData();
diff --git a/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs
index bcf8ec6bbd946f..64b17efdeeebee 100644
--- a/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs
+++ b/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs
@@ -20,8 +20,6 @@ public abstract class StackWalkDumpTestsBase : DumpTestBase
[ConditionalFact]
public void StackWalk_ContractIsAvailable()
{
- SkipIfTargetOS("Unix", "Thread.UEWatsonBucketTrackerBuckets is only supported on Windows");
- SkipIfVersion("net10.0", "Thread.UEWatsonBucketTrackerBuckets field not present in .NET 10 contract descriptor");
IStackWalk stackWalk = Target.Contracts.StackWalk;
Assert.NotNull(stackWalk);
}
@@ -29,8 +27,6 @@ public void StackWalk_ContractIsAvailable()
[ConditionalFact]
public void StackWalk_CanWalkCrashingThread()
{
- SkipIfTargetOS("Unix", "Thread.UEWatsonBucketTrackerBuckets is only supported on Windows");
- SkipIfVersion("net10.0", "Thread.UEWatsonBucketTrackerBuckets field not present in .NET 10 contract descriptor");
IThread threadContract = Target.Contracts.Thread;
IStackWalk stackWalk = Target.Contracts.StackWalk;
@@ -46,8 +42,6 @@ public void StackWalk_CanWalkCrashingThread()
[ConditionalFact]
public void StackWalk_HasMultipleFrames()
{
- SkipIfTargetOS("Unix", "Thread.UEWatsonBucketTrackerBuckets is only supported on Windows");
- SkipIfVersion("net10.0", "Thread.UEWatsonBucketTrackerBuckets field not present in .NET 10 contract descriptor");
IThread threadContract = Target.Contracts.Thread;
IStackWalk stackWalk = Target.Contracts.StackWalk;
@@ -67,8 +61,6 @@ public void StackWalk_HasMultipleFrames()
[ConditionalFact]
public void StackWalk_ManagedFramesHaveValidMethodDescs()
{
- SkipIfTargetOS("Unix", "Thread.UEWatsonBucketTrackerBuckets is only supported on Windows");
- SkipIfVersion("net10.0", "Thread.UEWatsonBucketTrackerBuckets field not present in .NET 10 contract descriptor");
IThread threadContract = Target.Contracts.Thread;
IStackWalk stackWalk = Target.Contracts.StackWalk;
IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem;
@@ -95,8 +87,6 @@ public void StackWalk_ManagedFramesHaveValidMethodDescs()
[ConditionalFact]
public void StackWalk_FramesHaveRawContext()
{
- SkipIfTargetOS("Unix", "Thread.UEWatsonBucketTrackerBuckets is only supported on Windows");
- SkipIfVersion("net10.0", "Thread.UEWatsonBucketTrackerBuckets field not present in .NET 10 contract descriptor");
IThread threadContract = Target.Contracts.Thread;
IStackWalk stackWalk = Target.Contracts.StackWalk;
diff --git a/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs
index 3de122e9e11c27..d9266ccbd50345 100644
--- a/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs
+++ b/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs
@@ -33,8 +33,6 @@ public void ThreadStoreData_HasExpectedThreadCount()
[ConditionalFact]
public void EnumerateThreads_CanWalkThreadList()
{
- SkipIfTargetOS("Unix", "Thread.UEWatsonBucketTrackerBuckets is only supported on Windows");
- SkipIfVersion("net10.0", "Thread.UEWatsonBucketTrackerBuckets field not present in .NET 10 contract descriptor");
IThread threadContract = Target.Contracts.Thread;
Assert.NotNull(threadContract);
@@ -77,8 +75,6 @@ public void ThreadStoreData_HasGCThread()
[ConditionalFact]
public void Threads_HaveValidIds()
{
- SkipIfTargetOS("Unix", "Thread.UEWatsonBucketTrackerBuckets is only supported on Windows");
- SkipIfVersion("net10.0", "Thread.UEWatsonBucketTrackerBuckets field not present in .NET 10 contract descriptor");
IThread threadContract = Target.Contracts.Thread;
ThreadStoreData storeData = threadContract.GetThreadStoreData();
From 056af5a74772d97cedaf999875fab3937566b98b Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Thu, 19 Feb 2026 13:03:53 -0500
Subject: [PATCH 23/28] remove more skips
---
src/native/managed/cdac/tests/DumpTests/LoaderDumpTests.cs | 6 ------
.../cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs | 1 -
2 files changed, 7 deletions(-)
diff --git a/src/native/managed/cdac/tests/DumpTests/LoaderDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/LoaderDumpTests.cs
index ed5653e6ec02eb..37ca410fd93061 100644
--- a/src/native/managed/cdac/tests/DumpTests/LoaderDumpTests.cs
+++ b/src/native/managed/cdac/tests/DumpTests/LoaderDumpTests.cs
@@ -30,7 +30,6 @@ public void Loader_CanGetRootAssembly()
public void Loader_RootAssemblyHasModule()
{
SkipIfVersion("net10.0", "Assembly type does not include IsDynamic/IsLoaded fields in .NET 10");
- SkipIfVersion("local", "Assembly type does not include IsLoaded field in current contract descriptor");
ILoader loader = Target.Contracts.Loader;
TargetPointer rootAssembly = loader.GetRootAssembly();
@@ -43,7 +42,6 @@ public void Loader_RootAssemblyHasModule()
public void Loader_CanGetModulePath()
{
SkipIfVersion("net10.0", "Assembly type does not include IsDynamic/IsLoaded fields in .NET 10");
- SkipIfVersion("local", "Assembly type does not include IsLoaded field in current contract descriptor");
ILoader loader = Target.Contracts.Loader;
TargetPointer rootAssembly = loader.GetRootAssembly();
@@ -74,7 +72,6 @@ public void Loader_GlobalLoaderAllocatorIsValid()
public void Loader_RootModuleHasFileName()
{
SkipIfVersion("net10.0", "Assembly type does not include IsDynamic/IsLoaded fields in .NET 10");
- SkipIfVersion("local", "Assembly type does not include IsLoaded field in current contract descriptor");
ILoader loader = Target.Contracts.Loader;
TargetPointer rootAssembly = loader.GetRootAssembly();
ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(rootAssembly);
@@ -89,7 +86,6 @@ public void Loader_RootModuleHasFileName()
public void Loader_RootModuleIsNotDynamic()
{
SkipIfVersion("net10.0", "Assembly type does not include IsDynamic/IsLoaded fields in .NET 10");
- SkipIfVersion("local", "Assembly type does not include IsLoaded field in current contract descriptor");
ILoader loader = Target.Contracts.Loader;
TargetPointer rootAssembly = loader.GetRootAssembly();
ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(rootAssembly);
@@ -101,7 +97,6 @@ public void Loader_RootModuleIsNotDynamic()
public void Loader_RootModuleHasLoaderAllocator()
{
SkipIfVersion("net10.0", "Assembly type does not include IsDynamic/IsLoaded fields in .NET 10");
- SkipIfVersion("local", "Assembly type does not include IsLoaded field in current contract descriptor");
ILoader loader = Target.Contracts.Loader;
TargetPointer rootAssembly = loader.GetRootAssembly();
ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(rootAssembly);
@@ -114,7 +109,6 @@ public void Loader_RootModuleHasLoaderAllocator()
public void Loader_RootModuleHasILBase()
{
SkipIfVersion("net10.0", "Assembly type does not include IsDynamic/IsLoaded fields in .NET 10");
- SkipIfVersion("local", "Assembly type does not include IsLoaded field in current contract descriptor");
ILoader loader = Target.Contracts.Loader;
TargetPointer rootAssembly = loader.GetRootAssembly();
ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(rootAssembly);
diff --git a/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs
index 14acb4a04ff6e6..ee3153edd0432f 100644
--- a/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs
+++ b/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs
@@ -22,7 +22,6 @@ public abstract class RuntimeTypeSystemDumpTestsBase : DumpTestBase
public void RuntimeTypeSystem_CanGetMethodTableFromModule()
{
SkipIfVersion("net10.0", "Assembly type does not include IsDynamic/IsLoaded fields in .NET 10");
- SkipIfVersion("local", "Assembly type does not include IsLoaded field in current contract descriptor");
ILoader loader = Target.Contracts.Loader;
Assert.NotNull(loader);
IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem;
From 265d1125536ef7d796ca06e6033c577ef229a5bf Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Thu, 19 Feb 2026 13:15:18 -0500
Subject: [PATCH 24/28] add serverGC test
---
.../DumpTests/Debuggees/ServerGC/Program.cs | 42 ++++++
.../Debuggees/ServerGC/ServerGC.csproj | 5 +
.../cdac/tests/DumpTests/DumpTests.targets | 1 +
.../cdac/tests/DumpTests/ObjectDumpTests.cs | 35 ++---
.../cdac/tests/DumpTests/RunDumpTests.ps1 | 2 +-
.../cdac/tests/DumpTests/ServerGCDumpTests.cs | 124 ++++++++++++++++++
6 files changed, 183 insertions(+), 26 deletions(-)
create mode 100644 src/native/managed/cdac/tests/DumpTests/Debuggees/ServerGC/Program.cs
create mode 100644 src/native/managed/cdac/tests/DumpTests/Debuggees/ServerGC/ServerGC.csproj
create mode 100644 src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs
diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/ServerGC/Program.cs b/src/native/managed/cdac/tests/DumpTests/Debuggees/ServerGC/Program.cs
new file mode 100644
index 00000000000000..ea8cd960dc8d88
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/ServerGC/Program.cs
@@ -0,0 +1,42 @@
+// 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.Runtime;
+using System.Runtime.InteropServices;
+
+///
+/// Debuggee for cDAC dump tests — exercises the GC contract in server GC mode.
+/// Allocates objects across heaps, pins some, then crashes.
+///
+internal static class Program
+{
+ private static void Main()
+ {
+ // Verify server GC is enabled
+ if (!GCSettings.IsServerGC)
+ {
+ Console.Error.WriteLine("ERROR: Server GC is not enabled.");
+ Environment.Exit(1);
+ }
+
+ // Allocate objects to populate multiple heaps
+ object[] roots = new object[100];
+ for (int i = 0; i < roots.Length; i++)
+ {
+ roots[i] = new byte[1024 * (i + 1)];
+ }
+
+ // Create pinned handles
+ GCHandle[] pinnedHandles = new GCHandle[10];
+ for (int i = 0; i < pinnedHandles.Length; i++)
+ {
+ pinnedHandles[i] = GCHandle.Alloc(new byte[256], GCHandleType.Pinned);
+ }
+
+ GC.KeepAlive(roots);
+ GC.KeepAlive(pinnedHandles);
+
+ Environment.FailFast("cDAC dump test: ServerGC debuggee intentional crash");
+ }
+}
diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/ServerGC/ServerGC.csproj b/src/native/managed/cdac/tests/DumpTests/Debuggees/ServerGC/ServerGC.csproj
new file mode 100644
index 00000000000000..e634aa8ad21491
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/ServerGC/ServerGC.csproj
@@ -0,0 +1,5 @@
+
+
+ true
+
+
diff --git a/src/native/managed/cdac/tests/DumpTests/DumpTests.targets b/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
index 7226183209b785..97a3e72c4066a6 100644
--- a/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
+++ b/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
@@ -43,6 +43,7 @@
+
diff --git a/src/native/managed/cdac/tests/DumpTests/ObjectDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/ObjectDumpTests.cs
index 4902404a406bbd..07a17565a1ac8c 100644
--- a/src/native/managed/cdac/tests/DumpTests/ObjectDumpTests.cs
+++ b/src/native/managed/cdac/tests/DumpTests/ObjectDumpTests.cs
@@ -8,7 +8,7 @@
namespace Microsoft.Diagnostics.DataContractReader.DumpTests;
///
-/// Dump-based integration tests for the Object and GC contracts.
+/// Dump-based integration tests for the Object and GC contracts in workstation GC mode.
/// Uses the GCRoots debuggee dump, which pins objects and creates GC handles.
///
public abstract class ObjectDumpTestsBase : DumpTestBase
@@ -30,6 +30,15 @@ public void GC_ContractIsAvailable()
Assert.NotNull(gcContract);
}
+ [ConditionalFact]
+ public void GC_IsWorkstationGC()
+ {
+ SkipIfVersion("net10.0", "GC contract is not available in .NET 10 dumps");
+ IGC gcContract = Target.Contracts.GC;
+ uint heapCount = gcContract.GetGCHeapCount();
+ Assert.Equal(1u, heapCount);
+ }
+
[ConditionalFact]
public void GC_HeapCountIsNonZero()
{
@@ -45,7 +54,6 @@ public void GC_MaxGenerationIsReasonable()
SkipIfVersion("net10.0", "GC contract is not available in .NET 10 dumps");
IGC gcContract = Target.Contracts.GC;
uint maxGen = gcContract.GetMaxGeneration();
- // .NET typically has gen0, gen1, gen2 (maxGen = 2)
Assert.True(maxGen >= 1 && maxGen <= 4,
$"Expected max generation between 1 and 4, got {maxGen}");
}
@@ -69,34 +77,12 @@ public void GC_StructuresAreValid()
Assert.True(valid, "Expected GC structures to be valid in a dump taken outside of GC");
}
- [ConditionalFact]
- public void GC_CanEnumerateHeaps()
- {
- SkipIfVersion("net10.0", "GC contract is not available in .NET 10 dumps");
- IGC gcContract = Target.Contracts.GC;
- uint heapCount = gcContract.GetGCHeapCount();
-
- // For workstation GC (heapCount == 1), GetGCHeaps may return an empty list
- // since the heap is accessed via GetHeapData() instead. For server GC
- // (heapCount > 1), GetGCHeaps should return the per-heap pointers.
- if (heapCount > 1)
- {
- var heaps = gcContract.GetGCHeaps().ToList();
- Assert.True(heaps.Count > 0, "Expected at least one GC heap pointer for server GC");
- foreach (TargetPointer heap in heaps)
- {
- Assert.NotEqual(TargetPointer.Null, heap);
- }
- }
- }
-
[ConditionalFact]
public void GC_BoundsAreReasonable()
{
SkipIfVersion("net10.0", "GC contract is not available in .NET 10 dumps");
IGC gcContract = Target.Contracts.GC;
gcContract.GetGCBounds(out TargetPointer minAddr, out TargetPointer maxAddr);
- // Min address should be less than max address
Assert.True(minAddr < maxAddr,
$"Expected GC min address (0x{minAddr:X}) < max address (0x{maxAddr:X})");
}
@@ -109,7 +95,6 @@ public void Object_StringMethodTableHasCorrectComponentSize()
TargetPointer stringMT = Target.ReadPointer(stringMTGlobal);
TypeHandle handle = rts.GetTypeHandle(stringMT);
- // String component size should be sizeof(char) = 2
uint componentSize = rts.GetComponentSize(handle);
Assert.Equal(2u, componentSize);
}
diff --git a/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1 b/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1
index 9c5a2f5f1d3038..b9a6bac55b605b 100644
--- a/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1
+++ b/src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1
@@ -109,7 +109,7 @@ if (-not (Test-Path $dotnet)) {
}
# --- Debuggees and versions ---
-$allDebugees = @("BasicThreads", "TypeHierarchy", "ExceptionState", "MultiModule", "GCRoots", "StackWalk")
+$allDebugees = @("BasicThreads", "TypeHierarchy", "ExceptionState", "MultiModule", "GCRoots", "ServerGC", "StackWalk")
$allVersions = @("local", "net10.0")
# --- DumpArchive mode: extract and test CI dumps ---
diff --git a/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs
new file mode 100644
index 00000000000000..1c0a83ad62f5ba
--- /dev/null
+++ b/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs
@@ -0,0 +1,124 @@
+// 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;
+using System.Linq;
+using Microsoft.Diagnostics.DataContractReader.Contracts;
+using Xunit;
+
+namespace Microsoft.Diagnostics.DataContractReader.DumpTests;
+
+///
+/// Dump-based integration tests for the GC contract in server GC mode.
+/// Uses the ServerGC debuggee dump, which enables server GC and allocates
+/// objects across multiple heaps.
+///
+public abstract class ServerGCDumpTestsBase : DumpTestBase
+{
+ protected override string DebuggeeName => "ServerGC";
+
+ [ConditionalFact]
+ public void ServerGC_ContractIsAvailable()
+ {
+ SkipIfVersion("net10.0", "GC contract is not available in .NET 10 dumps");
+ IGC gcContract = Target.Contracts.GC;
+ Assert.NotNull(gcContract);
+ }
+
+ [ConditionalFact]
+ public void ServerGC_IsServerGC()
+ {
+ SkipIfVersion("net10.0", "GC contract is not available in .NET 10 dumps");
+ IGC gcContract = Target.Contracts.GC;
+ // Server GC rejects the parameterless GetHeapData() — verify it uses
+ // the per-heap overload instead, confirming this is a server GC dump.
+ Assert.Throws(() => gcContract.GetHeapData());
+ }
+
+ [ConditionalFact]
+ public void ServerGC_MaxGenerationIsReasonable()
+ {
+ SkipIfVersion("net10.0", "GC contract is not available in .NET 10 dumps");
+ IGC gcContract = Target.Contracts.GC;
+ uint maxGen = gcContract.GetMaxGeneration();
+ Assert.True(maxGen >= 1 && maxGen <= 4,
+ $"Expected max generation between 1 and 4, got {maxGen}");
+ }
+
+ [ConditionalFact]
+ public void ServerGC_StructuresAreValid()
+ {
+ SkipIfVersion("net10.0", "GC contract is not available in .NET 10 dumps");
+ IGC gcContract = Target.Contracts.GC;
+ bool valid = gcContract.GetGCStructuresValid();
+ Assert.True(valid, "Expected GC structures to be valid in a dump taken outside of GC");
+ }
+
+ [ConditionalFact]
+ public void ServerGC_CanEnumerateHeaps()
+ {
+ SkipIfVersion("net10.0", "GC contract is not available in .NET 10 dumps");
+ IGC gcContract = Target.Contracts.GC;
+ uint heapCount = gcContract.GetGCHeapCount();
+
+ List heaps = gcContract.GetGCHeaps().ToList();
+ Assert.Equal((int)heapCount, heaps.Count);
+ foreach (TargetPointer heap in heaps)
+ {
+ Assert.NotEqual(TargetPointer.Null, heap);
+ }
+ }
+
+ [ConditionalFact]
+ public void ServerGC_CanGetHeapData()
+ {
+ SkipIfVersion("net10.0", "GC contract is not available in .NET 10 dumps");
+ IGC gcContract = Target.Contracts.GC;
+ List heaps = gcContract.GetGCHeaps().ToList();
+ Assert.True(heaps.Count > 0, "Expected at least one GC heap");
+
+ foreach (TargetPointer heap in heaps)
+ {
+ GCHeapData heapData = gcContract.GetHeapData(heap);
+ Assert.NotNull(heapData.GenerationTable);
+ Assert.True(heapData.GenerationTable.Count > 0, "Expected at least one generation");
+ }
+ }
+
+ [ConditionalFact]
+ public void ServerGC_BoundsAreReasonable()
+ {
+ SkipIfVersion("net10.0", "GC contract is not available in .NET 10 dumps");
+ IGC gcContract = Target.Contracts.GC;
+ gcContract.GetGCBounds(out TargetPointer minAddr, out TargetPointer maxAddr);
+ Assert.True(minAddr < maxAddr,
+ $"Expected GC min address (0x{minAddr:X}) < max address (0x{maxAddr:X})");
+ }
+
+ [ConditionalFact]
+ public void ServerGC_EachHeapHasGenerationData()
+ {
+ SkipIfVersion("net10.0", "GC contract is not available in .NET 10 dumps");
+ IGC gcContract = Target.Contracts.GC;
+ uint maxGen = gcContract.GetMaxGeneration();
+ List heaps = gcContract.GetGCHeaps().ToList();
+
+ foreach (TargetPointer heap in heaps)
+ {
+ GCHeapData heapData = gcContract.GetHeapData(heap);
+ Assert.NotNull(heapData.GenerationTable);
+ Assert.True(heapData.GenerationTable.Count > 0,
+ $"Expected generation table for heap 0x{heap:X} to be non-empty");
+ }
+ }
+}
+
+public class ServerGCDumpTests_Local : ServerGCDumpTestsBase
+{
+ protected override string RuntimeVersion => "local";
+}
+
+public class ServerGCDumpTests_Net10 : ServerGCDumpTestsBase
+{
+ protected override string RuntimeVersion => "net10.0";
+}
From d1f203cce28eb96de98f20a99462ff9c9526acf7 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Thu, 19 Feb 2026 13:15:53 -0500
Subject: [PATCH 25/28] try to fix arm64 build
---
eng/pipelines/runtime-diagnostics.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/eng/pipelines/runtime-diagnostics.yml b/eng/pipelines/runtime-diagnostics.yml
index 6e1b42a62dc5b3..5e66f36c93301d 100644
--- a/eng/pipelines/runtime-diagnostics.yml
+++ b/eng/pipelines/runtime-diagnostics.yml
@@ -162,6 +162,8 @@ extends:
- script: $(Build.SourcesDirectory)$(dir).dotnet$(dir)dotnet$(exeExt) build
$(Build.SourcesDirectory)/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj
/p:CIDumpVersionsOnly=true
+ /p:TargetArchitecture=$(archType)
+ -bl:$(Build.SourcesDirectory)/artifacts/log/DumpGeneration.binlog
displayName: 'Build Dump Tests and Generate Dumps'
- template: /eng/pipelines/common/upload-artifact-step.yml
parameters:
From 642be8b119221208c8d532275d59345dce099d77 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Thu, 19 Feb 2026 14:53:46 -0500
Subject: [PATCH 26/28] try using heap dumps
---
src/native/managed/cdac/tests/DumpTests/DumpTests.targets | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/native/managed/cdac/tests/DumpTests/DumpTests.targets b/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
index 97a3e72c4066a6..a3ec5b101d8c8c 100644
--- a/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
+++ b/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
@@ -108,7 +108,7 @@
+ EnvironmentVariables="DOTNET_DbgEnableMiniDump=1;DOTNET_DbgMiniDumpType=2;DOTNET_DbgMiniDumpName=$(_DumpFile)" />
@@ -129,7 +129,7 @@
+ EnvironmentVariables="DOTNET_DbgEnableMiniDump=1;DOTNET_DbgMiniDumpType=2;DOTNET_DbgMiniDumpName=$(_DumpFile)" />
From 2bb3bd146553b360ab27dd5465e316c8145d2870 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Thu, 19 Feb 2026 14:54:05 -0500
Subject: [PATCH 27/28] don't use osx_arm64
---
eng/pipelines/runtime-diagnostics.yml | 1 -
1 file changed, 1 deletion(-)
diff --git a/eng/pipelines/runtime-diagnostics.yml b/eng/pipelines/runtime-diagnostics.yml
index 5e66f36c93301d..a1e704baf30402 100644
--- a/eng/pipelines/runtime-diagnostics.yml
+++ b/eng/pipelines/runtime-diagnostics.yml
@@ -12,7 +12,6 @@ parameters:
- windows_x64
- linux_x64
- osx_x64
- - osx_arm64
resources:
repositories:
From eb5fb49a755c67944ccf473996f798c491e4b1a0 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Thu, 19 Feb 2026 17:27:22 -0500
Subject: [PATCH 28/28] change back to full dumps
---
src/native/managed/cdac/tests/DumpTests/DumpTests.targets | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/native/managed/cdac/tests/DumpTests/DumpTests.targets b/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
index a3ec5b101d8c8c..97a3e72c4066a6 100644
--- a/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
+++ b/src/native/managed/cdac/tests/DumpTests/DumpTests.targets
@@ -108,7 +108,7 @@
+ EnvironmentVariables="DOTNET_DbgEnableMiniDump=1;DOTNET_DbgMiniDumpType=4;DOTNET_DbgMiniDumpName=$(_DumpFile)" />
@@ -129,7 +129,7 @@
+ EnvironmentVariables="DOTNET_DbgEnableMiniDump=1;DOTNET_DbgMiniDumpType=4;DOTNET_DbgMiniDumpName=$(_DumpFile)" />