diff --git a/eng/pipelines/cdac/prepare-cdac-stress-helix-steps.yml b/eng/pipelines/cdac/prepare-cdac-stress-helix-steps.yml
new file mode 100644
index 00000000000000..436153bc7cc8ef
--- /dev/null
+++ b/eng/pipelines/cdac/prepare-cdac-stress-helix-steps.yml
@@ -0,0 +1,45 @@
+# prepare-cdac-stress-helix-steps.yml - Steps for preparing cDAC stress test Helix payloads.
+#
+# Used by CdacDumpTests stage in runtime-diagnostics.yml.
+# Handles: building stress test debuggees, preparing Helix payload, finding testhost.
+
+steps:
+- script: $(Build.SourcesDirectory)$(dir).dotnet$(dir)dotnet$(exeExt) msbuild
+ $(Build.SourcesDirectory)/src/native/managed/cdac/tests/StressTests/Microsoft.Diagnostics.DataContractReader.StressTests.csproj
+ /t:BuildDebuggeesOnly
+ /p:Configuration=$(_BuildConfig)
+ /p:TargetArchitecture=$(archType)
+ -bl:$(Build.SourcesDirectory)/artifacts/log/BuildStressDebuggees.binlog
+ displayName: 'Build Stress Debuggees'
+
+- script: $(Build.SourcesDirectory)$(dir).dotnet$(dir)dotnet$(exeExt) build
+ $(Build.SourcesDirectory)/src/native/managed/cdac/tests/StressTests/Microsoft.Diagnostics.DataContractReader.StressTests.csproj
+ /p:PrepareHelixPayload=true
+ /p:Configuration=$(_BuildConfig)
+ /p:HelixPayloadDir=$(Build.SourcesDirectory)/artifacts/helixPayload/cdac-stress
+ -bl:$(Build.SourcesDirectory)/artifacts/log/StressTestPayload.binlog
+ displayName: 'Prepare Stress Test Helix Payload'
+
+- pwsh: |
+ $testhostDir = Get-ChildItem -Directory -Path "$(Build.SourcesDirectory)/artifacts/bin/testhost/net*-$(osGroup)-*-$(archType)" | Select-Object -First 1 -ExpandProperty FullName
+ if (-not $testhostDir) {
+ Write-Error "No testhost directory found"
+ exit 1
+ }
+ Write-Host "TestHost root: $testhostDir"
+ Write-Host "##vso[task.setvariable variable=StressTestHostRootDir]$testhostDir"
+
+ $queue = switch ("$(osGroup)_$(archType)") {
+ "windows_x64" { "$(helix_windows_x64)" }
+ "windows_x86" { "$(helix_windows_x64)" }
+ "windows_arm64" { "$(helix_windows_arm64)" }
+ "linux_x64" { "$(helix_linux_x64_oldest)" }
+ "linux_arm64" { "$(helix_linux_arm64_oldest)" }
+ "linux_arm" { "$(helix_linux_arm32_oldest)" }
+ "osx_x64" { "$(helix_macos_x64)" }
+ "osx_arm64" { "$(helix_macos_arm64)" }
+ default { Write-Error "Unsupported platform: $(osGroup)_$(archType)"; exit 1 }
+ }
+ Write-Host "Helix queue: $queue"
+ Write-Host "##vso[task.setvariable variable=CdacStressHelixQueue]$queue"
+ displayName: 'Find Stress TestHost and Helix Queue'
diff --git a/eng/pipelines/runtime-diagnostics.yml b/eng/pipelines/runtime-diagnostics.yml
index f6a3f9215fdd0b..01bb6723bebab8 100644
--- a/eng/pipelines/runtime-diagnostics.yml
+++ b/eng/pipelines/runtime-diagnostics.yml
@@ -267,7 +267,7 @@ extends:
shouldContinueOnError: true
jobParameters:
nameSuffix: CdacDumpTest
- buildArgs: -s clr+libs+tools.cdac+tools.cdacdumptests -c $(_BuildConfig) -rc checked -lc $(_BuildConfig) /p:SkipDumpVersions=net10.0
+ buildArgs: -s clr+libs+tools.cdac+tools.cdacdumptests+tools.cdacstresstests -c $(_BuildConfig) -rc checked -lc $(_BuildConfig) /p:SkipDumpVersions=net10.0
timeoutInMinutes: 180
postBuildSteps:
- template: /eng/pipelines/cdac/prepare-cdac-helix-steps.yml
@@ -286,6 +286,16 @@ extends:
displayName: 'Publish Dump Artifacts'
condition: and(always(), ne(variables['Agent.JobStatus'], 'Succeeded'))
continueOnError: true
+ # cDAC Stress Tests — run GC stress verification on the same Checked build
+ - template: /eng/pipelines/cdac/prepare-cdac-stress-helix-steps.yml
+ - template: /eng/pipelines/common/templates/runtimes/send-to-helix-inner-step.yml
+ parameters:
+ displayName: 'Send cDAC Stress Tests to Helix'
+ sendParams: $(Build.SourcesDirectory)/src/native/managed/cdac/tests/StressTests/cdac-stress-helix.proj /t:Test /p:TargetOS=$(osGroup) /p:TargetArchitecture=$(archType) /p:HelixTargetQueues="$(CdacStressHelixQueue)" /p:TestHostPayload=$(StressTestHostRootDir) /p:StressTestsPayload=$(Build.SourcesDirectory)/artifacts/helixPayload/cdac-stress /bl:$(Build.SourcesDirectory)/artifacts/log/SendStressToHelix.binlog
+ environment:
+ _Creator: dotnet-bot
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+ NUGET_PACKAGES: $(Build.SourcesDirectory)$(dir).packages
- pwsh: |
if ("$(Agent.JobStatus)" -ne "Succeeded") {
Write-Error "One or more cDAC dump test failures were detected. Failing the job."
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs
deleted file mode 100644
index fa72eb606fad75..00000000000000
--- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs
+++ /dev/null
@@ -1,109 +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;
-using Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers;
-
-namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers;
-
-internal class GcScanner
-{
- private readonly Target _target;
- private readonly IExecutionManager _eman;
- private readonly IGCInfo _gcInfo;
-
- internal GcScanner(Target target)
- {
- _target = target;
- _eman = target.Contracts.ExecutionManager;
- _gcInfo = target.Contracts.GCInfo;
- }
-
- public bool EnumGcRefs(
- IPlatformAgnosticContext context,
- CodeBlockHandle cbh,
- CodeManagerFlags flags,
- GcScanContext scanContext)
- {
- TargetNUInt relativeOffset = _eman.GetRelativeOffset(cbh);
- _eman.GetGCInfo(cbh, out TargetPointer gcInfoAddr, out uint gcVersion);
-
- if (_eman.IsFilterFunclet(cbh))
- flags |= CodeManagerFlags.NoReportUntracked;
-
- IGCInfoHandle handle = _gcInfo.DecodePlatformSpecificGCInfo(gcInfoAddr, gcVersion);
- if (handle is not IGCInfoDecoder decoder)
- return false;
-
- uint stackBaseRegister = decoder.StackBaseRegister;
-
- // Lazily compute the caller SP for GC_CALLER_SP_REL slots.
- // The native code uses GET_CALLER_SP(pRD) which comes from EnsureCallerContextIsValid.
- TargetPointer? callerSP = null;
-
- return decoder.EnumerateLiveSlots(
- (uint)relativeOffset.Value,
- flags,
- (bool isRegister, uint registerNumber, int spOffset, uint spBase, uint gcFlags) =>
- {
- GcScanFlags scanFlags = GcScanFlags.None;
- if ((gcFlags & 0x1) != 0) // GC_SLOT_INTERIOR
- scanFlags |= GcScanFlags.GC_CALL_INTERIOR;
- if ((gcFlags & 0x2) != 0) // GC_SLOT_PINNED
- scanFlags |= GcScanFlags.GC_CALL_PINNED;
-
- if (isRegister)
- {
- TargetPointer regValue = ReadRegisterValue(context, (int)registerNumber);
- GcScanSlotLocation loc = new((int)registerNumber, 0, false);
- scanContext.GCEnumCallback(regValue, scanFlags, loc);
- }
- else
- {
- int spReg = context.StackPointerRegister;
- int reg = spBase switch
- {
- 1 => spReg, // GC_SP_REL → SP register number
- 2 => (int)stackBaseRegister, // GC_FRAMEREG_REL → frame base register
- 0 => -(spReg + 1), // GC_CALLER_SP_REL → -(SP + 1)
- _ => throw new InvalidOperationException($"Unknown stack slot base: {spBase}"),
- };
- TargetPointer baseAddr = spBase switch
- {
- 1 => context.StackPointer, // GC_SP_REL
- 2 => ReadRegisterValue(context, (int)stackBaseRegister), // GC_FRAMEREG_REL
- 0 => GetCallerSP(context, ref callerSP), // GC_CALLER_SP_REL
- _ => throw new InvalidOperationException($"Unknown stack slot base: {spBase}"),
- };
-
- TargetPointer addr = new(baseAddr.Value + (ulong)(long)spOffset);
- GcScanSlotLocation loc = new(reg, spOffset, true);
- scanContext.GCEnumCallback(addr, scanFlags, loc);
- }
- });
- }
-
- ///
- /// Compute the caller's SP by unwinding the current context one frame.
- /// Cached in to avoid repeated unwinds for the same frame.
- ///
- private TargetPointer GetCallerSP(IPlatformAgnosticContext context, ref TargetPointer? cached)
- {
- if (cached is null)
- {
- IPlatformAgnosticContext callerContext = context.Clone();
- callerContext.Unwind(_target);
- cached = callerContext.StackPointer;
- }
- return cached.Value;
- }
-
- private static TargetPointer ReadRegisterValue(IPlatformAgnosticContext context, int registerNumber)
- {
- if (!context.TryReadRegister(registerNumber, out TargetNUInt value))
- throw new ArgumentOutOfRangeException(nameof(registerNumber), $"Register number {registerNumber} not found");
-
- return new TargetPointer(value.Value);
- }
-
-}
diff --git a/src/native/managed/cdac/tests/StressTests/BasicCdacStressTests.cs b/src/native/managed/cdac/tests/StressTests/BasicCdacStressTests.cs
new file mode 100644
index 00000000000000..5da773f7828f7b
--- /dev/null
+++ b/src/native/managed/cdac/tests/StressTests/BasicCdacStressTests.cs
@@ -0,0 +1,63 @@
+// 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.Runtime.InteropServices;
+using Microsoft.DotNet.XUnitExtensions;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.Diagnostics.DataContractReader.Tests.GCStress;
+
+///
+/// Runs each debuggee app under corerun with DOTNET_CdacStress=0x51 and asserts
+/// that the cDAC stack reference verification achieves 100% pass rate.
+///
+///
+/// Prerequisites:
+/// - Build CoreCLR native + cDAC: build.cmd -subset clr.native+tools.cdac -c Debug -rc Checked -lc Release
+/// - Generate core_root: src\tests\build.cmd Checked generatelayoutonly /p:LibrariesConfiguration=Release
+/// - Build debuggees: dotnet build this test project
+///
+/// The tests use CORE_ROOT env var if set, otherwise default to the standard artifacts path.
+///
+public class BasicStressTests : CdacStressTestBase
+{
+ public BasicStressTests(ITestOutputHelper output) : base(output) { }
+
+ public static IEnumerable