Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 0 additions & 24 deletions eng/CdacPackageItems.props

This file was deleted.

8 changes: 6 additions & 2 deletions eng/InstallNativePackages.targets
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@
<PackageSourceFiles Include="$(NuGetPackageRoot)microsoft.diasymreader.native\$(MicrosoftDiaSymReaderNativeVersion)\runtimes\$(TargetRid)\native\*" Condition="'$(OS)' == 'Windows_NT'" />
</ItemGroup>

<Import Condition="'$(PackageWithCDac)' == 'true'" Project="$(RepositoryEngineeringDir)CdacPackageItems.props" />
<ItemGroup Condition="'$(PackageWithCDac)' == 'true'">
<PackageDownload Include="runtime.$(TargetRid).Microsoft.DotNet.Cdac.Transport"
Version="[$(runtimewinx64MicrosoftDotNetCdacTransportVersion)]" />
<PackageSourceFiles Include="$(NuGetPackageRoot)runtime.$(TargetRid).microsoft.dotnet.cdac.transport\$(runtimewinx64MicrosoftDotNetCdacTransportVersion)\runtimes\$(TargetRid)\native\*" />
</ItemGroup>

<!--
Installs the above packages into the $(ArtifactsBinNativeDir) directory.
Installs the above packages into the $(ArtifactsBinNativeDir) directory.
-->
<Target Name="InstallNativePackages">
<Message Importance="High" Text="Installing packages to $(ArtifactsBinNativeDir)..." />
Expand Down
8 changes: 8 additions & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@
<DotNetUseShippingVersions>true</DotNetUseShippingVersions>
<AutoGenerateAssemblyVersion>true</AutoGenerateAssemblyVersion>

<!--
Embed the cDAC native binary into the diagnostics-tool packages on preview branches.
Release/servicing branches (PreReleaseVersionLabel != 'preview') keep the legacy
behavior of pulling the DAC from the runtime install or symbol server. This can be
overridden explicitly on the command line with /p:PackageWithCDac=true|false.
-->
<PackageWithCDac Condition="'$(PackageWithCDac)' == '' and '$(PreReleaseVersionLabel)' == 'preview'">true</PackageWithCDac>

<!--
Our nightly tools are stable versioned but go to a non-stable feed.
Arcade usually disallows this, but we use such a versioning scheme by design.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,7 @@ public string GetTempDirectory()

public virtual bool DacSignatureVerificationEnabled { get; set; }

public bool UseContractReader { get; set; }

public bool ForceUseContractReader { get; set; }
public CDacLoadPolicy CDacLoadPolicy { get; set; }

#endregion

Expand Down
108 changes: 86 additions & 22 deletions src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
public class Runtime : IRuntime, IDisposable
{
private readonly ClrInfo _clrInfo;
private readonly IHostAssetResolver _hostAssetResolver;
private readonly ISettingsService _settingsService;
private readonly ISymbolService _symbolService;
private Version _runtimeVersion;
Expand All @@ -36,6 +37,10 @@ public Runtime(IServiceProvider services, int id, ClrInfo clrInfo)
Target = services.GetService<ITarget>() ?? throw new DiagnosticsException("Dump or live session target required");
Id = id;
_clrInfo = clrInfo ?? throw new ArgumentNullException(nameof(clrInfo));
// IHostAssetResolver is optional: it is registered by the SOS hosting layer to locate
// the bundled cDAC. When absent (hosts without SOS.Hosting, e.g. some test hosts), cDAC
// resolution returns null and the in-box DAC is used.
_hostAssetResolver = services.GetService<IHostAssetResolver>();
_settingsService = services.GetService<ISettingsService>() ?? throw new ArgumentException("ISettingsService required");
_symbolService = services.GetService<ISymbolService>() ?? throw new ArgumentException("ISymbolService required");

Expand Down Expand Up @@ -100,27 +105,8 @@ public Version RuntimeVersion
}
}

