Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
9e06fe4
add cdac dump-test infrastructure
max-charlamb Feb 18, 2026
20a18ae
centralize debuggee props
max-charlamb Feb 18, 2026
03d470d
add CI pipeline
max-charlamb Feb 18, 2026
d06c90f
address comments
max-charlamb Feb 18, 2026
7bf223f
move pipeline inside of runtime-diagnostics
max-charlamb Feb 18, 2026
8295470
comments
max-charlamb Feb 18, 2026
eb8ad7d
update yaml for cross-platform dump testing
max-charlamb Feb 18, 2026
f19b808
add more tests
max-charlamb Feb 18, 2026
5c06718
refactor skip logic
max-charlamb Feb 18, 2026
0727d57
refactor more
max-charlamb Feb 18, 2026
da8011b
update
max-charlamb Feb 18, 2026
7686c17
refactor
max-charlamb Feb 18, 2026
b4ad6ee
run stages in parallel
max-charlamb Feb 18, 2026
d02012e
Fix cross-platform cDAC dump test artifact download
steveisok Feb 19, 2026
b5ba445
Standardize cDAC dump artifacts on tar.gz format
steveisok Feb 19, 2026
3dc06d1
publish test results seperately
max-charlamb Feb 19, 2026
c8519a4
Merge branch 'cdac-dumptests' of https://github.com/max-charlamb/runt…
max-charlamb Feb 19, 2026
ab80618
add support for running from CI dumps
max-charlamb Feb 19, 2026
3f16942
fix windows paths on unix machines
max-charlamb Feb 19, 2026
06cc2bd
add more platforms
max-charlamb Feb 19, 2026
ef9ff5f
address comments
max-charlamb Feb 19, 2026
dd69e3c
only build on avaialble ADO queues
max-charlamb Feb 19, 2026
6056ed8
fix cDAC Thread object from requiring the UEWatsonBucketTrackerBucket…
max-charlamb Feb 19, 2026
056af5a
remove more skips
max-charlamb Feb 19, 2026
265d112
add serverGC test
max-charlamb Feb 19, 2026
d1f203c
try to fix arm64 build
max-charlamb Feb 19, 2026
642be8b
try using heap dumps
max-charlamb Feb 19, 2026
2bb3bd1
don't use osx_arm64
max-charlamb Feb 19, 2026
eb5fb49
change back to full dumps
max-charlamb Feb 19, 2026
1408dc5
fix windows dump generation
max-charlamb Feb 20, 2026
c0274ea
update dump collection to allow heap or full
max-charlamb Feb 20, 2026
bc60842
temporarily disable osx run for time
max-charlamb Feb 20, 2026
9929328
update dump collection
max-charlamb Feb 20, 2026
6879035
update to allow net10.0 testing
max-charlamb Feb 20, 2026
4151ab4
fix comments
max-charlamb Feb 20, 2026
23b59ee
add docs
max-charlamb Feb 20, 2026
774a1eb
fix stackwalk on .net10.0
max-charlamb Feb 20, 2026
8cbce1a
fail if any job fails
max-charlamb Feb 20, 2026
587bdfe
improve string handling
max-charlamb Feb 20, 2026
46a93dd
handle DisableAuxProviderSignatureCheck more safely
max-charlamb Feb 20, 2026
612897c
remove useless test
max-charlamb Feb 20, 2026
10a0749
update GC tests
max-charlamb Feb 20, 2026
1cfc60b
another round of test cleanup
max-charlamb Feb 20, 2026
c2cfa22
update docs
max-charlamb Feb 20, 2026
d9837c3
update script to make it more accurate
max-charlamb Feb 20, 2026
35ddc37
WIP convert to Theories
max-charlamb Feb 23, 2026
a195e29
simplify
max-charlamb Feb 23, 2026
7401e80
update
max-charlamb Feb 23, 2026
de143fd
remove extra file
max-charlamb Feb 23, 2026
8cafdb9
fix
max-charlamb Feb 23, 2026
d64f9e2
comments
max-charlamb Feb 23, 2026
060bc13
update readme
max-charlamb Feb 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion eng/Subsets.props
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@
<AllSubsetsExpansion>$(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>
<AllSubsetsExpansion Condition="$([MSBuild]::IsOsPlatform(Windows))">$(AllSubsetsExpansion)+linuxdac+alpinedac</AllSubsetsExpansion>
<AllSubsetsExpansion>$(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>
<AllSubsetsExpansion>$(AllSubsetsExpansion)+tools.illink+tools.cdac+tools.illinktests+tools.cdactests</AllSubsetsExpansion>
<AllSubsetsExpansion>$(AllSubsetsExpansion)+tools.illink+tools.cdac+tools.illinktests+tools.cdactests+tools.cdacdumptests</AllSubsetsExpansion>
<AllSubsetsExpansion>$(AllSubsetsExpansion)+host.native+host.pkg+host.tools+host.pretest+host.tests</AllSubsetsExpansion>
<AllSubsetsExpansion>$(AllSubsetsExpansion)+libs.native+libs.sfx+libs.oob+libs.pretest+libs.tests</AllSubsetsExpansion>
<AllSubsetsExpansion>$(AllSubsetsExpansion)+packs.product+packs.installers+packs.tests</AllSubsetsExpansion>
Expand Down Expand Up @@ -253,6 +253,7 @@
<SubsetName Include="Tools.Cdac" Description="Diagnostic data contract reader and related projects." />
<SubsetName Include="Tools.ILLinkTests" OnDemand="true" Description="Unit tests for the tools.illink subset." />
<SubsetName Include="Tools.CdacTests" OnDemand="true" Description="Unit tests for the diagnostic data contract reader." />
<SubsetName Include="Tools.CdacDumpTests" OnDemand="true" Description="Dump-based integration tests for the diagnostic data contract reader." />
<SubsetName Include="Tools.ILAsm" OnDemand="true" Description="Build only the managed ilasm tool." />

<!-- Host -->
Expand Down Expand Up @@ -523,6 +524,10 @@
<ProjectToBuild Include="$(SharedNativeRoot)managed\cdac\tests\Microsoft.Diagnostics.DataContractReader.Tests.csproj" Test="true" Category="tools"/>
</ItemGroup>

<ItemGroup Condition="$(_subset.Contains('+tools.cdacdumptests+'))">
<ProjectToBuild Include="$(SharedNativeRoot)managed\cdac\tests\DumpTests\Microsoft.Diagnostics.DataContractReader.DumpTests.csproj" Test="true" Category="tools"/>
</ItemGroup>

<ItemGroup Condition="$(_subset.Contains('+tools.illink+'))">
<ProjectToBuild Include="$(ToolsProjectRoot)illink\src\linker\Mono.Linker.csproj" Category="tools" />
<ProjectToBuild Include="$(ToolsProjectRoot)illink\src\ILLink.Tasks\ILLink.Tasks.csproj" Category="tools" />
Expand Down
1 change: 1 addition & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@
<NewtonsoftJsonVersion>13.0.3</NewtonsoftJsonVersion>
<NewtonsoftJsonBsonVersion>1.0.2</NewtonsoftJsonBsonVersion>
<MoqVersion>4.18.4</MoqVersion>
<MicrosoftDiagnosticsRuntimeVersion>3.1.512801</MicrosoftDiagnosticsRuntimeVersion>
<AwesomeAssertionsVersion>8.0.2</AwesomeAssertionsVersion>
<FsCheckVersion>2.14.3</FsCheckVersion>
<CommandLineParserVersion>2.9.1</CommandLineParserVersion>
Expand Down
92 changes: 92 additions & 0 deletions eng/pipelines/runtime-diagnostics.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ parameters:
displayName: Diagnostics Branch
type: string
default: main
- name: cdacDumpPlatforms
displayName: cDAC Dump Platforms
type: object
default:
- windows_x64
- linux_x64
# - osx_x64 # Temporarily due to CI capacity constraints. Will re-enable once osx queues are more available.

resources:
repositories:
Expand Down Expand Up @@ -134,3 +141,88 @@ extends:
buildConfiguration: $(_BuildConfig)
continueOnError: true
condition: always()

#
# cDAC Dump Creation — Build runtime, create crash dumps, publish dump artifacts
#
- stage: DumpCreation
dependsOn: []
jobs:
- template: /eng/pipelines/common/platform-matrix.yml
parameters:
jobTemplate: /eng/pipelines/common/global-build-job.yml
buildConfig: release
platforms: ${{ parameters.cdacDumpPlatforms }}
jobParameters:
buildArgs: -s clr+libs+tools.cdac -c $(_BuildConfig) -rc $(_BuildConfig) -lc $(_BuildConfig)
nameSuffix: CdacDumpGeneration
timeoutInMinutes: 120
postBuildSteps:
- script: $(Build.SourcesDirectory)$(dir).dotnet$(dir)dotnet$(exeExt) msbuild
$(Build.SourcesDirectory)/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj
/t:GenerateAllDumps
/p:CIDumpVersionsOnly=true
/p:SetDisableAuxProviderSignatureCheck=true
/p:TargetArchitecture=$(archType)
-bl:$(Build.SourcesDirectory)/artifacts/log/DumpGeneration.binlog
displayName: 'Generate cDAC Dumps'
- template: /eng/pipelines/common/upload-artifact-step.yml
parameters:
rootFolder: $(Build.SourcesDirectory)/artifacts/dumps/cdac
includeRootFolder: false
archiveType: tar
archiveExtension: .tar.gz
tarCompression: gz
artifactName: CdacDumps_$(osGroup)_$(archType)
displayName: cDAC Dump Artifacts

#
# 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
buildConfig: release
platforms: ${{ parameters.cdacDumpPlatforms }}
jobParameters:
buildArgs: -s tools.cdacdumptests /p:SkipDumpVersions=net10.0
nameSuffix: CdacDumpTests
timeoutInMinutes: 60
postBuildSteps:
# 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()
# Fail the job if any test or publish step above reported issues.
- script: echo "One or more dump test steps failed." && exit 1
displayName: 'Fail if tests failed'
condition: eq(variables['Agent.JobStatus'], 'SucceededWithIssues')
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
103 changes: 103 additions & 0 deletions src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// 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 Microsoft.Diagnostics.Runtime;

namespace Microsoft.Diagnostics.DataContractReader.DumpTests;

/// <summary>
/// Wraps a ClrMD DataTarget to provide the memory read callback and symbol lookup
/// needed to create a <see cref="ContractDescriptorTarget"/> from a crash dump.
/// </summary>
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;
}

/// <summary>
/// Open a crash dump and prepare it for cDAC analysis.
/// </summary>
public static ClrMdDumpHost Open(string dumpPath)
{
DataTarget dataTarget = DataTarget.LoadDump(dumpPath);
return new ClrMdDumpHost(dumpPath, dataTarget);
}

/// <summary>
/// Read memory from the dump at the specified address.
/// Returns 0 on success, non-zero on failure.
/// </summary>
public int ReadFromTarget(ulong address, Span<byte> buffer)
{
int bytesRead = _dataTarget.DataReader.Read(address, buffer);
return bytesRead == buffer.Length ? 0 : -1;
}

/// <summary>
/// Get a thread's register context from the dump.
/// Returns 0 on success, non-zero on failure.
/// </summary>
public int GetThreadContext(uint threadId, uint contextFlags, Span<byte> buffer)
{
return _dataTarget.DataReader.GetThreadContext(threadId, contextFlags, buffer) ? 0 : -1;
}

/// <summary>
/// Locate the DotNetRuntimeContractDescriptor symbol address in the dump.
/// Uses ClrMD's built-in export resolution which handles PE, ELF, and Mach-O formats.
/// </summary>
public ulong FindContractDescriptorAddress()
{
foreach (ModuleInfo module in _dataTarget.DataReader.EnumerateModules())
{
string? fileName = module.FileName;
if (fileName is null)
continue;

// 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[(lastSep + 1)..] : fileName;
if (!IsRuntimeModule(name))
continue;

ulong address = module.GetExportSymbolAddress("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;
}

public void Dispose()
{
_dataTarget.Dispose();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<Project Sdk="Microsoft.NET.Sdk">
</Project>
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// 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.
/// </summary>
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");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project>
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)..\'))" />

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>$(NetCoreAppToolCurrent);net10.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
<OutputPath>$(ArtifactsBinDir)DumpTests\$(MSBuildProjectName)\$(Configuration)\</OutputPath>
<AppendTargetFrameworkToOutputPath>true</AppendTargetFrameworkToOutputPath>
<!-- Debuggees intentionally use unsealed types for type hierarchy testing -->
<NoWarn>$(NoWarn);CA1852</NoWarn>
<!-- Default dump type for dump generation. Override per-debuggee csproj as needed.
Supported values: "Heap", "Full", or "Heap;Full" for both. -->
<DumpTypes Condition="'$(DumpTypes)' == ''">Heap</DumpTypes>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project>
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.targets', '$(MSBuildThisFileDirectory)..\'))" />

<!-- Target invoked by GenerateAllDumps (from DumpTests.targets) on each debuggee csproj
to extract the DumpTypes property. Returns the project name with DumpTypes metadata. -->
<Target Name="GetDumpTypes" Returns="@(_DumpTypeOutput)">
<ItemGroup>
<_DumpTypeOutput Include="$(MSBuildProjectName)" DumpTypes="$(DumpTypes)" />
</ItemGroup>
</Target>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<Project Sdk="Microsoft.NET.Sdk">
</Project>
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Debuggee for cDAC dump tests — exercises the Object and GC contracts.
/// Pins objects, creates GC handles, then crashes.
/// </summary>
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");
}
}
Loading
Loading