Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 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
616900e
WIP helix changes
max-charlamb Feb 19, 2026
2c2158b
fix script
max-charlamb Feb 19, 2026
d361c2e
try new update
max-charlamb Feb 19, 2026
4ae58f7
fix pipeline
max-charlamb Feb 19, 2026
3e3de9c
Fix cDAC dump Helix error: pass _Creator for anonymous queue access
max-charlamb Feb 19, 2026
16babf9
fix helix
max-charlamb Feb 20, 2026
10fb0da
move pipeline to runtime
max-charlamb Feb 20, 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.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1855,3 +1855,95 @@ extends:
eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_libraries.containsChange'], true),
eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_mono_excluding_wasm.containsChange'], true),
eq(variables['isRollingBuild'], true))

#
# cDAC Dump Tests (x64) — Reuse CoreCLR_Libraries build, send dump generation + testing to Helix
#
- template: /eng/pipelines/common/platform-matrix.yml
parameters:
jobTemplate: /eng/pipelines/common/global-build-job.yml
buildConfig: ${{ variables.debugOnPrReleaseOnRolling }}
platforms:
- windows_x64
- linux_x64
shouldContinueOnError: true
jobParameters:
nameSuffix: CdacDumpTest
buildArgs: -s tools.cdac+tools.cdacdumptests -c $(_BuildConfig) -rc Release -lc $(_BuildConfig) /p:SkipDumpGeneration=true
timeoutInMinutes: 120
dependsOnGlobalBuilds:
- nameSuffix: CoreCLR_Libraries
buildConfig: ${{ variables.debugOnPrReleaseOnRolling }}
preBuildSteps:
- template: /eng/pipelines/common/download-artifact-step.yml
parameters:
artifactName: CoreCLR_Libraries_BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig)
artifactFileName: CoreCLR_Libraries_BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig)$(archiveExtension)
unpackFolder: $(Build.SourcesDirectory)/artifacts/bin
displayName: 'CoreCLR Libraries Build Artifacts'
postBuildSteps:
- script: $(Build.SourcesDirectory)$(dir).dotnet$(dir)dotnet$(exeExt) build
$(Build.SourcesDirectory)/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj
/p:SkipDumpGeneration=true
/p:PrepareHelixPayload=true
/p:HelixPayloadDir=$(Build.SourcesDirectory)/artifacts/helixPayload/cdac
-bl:$(Build.SourcesDirectory)/artifacts/log/DumpTestPayload.binlog
displayName: 'Prepare Helix Payload'
- powershell: |
$testhostDir = Get-ChildItem -Directory -Path "$(Build.SourcesDirectory)/artifacts/bin/testhost/net*" | Select-Object -First 1 -ExpandProperty FullName
Write-Host "TestHost directory: $testhostDir"
Write-Host "##vso[task.setvariable variable=TestHostPayloadDir]$testhostDir"
displayName: 'Find TestHost Directory'
- template: /eng/pipelines/common/templates/runtimes/send-to-helix-inner-step.yml
parameters:
displayName: 'Send cDAC Dump Tests to Helix'
sendParams: $(Build.SourcesDirectory)/src/native/managed/cdac/tests/DumpTests/cdac-dump-helix.proj /restore /t:Test /p:TargetOS=$(osGroup) /p:TargetArchitecture=$(archType) /p:TestHostPayload=$(TestHostPayloadDir) /p:DumpTestsPayload=$(Build.SourcesDirectory)/artifacts/helixPayload/cdac /bl:$(Build.SourcesDirectory)/artifacts/log/SendToHelix.binlog
environment:
_Creator: dotnet-bot
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
condition: >-
or(
eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_non_mono_and_wasm.containsChange'], true),
eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_tools_cdac.containsChange'], true),
eq(variables['isRollingBuild'], true))

#
# cDAC Dump Tests (arm64) — Full build (no CoreCLR_Libraries artifact available for arm64)
#
- template: /eng/pipelines/common/platform-matrix.yml
parameters:
jobTemplate: /eng/pipelines/common/global-build-job.yml
buildConfig: ${{ variables.debugOnPrReleaseOnRolling }}
platforms:
- windows_arm64
- linux_arm64
shouldContinueOnError: true
jobParameters:
nameSuffix: CdacDumpTest
buildArgs: -s clr+libs+tools.cdac+tools.cdacdumptests -c $(_BuildConfig) -rc $(_BuildConfig) -lc $(_BuildConfig) /p:SkipDumpGeneration=true
timeoutInMinutes: 180
postBuildSteps:
- script: $(Build.SourcesDirectory)$(dir).dotnet$(dir)dotnet$(exeExt) build
$(Build.SourcesDirectory)/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj
/p:SkipDumpGeneration=true
/p:PrepareHelixPayload=true
/p:HelixPayloadDir=$(Build.SourcesDirectory)/artifacts/helixPayload/cdac
-bl:$(Build.SourcesDirectory)/artifacts/log/DumpTestPayload.binlog
displayName: 'Prepare Helix Payload'
- powershell: |
$testhostDir = Get-ChildItem -Directory -Path "$(Build.SourcesDirectory)/artifacts/bin/testhost/net*" | Select-Object -First 1 -ExpandProperty FullName
Write-Host "TestHost directory: $testhostDir"
Write-Host "##vso[task.setvariable variable=TestHostPayloadDir]$testhostDir"
displayName: 'Find TestHost Directory'
- template: /eng/pipelines/common/templates/runtimes/send-to-helix-inner-step.yml
parameters:
displayName: 'Send cDAC Dump Tests to Helix'
sendParams: $(Build.SourcesDirectory)/src/native/managed/cdac/tests/DumpTests/cdac-dump-helix.proj /restore /t:Test /p:TargetOS=$(osGroup) /p:TargetArchitecture=$(archType) /p:TestHostPayload=$(TestHostPayloadDir) /p:DumpTestsPayload=$(Build.SourcesDirectory)/artifacts/helixPayload/cdac /bl:$(Build.SourcesDirectory)/artifacts/log/SendToHelix.binlog
environment:
_Creator: dotnet-bot
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
condition: >-
or(
eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_non_mono_and_wasm.containsChange'], true),
eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_tools_cdac.containsChange'], true),
eq(variables['isRollingBuild'], true))
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
104 changes: 104 additions & 0 deletions src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// 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.Substring(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();
GC.SuppressFinalize(this);
}
}
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,14 @@
<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>
</PropertyGroup>
</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,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;

/// <summary>
/// Debuggee for cDAC dump tests — exercises the Exception contract.
/// Creates a nested exception chain then crashes with FailFast.
/// </summary>
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");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<Project Sdk="Microsoft.NET.Sdk">
</Project>
Loading
Loading