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"); }