diff --git a/eng/build.ps1 b/eng/build.ps1
index 0c314072b4..975097c9a6 100644
--- a/eng/build.ps1
+++ b/eng/build.ps1
@@ -10,8 +10,8 @@ Param(
[switch] $skipmanaged,
[switch] $skipnative,
[switch] $bundletools,
- [switch] $useCdac,
- [switch] $noFallback,
+ [ValidateSet("", "cdac", "cdacfallback", "cdacverify", "dac")][string] $dacMode = '',
+ [string] $cdacPath = '',
[switch] $testInterpreter,
[string] $methodfilter = '',
[string] $classfilter = '',
@@ -26,11 +26,12 @@ Param(
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
-if ($noFallback -and -not $useCdac) {
- Write-Error "-noFallback requires -useCdac to also be specified."
+if ($cdacPath -ne '' -and $dacMode -ne 'cdac') {
+ Write-Error "-cdacPath is only valid with -dacMode cdac."
exit 1
}
+
$crossbuild = $false
if (($architecture -eq "arm") -or ($architecture -eq "arm64")) {
$processor = @([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString().ToLowerInvariant())
@@ -73,6 +74,21 @@ if (-not $skipnative) {
}
}
+# Overlay an externally-provided cDAC (mscordaccore_universal) next to the freshly built sos.dll. SOS
+# resolves the cDAC from its own native binaries directory, so this is the only spot it is picked up
+# from. Used by the cdac DacMode to exercise the runtime-under-test's own cDAC instead of the copy
+# restored from a referenced runtime package.
+if ($cdacPath -ne '') {
+ if (-not (Test-Path $cdacPath)) {
+ Write-Error "-cdacPath '$cdacPath' does not exist."
+ exit 1
+ }
+ $cdacDest = Join-Path (Join-Path $artifactsdir "bin\$os.$architecture.$configuration") "mscordaccore_universal.dll"
+ New-Item -ItemType Directory -Force -Path (Split-Path $cdacDest -Parent) | Out-Null
+ Write-Host "Overlaying cDAC: $cdacPath -> $cdacDest"
+ Copy-Item $cdacPath $cdacDest -Force
+}
+
# Install sdk for building, restore and build managed components.
# Test runtime installation and debuggee building is handled by src/tests/dirs.proj targets.
if (-not $skipmanaged) {
@@ -90,12 +106,8 @@ if (-not $skipmanaged) {
# Run the xunit tests
if ($test) {
if (-not $crossbuild) {
- if ($useCdac) {
- $env:SOS_TEST_CDAC="true"
- }
-
- if ($noFallback) {
- $env:SOS_TEST_CDAC_NO_FALLBACK="true"
+ if ($dacMode -ne '') {
+ $env:SOS_TEST_DAC_MODE=$dacMode
}
if ($testInterpreter) {
diff --git a/eng/build.sh b/eng/build.sh
index 3b5147ae16..1320b0a723 100755
--- a/eng/build.sh
+++ b/eng/build.sh
@@ -30,8 +30,8 @@ __PrivateBuild=0
__Test=0
__TestFilter=
__UnprocessedBuildArgs=
-__UseCdac=0
-__NoFallback=0
+__DacMode=
+__CDacPath=
__TestInterpreter=0
__LiveRuntimeDir=
@@ -40,6 +40,8 @@ usage_list+=("-skipnative: do not build native components.")
usage_list+=("-test: run xunit tests")
usage_list+=("-methodfilter: pass method filter to xunit runner (Namespace.ClassName.MethodName)")
usage_list+=("-classfilter: pass class filter to xunit runner (Namespace.ClassName)")
+usage_list+=("-dacmode: which DAC/cDAC the SOS tests load: cdac, cdacfallback, cdacverify, or dac.")
+usage_list+=("-cdacpath: path to an mscordaccore_universal to overlay next to sos.dll (only with -dacmode cdac).")
handle_arguments() {
lowerI="$(echo "${1/--/-}" | tr "[:upper:]" "[:lower:]")"
@@ -115,12 +117,14 @@ handle_arguments() {
__ShiftArgs=1
;;
- usecdac|-usecdac)
- __UseCdac=1
+ dacmode|-dacmode)
+ __DacMode="$(echo "$2" | tr "[:upper:]" "[:lower:]")"
+ __ShiftArgs=1
;;
- nofallback|-nofallback)
- __NoFallback=1
+ cdacpath|-cdacpath)
+ __CDacPath="$2"
+ __ShiftArgs=1
;;
testinterpreter|-testinterpreter)
@@ -140,8 +144,12 @@ handle_arguments() {
source "$__RepoRootDir"/eng/native/build-commons.sh
-if [[ "$__NoFallback" == 1 && "$__UseCdac" != 1 ]]; then
- echo "-nofallback requires -usecdac to also be specified."
+case "$__DacMode" in
+ ""|cdac|cdacfallback|cdacverify|dac) ;;
+ *) echo "Invalid -dacmode '$__DacMode'. Expected cdac, cdacfallback, cdacverify, or dac."; exit 1 ;;
+esac
+if [[ -n "$__CDacPath" && "$__DacMode" != "cdac" ]]; then
+ echo "-cdacpath is only valid with -dacmode cdac."
exit 1
fi
@@ -234,6 +242,26 @@ if [[ "$__NativeBuild" == 1 ]]; then
fi
fi
+#
+# Overlay an externally-provided cDAC (libmscordaccore_universal) next to the freshly built sos so
+# SOS resolves it from its own native binaries directory. Used by the cdac DacMode to exercise the
+# runtime-under-test's own cDAC instead of the copy restored from a referenced runtime package.
+#
+if [[ -n "$__CDacPath" ]]; then
+ if [[ ! -f "$__CDacPath" ]]; then
+ echo "-cdacpath '$__CDacPath' does not exist."
+ exit 1
+ fi
+ if [[ "$__TargetOS" == "osx" ]]; then
+ __CDacDestName="libmscordaccore_universal.dylib"
+ else
+ __CDacDestName="libmscordaccore_universal.so"
+ fi
+ mkdir -p "$__BinDir"
+ echo "Overlaying cDAC: $__CDacPath -> $__BinDir/$__CDacDestName"
+ cp -f "$__CDacPath" "$__BinDir/$__CDacDestName"
+fi
+
#
# Managed build
#
@@ -311,12 +339,8 @@ if [[ "$__Test" == 1 ]]; then
# The .NET createdump facility writes dumps directly and is not affected by ulimit.
ulimit -c 0
- if [[ "$__UseCdac" == 1 ]]; then
- export SOS_TEST_CDAC="true"
- fi
-
- if [[ "$__NoFallback" == 1 ]]; then
- export SOS_TEST_CDAC_NO_FALLBACK="true"
+ if [[ -n "$__DacMode" ]]; then
+ export SOS_TEST_DAC_MODE="$__DacMode"
fi
if [[ "$__TestInterpreter" == 1 ]]; then
diff --git a/eng/testsoscdac.cmd b/eng/testsoscdac.cmd
index c499ffaecb..0083e1b094 100644
--- a/eng/testsoscdac.cmd
+++ b/eng/testsoscdac.cmd
@@ -1,2 +1,2 @@
-set SOS_TEST_CDAC=true
+set SOS_TEST_DAC_MODE=cdacfallback
%~dp0..\.dotnet\dotnet.exe test --no-build --logger "console;verbosity=detailed" %~dp0..\src\tests\SOS.UnitTests\SOS.UnitTests.csproj --filter "Category=CDACCompatible"
diff --git a/eng/testsoscdac.sh b/eng/testsoscdac.sh
index bafe751021..fb3a8fa996 100644
--- a/eng/testsoscdac.sh
+++ b/eng/testsoscdac.sh
@@ -14,5 +14,5 @@ done
scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
export LLDB_PATH=/usr/bin/lldb
-export SOS_TEST_CDAC=true
+export SOS_TEST_DAC_MODE=cdacfallback
$scriptroot/../.dotnet/dotnet test --no-build --logger "console;verbosity=detailed" $scriptroot/../src/tests/SOS.UnitTests/SOS.UnitTests.csproj --filter "Category=CDACCompatible"
diff --git a/src/Microsoft.Diagnostics.TestHelpers/TestConfiguration.cs b/src/Microsoft.Diagnostics.TestHelpers/TestConfiguration.cs
index f93937f875..bd3112cfe8 100644
--- a/src/Microsoft.Diagnostics.TestHelpers/TestConfiguration.cs
+++ b/src/Microsoft.Diagnostics.TestHelpers/TestConfiguration.cs
@@ -72,8 +72,6 @@ private void ParseConfigFile(string path)
["TargetRid"] = GetRid(),
["TargetArchitecture"] = OS.TargetArchitecture.ToString().ToLowerInvariant(),
["NuGetPackageCacheDir"] = nugetPackages,
- ["TestCDAC"] = Environment.GetEnvironmentVariable("SOS_TEST_CDAC"),
- ["TestCDACNoFallback"] = Environment.GetEnvironmentVariable("SOS_TEST_CDAC_NO_FALLBACK")
};
if (OS.Kind == OSKind.Windows)
{
@@ -455,13 +453,20 @@ private string GetStringViewWithVersion(string version)
{
sb.Append(".singlefile");
}
- if (TestCDACNoFallback)
+ switch (DacMode)
{
- sb.Append(".cdac_no_fallback");
- }
- else if (TestCDAC)
- {
- sb.Append(".cdac");
+ case DacMode.CDacFallback:
+ sb.Append(".cdacfallback");
+ break;
+ case DacMode.CDacVerify:
+ sb.Append(".cdacverify");
+ break;
+ case DacMode.CDac:
+ sb.Append(".cdac");
+ break;
+ case DacMode.Dac:
+ sb.Append(".dac");
+ break;
}
if (UseInterpreter)
{
@@ -562,19 +567,25 @@ public bool IsDesktop
}
///
- /// Returns true if test should use the cDAC.
+ /// Controls which DAC/cDAC the SOS tests load (see ). The value comes
+ /// from the SOS_TEST_DAC_MODE environment variable, which eng/build.* sets from its -dacMode argument
+ /// (and which direct "dotnet test" runs can set themselves).
///
- public bool TestCDAC
+ public DacMode DacMode
{
- get { return string.Equals(GetValue("TestCDAC"), "true", StringComparison.InvariantCultureIgnoreCase); }
- }
-
- ///
- /// Returns true if tests should use the cDAC with no fallback to the legacy DAC.
- ///
- public bool TestCDACNoFallback
- {
- get { return string.Equals(GetValue("TestCDACNoFallback"), "true", StringComparison.InvariantCultureIgnoreCase); }
+ get
+ {
+ string mode = Environment.GetEnvironmentVariable("SOS_TEST_DAC_MODE");
+ return (mode ?? string.Empty).Trim().ToLowerInvariant() switch
+ {
+ "" => DacMode.Default,
+ "cdac" => DacMode.CDac,
+ "cdacfallback" => DacMode.CDacFallback,
+ "cdacverify" => DacMode.CDacVerify,
+ "dac" => DacMode.Dac,
+ _ => throw new NotSupportedException($"Unknown DacMode '{mode}'. Expected cdac, cdacfallback, cdacverify, dac, or empty."),
+ };
+ }
}
///
@@ -952,6 +963,40 @@ public enum OSKind
Unknown,
}
+ ///
+ /// Controls which DAC/cDAC the SOS tests load. The harness (SOSRunner) translates this into the
+ /// DOTNET_ENABLE_CDAC / CDAC_NO_FALLBACK environment variables and the "runtimes --usecdac" SOS
+ /// command, so there are no per-mode special cases elsewhere.
+ ///
+ public enum DacMode
+ {
+ ///
+ /// Unspecified: the harness applies no DAC/cDAC configuration (SOS uses its default load policy).
+ ///
+ Default,
+
+ ///
+ /// cDAC hosted by the in-box DAC, with per-API fallback to the legacy DAC.
+ ///
+ CDacFallback,
+
+ ///
+ /// cDAC hosted by the in-box DAC, with no fallback to the legacy DAC (still verifies against it).
+ ///
+ CDacVerify,
+
+ ///
+ /// The standalone cDAC (mscordaccore_universal) loaded directly through SOS hosting -- the
+ /// canonical cDAC path.
+ ///
+ CDac,
+
+ ///
+ /// The legacy in-box DAC only.
+ ///
+ Dac,
+ }
+
///
/// The OS specific configuration
///
diff --git a/src/tests/SOS.UnitTests/SOSRunner.cs b/src/tests/SOS.UnitTests/SOSRunner.cs
index 5555a42916..4314fa1985 100644
--- a/src/tests/SOS.UnitTests/SOSRunner.cs
+++ b/src/tests/SOS.UnitTests/SOSRunner.cs
@@ -719,14 +719,30 @@ public static async Task StartDebugger(TestInformation information, D
WithLog(scriptLogger).
WithTimeout(TimeSpan.FromMinutes(10));
- if (config.TestCDACNoFallback)
+ // Configure which DAC/cDAC SOS loads, driven entirely by the DacMode test setting (see
+ // TestConfiguration.DacMode). The harness translates the mode through two channels:
+ // * Env vars (DOTNET_ENABLE_CDAC / CDAC_NO_FALLBACK) set here on the debugger process. The
+ // in-box DAC, loaded into the debugger host, reads them and hosts the cDAC reader itself;
+ // there is no SOS-command equivalent. They are set before the debugger launches.
+ // * SOS's own cDAC load policy ("runtimes --usecdac"), applied in LoadSosExtension. That
+ // command is an SOS extension command and is not available until SOS has been loaded, so
+ // it cannot be issued as a pre-SOS initial debugger command.
+ switch (config.DacMode)
{
- processRunner.WithEnvironmentVariable("DOTNET_ENABLE_CDAC", "1");
- processRunner.WithEnvironmentVariable("CDAC_NO_FALLBACK", "1");
- }
- else if (config.TestCDAC)
- {
- processRunner.WithEnvironmentVariable("DOTNET_ENABLE_CDAC", "1");
+ case DacMode.CDacFallback:
+ // cDAC hosted by the in-box DAC, with per-API fallback to the legacy DAC.
+ processRunner.WithEnvironmentVariable("DOTNET_ENABLE_CDAC", "1");
+ break;
+ case DacMode.CDacVerify:
+ // cDAC hosted by the in-box DAC, with no fallback to the legacy DAC.
+ processRunner.WithEnvironmentVariable("DOTNET_ENABLE_CDAC", "1");
+ processRunner.WithEnvironmentVariable("CDAC_NO_FALLBACK", "1");
+ break;
+ case DacMode.CDac:
+ case DacMode.Dac:
+ case DacMode.Default:
+ // No debuggee env vars; the SOS load policy (if any) is applied in LoadSosExtension.
+ break;
}
// Enable stress logging for both live and dump paths when requested
@@ -1134,6 +1150,23 @@ public async Task LoadSosExtension()
default:
throw new Exception($"{DebuggerToString} cannot load sos extension");
}
+
+ // Apply the cDAC load policy selected by the test's DacMode now that SOS is loaded (the
+ // "runtimes" command is unavailable before this) and before any runtime is accessed, so SOS
+ // uses the requested DAC/cDAC the first time it resolves the runtime. CDacFallback/CDacVerify
+ // instead rely on the in-box DAC via env vars set in StartDebugger and keep SOS's default
+ // policy (which does not load the standalone cDAC when DOTNET_ENABLE_CDAC is set).
+ string cdacPolicyCommand = _config.DacMode switch
+ {
+ DacMode.CDac => "runtimes --usecdac true", // Force the standalone cDAC next to sos.dll.
+ DacMode.Dac => "runtimes --usecdac false", // Force the legacy in-box DAC.
+ _ => null,
+ };
+ if (cdacPolicyCommand is not null && Debugger != NativeDebugger.Gdb)
+ {
+ commands.Add((Debugger == NativeDebugger.Cdb ? "!" : "") + cdacPolicyCommand);
+ }
+
await RunCommands(commands);
// Helper function to switch to the thread with an exception
@@ -1599,7 +1632,7 @@ private HashSet GetEnabledDefines()
{
defines.Add("HOST_RUNTIME_NONE");
}
- if (_config.TestCDACNoFallback)
+ if (_config.DacMode == DacMode.CDacVerify)
{
defines.Add("CDAC_NO_FALLBACK_TESTING");
}