diff --git a/diagnostics.yml b/diagnostics.yml index 1de7d45acb..e2f7ba097b 100644 --- a/diagnostics.yml +++ b/diagnostics.yml @@ -94,6 +94,17 @@ extends: architecture: arm64 artifactUploadPath: bin/Windows_NT.arm64.Release + - ${{ if ne(parameters.buildOnly, true) }}: + - template: /eng/pipelines/build.yml + parameters: + jobTemplate: ${{ variables.jobTemplate }} + name: Windows_cDAC + osGroup: Windows_NT + useCdac: true + buildConfigs: + - configuration: Release + architecture: x64 + - template: /eng/pipelines/build.yml parameters: jobTemplate: ${{ variables.jobTemplate }} @@ -234,6 +245,19 @@ extends: - configuration: Debug architecture: x64 + - template: /eng/pipelines/build.yml + parameters: + jobTemplate: ${{ variables.jobTemplate }} + name: Ubuntu_22_04_cDAC + osGroup: Linux + container: test_ubuntu_22_04 + dependsOn: Linux + testOnly: true + useCdac: true + buildConfigs: + - configuration: Release + architecture: x64 + - template: /eng/pipelines/build.yml parameters: jobTemplate: ${{ variables.jobTemplate }} diff --git a/eng/pipelines/build.yml b/eng/pipelines/build.yml index ba061cc2f2..4e4c258958 100644 --- a/eng/pipelines/build.yml +++ b/eng/pipelines/build.yml @@ -60,6 +60,11 @@ parameters: type: boolean default: false + # Optional: run tests with cDAC-only mode +- name: useCdac + type: boolean + default: false + # Optional: architecture cross build if true - name: crossBuild type: boolean @@ -144,6 +149,12 @@ jobs: - ${{ if eq(parameters.testOnly, 'true') }}: - _TestArgs: '-test -skipnative' + - ${{ if and(eq(parameters.useCdac, 'true'), eq(parameters.testOnly, 'true')) }}: + - _TestArgs: '-test -skipnative -privatebuild -useCdac' + + - ${{ if and(eq(parameters.useCdac, 'true'), ne(parameters.testOnly, 'true')) }}: + - _TestArgs: '-test -privatebuild -useCdac' + - ${{ if or(eq(parameters.buildOnly, 'true'), eq(parameters.isCodeQLRun, 'true')) }}: - _TestArgs: '' diff --git a/eng/testsoscdac.cmd b/eng/testsoscdac.cmd deleted file mode 100644 index c499ffaecb..0000000000 --- a/eng/testsoscdac.cmd +++ /dev/null @@ -1,2 +0,0 @@ -set SOS_TEST_CDAC=true -%~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 deleted file mode 100644 index bafe751021..0000000000 --- a/eng/testsoscdac.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash - -source="${BASH_SOURCE[0]}" - -# resolve $SOURCE until the file is no longer a symlink -while [[ -h $source ]]; do - scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" - source="$(readlink "$source")" - - # if $source was a relative symlink, we need to resolve it relative to the path where the - # symlink file was located - [[ $source != /* ]] && source="$scriptroot/$source" -done - -scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" -export LLDB_PATH=/usr/bin/lldb -export SOS_TEST_CDAC=true -$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.DebugServices.Implementation/DumpTargetFactory.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/DumpTargetFactory.cs index 7391f4a176..bf94282848 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/DumpTargetFactory.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/DumpTargetFactory.cs @@ -30,13 +30,21 @@ public ITarget OpenDump(string fileName) try { - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && (targetPlatform != OSPlatform.OSX)) - { - throw new NotSupportedException("Analyzing Windows or Linux dumps not supported when running on MacOS"); - } - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && (targetPlatform != OSPlatform.Linux)) + // Cross-platform dump analysis is allowed when using cDAC-only mode (ForceUseContractReader) + // because the cDAC is a host-native NativeAOT binary that can analyze dumps from any platform. + ISettingsService settingsService = _host.Services.GetService(); + bool allowCrossPlatform = settingsService?.ForceUseContractReader == true; + + if (!allowCrossPlatform) { - throw new NotSupportedException("Analyzing Windows or MacOS dumps not supported when running on Linux"); + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && (targetPlatform != OSPlatform.OSX)) + { + throw new NotSupportedException("Analyzing Windows or Linux dumps not supported when running on MacOS"); + } + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && (targetPlatform != OSPlatform.Linux)) + { + throw new NotSupportedException("Analyzing Windows or MacOS dumps not supported when running on Linux"); + } } return new TargetFromDataReader(dataTarget, targetPlatform, _host, fileName); } diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs index f3eeb4aa95..1a608eb152 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs @@ -180,7 +180,11 @@ private string GetLibraryPath(DebugLibraryKind kind) foreach (DebugLibraryInfo libraryInfo in _clrInfo.DebuggingLibraries) { - if (libraryInfo.Kind == kind && RuntimeInformation.IsOSPlatform(libraryInfo.Platform) && libraryInfo.TargetArchitecture == currentArch) + // For cDAC, skip the platform filter — cDAC is a host-native NativeAOT binary + // that can analyze dumps from any target platform. + bool platformMatch = kind == DebugLibraryKind.CDac || RuntimeInformation.IsOSPlatform(libraryInfo.Platform); + + if (libraryInfo.Kind == kind && platformMatch && libraryInfo.TargetArchitecture == currentArch) { libraryPath = GetLocalPath(libraryInfo); if (libraryPath is not null) @@ -206,7 +210,22 @@ private string GetLocalPath(DebugLibraryInfo libraryInfo) string localFilePath; if (libraryInfo.Kind == DebugLibraryKind.CDac) { + // First try the absolute path from ClrMD (works for same-platform scenarios) localFilePath = libraryInfo.FileName; + if (File.Exists(localFilePath)) + { + return localFilePath; + } + // Fall back to RuntimeModuleDirectory if set (supports user-provided cDAC path via setclrpath) + if (!string.IsNullOrEmpty(RuntimeModuleDirectory)) + { + localFilePath = Path.Combine(RuntimeModuleDirectory, Path.GetFileName(libraryInfo.FileName)); + if (File.Exists(localFilePath)) + { + return localFilePath; + } + } + return null; } else { diff --git a/src/SOS/SOS.Hosting/RuntimeWrapper.cs b/src/SOS/SOS.Hosting/RuntimeWrapper.cs index ac37d23e01..89942d67e9 100644 --- a/src/SOS/SOS.Hosting/RuntimeWrapper.cs +++ b/src/SOS/SOS.Hosting/RuntimeWrapper.cs @@ -238,7 +238,13 @@ private int GetClrDataProcess( return HResult.E_INVALIDARG; } *ppClrDataProcess = IntPtr.Zero; - if ((flags & ClrDataProcessFlags.UseCDac) != 0) + + ISettingsService settingsService = _services.GetService(); + bool forceUseCDac = settingsService?.ForceUseContractReader == true; + + // Try cDAC if explicitly requested via flags, or if ForceUseContractReader is set + // (which overrides the caller's flags — callers like LoadClrDebugDll may not pass UseCDac). + if ((flags & ClrDataProcessFlags.UseCDac) != 0 || forceUseCDac) { if (_cdacDataProcess == IntPtr.Zero) { @@ -253,8 +259,9 @@ private int GetClrDataProcess( } *ppClrDataProcess = _cdacDataProcess; } - // Fallback to regular DAC instance if CDac isn't enabled or there where errors creating the instance - if (*ppClrDataProcess == IntPtr.Zero) + // Skip legacy DAC fallback when cDAC-only mode is forced — there may not be a + // platform-matching legacy DAC available (e.g., analyzing a Linux dump on Windows). + if (*ppClrDataProcess == IntPtr.Zero && !forceUseCDac) { if (_clrDataProcess == IntPtr.Zero) { @@ -271,6 +278,10 @@ private int GetClrDataProcess( } if (*ppClrDataProcess == IntPtr.Zero) { + if (forceUseCDac) + { + Trace.TraceError("cDAC-only mode (ForceUseContractReader): cDAC failed to load and legacy DAC fallback is disabled."); + } return HResult.E_NOINTERFACE; } return HResult.S_OK; diff --git a/src/tests/SOS.UnitTests/SOSRunner.cs b/src/tests/SOS.UnitTests/SOSRunner.cs index 80fed0eb5e..c4cc42be7e 100644 --- a/src/tests/SOS.UnitTests/SOSRunner.cs +++ b/src/tests/SOS.UnitTests/SOSRunner.cs @@ -685,6 +685,10 @@ public static async Task StartDebugger(TestInformation information, D && !config.IsPrivateBuildTesting() && !config.IsNightlyBuild(); initialCommands.Add($"runtimes --DacSignatureVerification:{(shouldVerifyDacSignature ? "true" : "false")}"); + if (config.TestCDAC) + { + initialCommands.Add("runtimes --forceusecdac"); + } arguments.Append(debuggerPath); arguments.Append(@" analyze %DUMP_NAME%"); debuggerPath = config.DotNetDumpHost();