public string GetCDacFilePath()
{
if (_cdacFilePath is null)
{
if (_settingsService.UseContractReader || _settingsService.ForceUseContractReader)
{
_cdacFilePath = GetLibraryPath(DebugLibraryKind.CDac);
}
}
return _cdacFilePath;
}

public string GetDacFilePath(out bool verifySignature)
{
if (_settingsService.ForceUseContractReader)
{
// Don't verify signature when using the CDAC and don't change the cached value
// because it only applies to the regular DAC in _dacFilePath.
verifySignature = false;
return GetCDacFilePath();
}
if (_dacFilePath is null)
{
_dacFilePath = GetLibraryPath(DebugLibraryKind.Dac);
Expand All @@ -133,6 +119,26 @@ public string GetDacFilePath(out bool verifySignature)
return _dacFilePath;
}

public string GetCDacFilePath()
{
// ShouldUseCDac() evaluates the cDAC loading policy. When it returns false the caller
// uses the in-box DAC from GetDacFilePath instead.
if (!ShouldUseCDac())
{
return null;
}

// The cDAC is bundled with the diagnostics tool and is never downloaded, so a missing
// path means it isn't available for this host.
_cdacFilePath ??= GetLibraryPath(DebugLibraryKind.CDac);
if (_cdacFilePath is null && _settingsService.CDacLoadPolicy == CDacLoadPolicy.UseCDac)
{
// The cDAC was explicitly forced but isn't bundled with this tool.
throw new DiagnosticsException($"The cDAC was explicitly requested but no matching cDAC is available for this runtime: {RuntimeModule.FileName}");
}
return _cdacFilePath;
}

public string GetDbiFilePath()
{
_dbiFilePath ??= GetLibraryPath(DebugLibraryKind.Dbi);
Expand All @@ -141,12 +147,59 @@ public string GetDbiFilePath()

#endregion

/// <summary>
/// The minimum runtime major version that supports the cDAC.
/// </summary>
private const int MinCDacRuntimeMajorVersion = 11;

/// <summary>
/// Evaluates the cDAC loading policy for this runtime. This is the single place that
/// decides whether the diagnostics tool should load the cDAC itself in place of the
/// in-box DAC, based on the <see cref="ISettingsService.CDacLoadPolicy"/> setting and the
/// target runtime version.
/// </summary>
private bool ShouldUseCDac()
{
return _settingsService.CDacLoadPolicy switch
{
CDacLoadPolicy.UseLegacyDac => false, // Never load the cDAC.
CDacLoadPolicy.UseCDac => true, // Always use the cDAC, regardless of the runtime version. Availability is
// checked by the caller (a missing forced cDAC is a hard error).
_ => ShouldUseCDacByDefault(), // No explicit setting: evaluate the default policy.
};
}

/// <summary>
/// The default cDAC policy used when <see cref="ISettingsService.CDacLoadPolicy"/> is not set.
/// </summary>
private bool ShouldUseCDacByDefault()
{
// When DOTNET_ENABLE_CDAC is requested, the in-box (legacy) DAC loads and drives the
// cDAC contract reader itself, including its own dac-vs-cdac fallback/comparison
// (see CDAC_NO_FALLBACK). Defer to that mechanism rather than loading the cDAC
// directly so those scenarios (for example, the runtime's cDAC test pipeline that
// points at a freshly built cDAC via -liveruntimedir) keep working.
if (Environment.GetEnvironmentVariable("DOTNET_ENABLE_CDAC") == "1"
|| Environment.GetEnvironmentVariable("COMPlus_ENABLE_CDAC") == "1")
{
return false;
}

// Default policy: use the cDAC only for runtimes that support it. This needs to be
// changed to consider native AOT and singlefile. This is a dummy policy for work
// we will offload to dbgshim.
return RuntimeVersion is not null && RuntimeVersion.Major >= MinCDacRuntimeMajorVersion;
}

/// <summary>
/// Create ClrRuntime instance
/// </summary>
private ClrRuntime CreateRuntime()
{
string dacFilePath = GetDacFilePath(out _);
// Prefer the cDAC for the ClrMD data-access path when policy selects it; fall back to the in-box DAC.
// We ignore the dac verification param since it's already set as part of the CLRMD DataTarget creation
// now (it's a global setting to the session).
string dacFilePath = GetCDacFilePath() ?? GetDacFilePath(out _);
if (dacFilePath is not null)
{
Trace.TraceInformation($"Creating ClrRuntime #{Id} {dacFilePath}");
Expand Down Expand Up @@ -187,6 +240,13 @@ private string GetLibraryPath(DebugLibraryKind kind)
{
break;
}
// The cDAC is an analyzer-host artifact shipped inside the diagnostics tool
// (next to sos.dll, matching the host's RID). It is not symbol-store indexed
// by the target runtime, so never attempt to download it.
if (libraryInfo.Kind == DebugLibraryKind.CDac)
{
continue;
}
if (libraryInfo.ArchivedUnder != SymbolProperties.None)
{
libraryPath = DownloadFile(libraryInfo);
Expand All @@ -206,7 +266,11 @@ private string GetLocalPath(DebugLibraryInfo libraryInfo)
string localFilePath;
if (libraryInfo.Kind == DebugLibraryKind.CDac)
{
localFilePath = libraryInfo.FileName;
// The cDAC ships next to the native sos module. Ask the host asset resolver where it
// is rather than reasoning about layouts here (ClrMD's DebuggingLibraries entry points
// at the managed-assembly base directory, so it is ignored). The shared existence
// check below verifies the path, so the in-box DAC is used when the cDAC isn't bundled.
localFilePath = _hostAssetResolver?.GetCDacPath();
}
else
{
Expand All @@ -219,7 +283,7 @@ private string GetLocalPath(DebugLibraryInfo libraryInfo)
localFilePath = Path.Combine(Path.GetDirectoryName(RuntimeModule.FileName), Path.GetFileName(libraryInfo.FileName));
}
}
if (!File.Exists(localFilePath))
if (localFilePath is null || !File.Exists(localFilePath))
{
localFilePath = null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Diagnostics.Runtime;

namespace Microsoft.Diagnostics.DebugServices.Implementation
Expand Down Expand Up @@ -33,10 +35,33 @@ public IEnumerable<IRuntime> EnumerateRuntimes(int startingRuntimeId, RuntimeEnu
// not flushed when the Target/RuntimeService is flushed; they are all disposed and the list cleared. They are
// all re-created the next time the IRuntime or ClrRuntime instance is queried.
ISettingsService settingsService = _services.GetService<ISettingsService>();
bool verifyDac = settingsService?.DacSignatureVerificationEnabled ?? true;

// The cDAC (mscordaccore_universal) ships inside the (signed) diagnostics tool package and
// carries no individual DAC signature, so it cannot satisfy ClrMD's signature check. Trust it
// the same way the native and SOS-hosting cDAC load paths do (load without verification), while
// still verifying the in-box DAC. We trust ONLY the exact cDAC path the host resolver provides
// (the bundled binary next to sos); matching by file name alone would let a name-hijacked DLL
// loaded from elsewhere (target runtime dir, symbol cache, ...) bypass verification.
string trustedCDacPath = _services.GetService<IHostAssetResolver>()?.GetCDacPath();
string normalizedTrustedCDacPath = string.IsNullOrEmpty(trustedCDacPath) ? null : Path.GetFullPath(trustedCDacPath);
StringComparison pathComparison = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;

DataTarget dataTarget = new(_services.GetService<IDataReader>(), new DataTargetOptions()
{
ForceCompleteRuntimeEnumeration = (flags & RuntimeEnumerationFlags.All) != 0,
VerifyDacOnWindows = settingsService?.DacSignatureVerificationEnabled ?? true
VerifyDacOnWindows = verifyDac,
// Takes priority over VerifyDacOnWindows: skip verification only for the exact bundled cDAC.
DacSignatureVerificationOverride = (dacFilePath) =>
{
if (normalizedTrustedCDacPath is not null
&& !string.IsNullOrEmpty(dacFilePath)
&& string.Equals(Path.GetFullPath(dacFilePath), normalizedTrustedCDacPath, pathComparison))
{
return false;
}
return verifyDac;
}
});
for (int i = 0; i < dataTarget.ClrVersions.Length; i++)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -360,8 +360,7 @@ private sealed class ExtensionLoadContext : AssemblyLoadContext
"Microsoft.FileFormats",
"Microsoft.SymbolStore",
"SOS.Extensions",
"SOS.Hosting",
"SOS.InstallHelper"
"SOS.Hosting"
};

private static readonly string _defaultAssembliesPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
Expand Down
28 changes: 28 additions & 0 deletions src/Microsoft.Diagnostics.DebugServices/CDacLoadPolicy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.Diagnostics.DebugServices
{
/// <summary>
/// Controls whether the cDAC is used in place of the in-box DAC.
/// </summary>
public enum CDacLoadPolicy
{
/// <summary>
/// Evaluate policy and fall back. The cDAC is used when the target runtime supports it
/// and a matching cDAC is available next to the diagnostics tool; otherwise the in-box
/// DAC is used.
/// </summary>
Default,

/// <summary>
/// Always use the cDAC. Runtime construction fails if no matching cDAC is available.
/// </summary>
UseCDac,

/// <summary>
/// Always use the in-box DAC. The cDAC is never loaded.
/// </summary>
UseLegacyDac,
}
}
29 changes: 29 additions & 0 deletions src/Microsoft.Diagnostics.DebugServices/IHostAssetResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.Diagnostics.DebugServices
{
/// <summary>
/// Answers questions about where the host's assets live — the native binaries the host ships
/// (the native sos module, the cDAC, DiaSymReader, …). The directory of those assets is
/// host-specific: a native debugger host supplies it (the SOS hosting layer feeds it from the
/// host's sos module location), while in-process hosts (dotnet-dump) derive it from the tool's
/// package layout. Runtimes and other services query this resolver instead of reasoning about
/// layouts themselves.
/// </summary>
public interface IHostAssetResolver
{
/// <summary>
/// The directory containing the host's native binaries (the native sos module and the
/// cDAC that ships next to it).
/// </summary>
string NativeBinariesDirectory { get; }

/// <summary>
/// The full path to where the cDAC native library (mscordaccore_universal) ships for the
/// current host (next to the native sos module). The path is not probed; the caller is
/// expected to check existence (the cDAC is not bundled in, for example, release builds).
/// </summary>
string GetCDacPath();
}
}
8 changes: 5 additions & 3 deletions src/Microsoft.Diagnostics.DebugServices/IRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,15 @@ public interface IRuntime
string RuntimeModuleDirectory { get; set; }

/// <summary>
/// Returns the DAC file path
/// Returns the DAC file path to use for this runtime.
/// </summary>
/// <param name="verifySignature">returns if the DAC signature should be verified</param>
/// <param name="verifySignature">returns whether the returned DAC requires signature verification.</param>
string GetDacFilePath(out bool verifySignature);

/// <summary>
/// Returns the CDac file path if enabled by global settings
/// Returns the cDAC (mscordaccore_universal) file path to use for this runtime, or null
/// when the cDAC should not be used (policy disabled or unsupported runtime) or isn't
/// available.
/// </summary>
string GetCDacFilePath();

Expand Down
10 changes: 3 additions & 7 deletions src/Microsoft.Diagnostics.DebugServices/ISettingsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,9 @@ public interface ISettingsService
bool DacSignatureVerificationEnabled { get; set; }

/// <summary>
/// If true, uses the CDAC contract reader if available.
/// Controls whether the cDAC is used in place of the in-box DAC. See
/// <see cref="DebugServices.CDacLoadPolicy"/> for the individual policy values.
/// </summary>
bool UseContractReader { get; set; }

/// <summary>
/// If true, always use the CDAC contract reader even when not requested
/// </summary>
bool ForceUseContractReader { get; set; }
CDacLoadPolicy CDacLoadPolicy { get; set; }
}
}
Loading
Loading