Skip to content

Commit 213ee92

Browse files
authored
SOS: activate cDAC through dbgshim (#5966)
## Summary Use dbgshim as SOS's only cDAC activation path. SOS supplies the runtime module and data target, receives a cDAC-backed `IXCLRDataProcess`, and registers it with ClrMD. Remove runtime-version checks from cDAC selection. The cDAC now determines whether it supports a target through contract validation. ## Policy | Policy | Behavior | |---|---| | `Default` | Tool-defined cDAC and DAC fallback policy | | `PreferCDac` | Try cDAC, then allow DAC fallback | | `OnlyUseCDac` | Require cDAC; do not fall back | | `UseLegacyDac` | Use the DAC without trying cDAC | `runtimes --usecdac` sets the policy for the SOS session. ## Design - `CreateClrDataProcessFromCDac` always requests `CDacOnly` from dbgshim, so a successful result is known to be cDAC-backed. - `Runtime` owns and caches the cDAC process, activation HRESULT, and lifetime. - `RuntimeWrapper` is the COM projection of `Runtime` used by native SOS; it does not independently activate cDAC. - Native SOS without managed hosting uses the equivalent dbgshim activation path. - ClrMD receives an activated cDAC interface through `AddLoadedRuntime`; ClrMD only loads the legacy DAC fallback. - dbgshim is packaged for every SOS RID. cDAC is packaged for every SOS RID when enabled.
1 parent 670d2b9 commit 213ee92

20 files changed

Lines changed: 763 additions & 341 deletions

File tree

src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs

Lines changed: 101 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Runtime.InteropServices;
1010
using System.Text;
1111
using Microsoft.Diagnostics.Runtime;
12+
using Microsoft.Diagnostics.Runtime.Utilities;
1213
using Microsoft.SymbolStore;
1314
using Microsoft.SymbolStore.KeyGenerators;
1415

@@ -20,14 +21,14 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
2021
public class Runtime : IRuntime, IDisposable
2122
{
2223
private readonly ClrInfo _clrInfo;
23-
private readonly IHostAssetResolver _hostAssetResolver;
2424
private readonly ISettingsService _settingsService;
2525
private readonly ISymbolService _symbolService;
2626
private Version _runtimeVersion;
2727
private ClrRuntime _clrRuntime;
28+
private IClrDataProcess _clrDataProcess;
29+
private int? _cdacActivationResult;
2830
private string _dacFilePath;
29-
private bool _verifySignature; // This only applies to the regular DAC, not the CDAC
30-
private string _cdacFilePath;
31+
private bool _verifySignature;
3132
private string _dbiFilePath;
3233

3334
protected readonly ServiceContainer _serviceContainer;
@@ -37,10 +38,6 @@ public Runtime(IServiceProvider services, int id, ClrInfo clrInfo)
3738
Target = services.GetService<ITarget>() ?? throw new DiagnosticsException("Dump or live session target required");
3839
Id = id;
3940
_clrInfo = clrInfo ?? throw new ArgumentNullException(nameof(clrInfo));
40-
// IHostAssetResolver is optional: it is registered by the SOS hosting layer to locate
41-
// the bundled cDAC. When absent (hosts without SOS.Hosting, e.g. some test hosts), cDAC
42-
// resolution returns null and the in-box DAC is used.
43-
_hostAssetResolver = services.GetService<IHostAssetResolver>();
4441
_settingsService = services.GetService<ISettingsService>() ?? throw new ArgumentException("ISettingsService required");
4542
_symbolService = services.GetService<ISymbolService>() ?? throw new ArgumentException("ISymbolService required");
4643

@@ -64,6 +61,9 @@ void IDisposable.Dispose()
6461
_clrRuntime = null;
6562
_serviceContainer.RemoveService(typeof(IRuntime));
6663
_serviceContainer.DisposeServices();
64+
_clrDataProcess?.Dispose();
65+
_clrDataProcess = null;
66+
_cdacActivationResult = null;
6767
}
6868

6969
#region IRuntime
@@ -111,24 +111,22 @@ public string GetDacFilePath(out bool verifySignature)
111111
return _dacFilePath;
112112
}
113113

114-
public string GetCDacFilePath()
114+
public int GetClrDataProcessFromCDac(out IntPtr clrDataProcess)
115115
{
116-
// ShouldUseCDac() evaluates the cDAC loading policy. When it returns false the caller
117-
// uses the in-box DAC from GetDacFilePath instead.
118-
if (!ShouldUseCDac())
116+
if (!_cdacActivationResult.HasValue)
119117
{
120-
return null;
118+
IClrDataProcessActivator activator = Services.GetService<IClrDataProcessActivator>();
119+
_cdacActivationResult = activator?.CreateClrDataProcessFromCDac(this, out _clrDataProcess) ?? HResult.E_NOINTERFACE;
121120
}
122121

123-
// The cDAC is bundled with the diagnostics tool and is never downloaded, so a missing
124-
// path means it isn't available for this host.
125-
_cdacFilePath ??= GetLibraryPath(DebugLibraryKind.CDac);
126-
if (_cdacFilePath is null && _settingsService.CDacLoadPolicy == CDacLoadPolicy.UseCDac)
122+
clrDataProcess = _clrDataProcess?.Interface ?? IntPtr.Zero;
123+
int result = _cdacActivationResult ?? HResult.E_NOINTERFACE;
124+
if (result >= 0 && clrDataProcess == IntPtr.Zero)
127125
{
128-
// The cDAC was explicitly forced but isn't bundled with this tool.
129-
throw new DiagnosticsException($"The cDAC was explicitly requested but no matching cDAC is available for this runtime: {RuntimeModule.FileName}");
126+
result = HResult.E_NOINTERFACE;
127+
_cdacActivationResult = result;
130128
}
131-
return _cdacFilePath;
129+
return result;
132130
}
133131

134132
public string GetDbiFilePath()
@@ -140,82 +138,106 @@ public string GetDbiFilePath()
140138
#endregion
141139

142140
/// <summary>
143-
/// The minimum runtime major version that supports the cDAC.
144-
/// </summary>
145-
private const int MinCDacRuntimeMajorVersion = 11;
146-
147-
/// <summary>
148-
/// Evaluates the cDAC loading policy for this runtime. This is the single place that
149-
/// decides whether the diagnostics tool should load the cDAC itself in place of the
150-
/// in-box DAC, based on the <see cref="ISettingsService.CDacLoadPolicy"/> setting and the
151-
/// target runtime version.
141+
/// Create ClrRuntime instance
152142
/// </summary>
153-
private bool ShouldUseCDac()
143+
private ClrRuntime CreateRuntime()
154144
{
155-
return _settingsService.CDacLoadPolicy switch
145+
CDacLoadPolicy policy = _settingsService.CDacLoadPolicy;
146+
bool useCDac = CDacPolicy.ShouldTryCDac(policy);
147+
Trace.TraceInformation($"Runtime #{Id} data-access: begin (cDAC policy={policy}, cDAC attempted={useCDac})");
148+
149+
if (useCDac)
150+
{
151+
int hr = GetClrDataProcessFromCDac(out IntPtr clrDataProcess);
152+
if (hr >= 0 && clrDataProcess != IntPtr.Zero)
153+
{
154+
Trace.TraceInformation($"Runtime #{Id} data-access: received an IXCLRDataProcess");
155+
return CreateRuntimeFromClrDataProcess();
156+
}
157+
Trace.TraceInformation($"Runtime #{Id} data-access: IXCLRDataProcess activation failed {hr:X8}");
158+
}
159+
160+
if (policy == CDacLoadPolicy.OnlyUseCDac)
161+
{
162+
Trace.TraceError($"Runtime #{Id} data-access: cDAC was required but could not service this runtime: {RuntimeModule.FileName}");
163+
return null;
164+
}
165+
166+
// We ignore the dac signature verification param since it's already set as part of the CLRMD DataTarget creation
167+
// now (it's a global setting to the session).
168+
string dacFilePath = GetDacFilePath(out _);
169+
if (dacFilePath is not null)
156170
{
157-
CDacLoadPolicy.UseLegacyDac => false, // Never load the cDAC.
158-
CDacLoadPolicy.UseCDac => true, // Always use the cDAC, regardless of the runtime version. Availability is
159-
// checked by the caller (a missing forced cDAC is a hard error).
160-
_ => ShouldUseCDacByDefault(), // No explicit setting: evaluate the default policy.
161-
};
171+
Trace.TraceInformation($"Runtime #{Id} data-access: falling back to the in-box DAC {dacFilePath}");
172+
return TryCreateRuntimeFromDac(dacFilePath);
173+
}
174+
175+
Trace.TraceError($"Runtime #{Id} data-access: could not find or download a matching DAC for this runtime: {RuntimeModule.FileName}");
176+
return null;
162177
}
163178

164179
/// <summary>
165-
/// The default cDAC policy used when <see cref="ISettingsService.CDacLoadPolicy"/> is not set.
180+
/// Creates a ClrRuntime with the specified DAC.
166181
/// </summary>
167-
private bool ShouldUseCDacByDefault()
182+
private ClrRuntime TryCreateRuntimeFromDac(string dacFilePath)
168183
{
169-
// When DOTNET_ENABLE_CDAC is requested, the in-box (legacy) DAC loads and drives the
170-
// cDAC contract reader itself, including its own dac-vs-cdac fallback/comparison
171-
// (see CDAC_NO_FALLBACK). Defer to that mechanism rather than loading the cDAC
172-
// directly so those scenarios (for example, the runtime's cDAC test pipeline that
173-
// points at a freshly built cDAC via -liveruntimedir) keep working.
174-
if (Environment.GetEnvironmentVariable("DOTNET_ENABLE_CDAC") == "1"
175-
|| Environment.GetEnvironmentVariable("COMPlus_ENABLE_CDAC") == "1")
184+
Trace.TraceInformation($"Creating ClrRuntime #{Id} {dacFilePath}");
185+
try
176186
{
177-
return false;
187+
// Ignore the DAC version mismatch that can happen because the clrmd ELF dump reader
188+
// returns 0.0.0.0 for the runtime module that the DAC is matched against.
189+
return _clrRuntime = _clrInfo.CreateRuntime(dacFilePath, ignoreMismatch: true);
190+
}
191+
catch (Exception ex) when
192+
(ex is DllNotFoundException or
193+
FileNotFoundException or
194+
InvalidOperationException or
195+
InvalidDataException or
196+
ClrDiagnosticsException)
197+
{
198+
Trace.TraceError("CreateRuntime FAILED: {0}", ex.ToString());
199+
return null;
178200
}
179-
180-
// Default policy: use the cDAC only for runtimes that support it. This needs to be
181-
// changed to consider native AOT and singlefile. This is a dummy policy for work
182-
// we will offload to dbgshim.
183-
return RuntimeVersion is not null && RuntimeVersion.Major >= MinCDacRuntimeMajorVersion;
184201
}
185202

186203
/// <summary>
187-
/// Create ClrRuntime instance
204+
/// Creates a ClrRuntime with the supplied IXCLRDataProcess.
188205
/// </summary>
189-
private ClrRuntime CreateRuntime()
206+
private ClrRuntime CreateRuntimeFromClrDataProcess()
190207
{
191-
// Prefer the cDAC for the ClrMD data-access path when policy selects it; fall back to the in-box DAC.
192-
// We ignore the dac verification param since it's already set as part of the CLRMD DataTarget creation
193-
// now (it's a global setting to the session).
194-
string dacFilePath = GetCDacFilePath() ?? GetDacFilePath(out _);
195-
if (dacFilePath is not null)
208+
try
196209
{
197-
Trace.TraceInformation($"Creating ClrRuntime #{Id} {dacFilePath}");
198-
try
199-
{
200-
// Ignore the DAC version mismatch that can happen because the clrmd ELF dump reader
201-
// returns 0.0.0.0 for the runtime module that the DAC is matched against.
202-
return _clrRuntime = _clrInfo.CreateRuntime(dacFilePath, ignoreMismatch: true);
203-
}
204-
catch (Exception ex) when
205-
(ex is DllNotFoundException or
206-
FileNotFoundException or
207-
InvalidOperationException or
208-
InvalidDataException or
209-
ClrDiagnosticsException)
210-
{
211-
Trace.TraceError("CreateRuntime FAILED: {0}", ex.ToString());
212-
}
210+
_clrInfo.DataTarget.AddLoadedRuntime(_clrInfo, _clrDataProcess.Interface);
213211
}
214-
else
212+
catch (Exception ex) when
213+
(ex is DllNotFoundException or
214+
FileNotFoundException or
215+
InvalidOperationException or
216+
InvalidDataException or
217+
ClrDiagnosticsException)
215218
{
216-
Trace.TraceError($"Could not find or download matching DAC for this runtime: {RuntimeModule.FileName}");
219+
Trace.TraceError("Register IXCLRDataProcess FAILED: {0}", ex.ToString());
220+
_clrDataProcess.Dispose();
221+
_clrDataProcess = null;
222+
_cdacActivationResult = null;
223+
return null;
224+
}
225+
226+
try
227+
{
228+
Trace.TraceInformation($"Creating ClrRuntime #{Id} from IXCLRDataProcess");
229+
return _clrRuntime = _clrInfo.CreateRuntime();
230+
}
231+
catch (Exception ex) when
232+
(ex is DllNotFoundException or
233+
FileNotFoundException or
234+
InvalidOperationException or
235+
InvalidDataException or
236+
ClrDiagnosticsException)
237+
{
238+
Trace.TraceError("CreateRuntime from registered IXCLRDataProcess FAILED: {0}", ex.ToString());
239+
return null;
217240
}
218-
return null;
219241
}
220242

221243
private string GetLibraryPath(DebugLibraryKind kind)
@@ -232,13 +254,6 @@ private string GetLibraryPath(DebugLibraryKind kind)
232254
{
233255
break;
234256
}
235-
// The cDAC is an analyzer-host artifact shipped inside the diagnostics tool
236-
// (next to sos.dll, matching the host's RID). It is not symbol-store indexed
237-
// by the target runtime, so never attempt to download it.
238-
if (libraryInfo.Kind == DebugLibraryKind.CDac)
239-
{
240-
continue;
241-
}
242257
if (libraryInfo.ArchivedUnder != SymbolProperties.None)
243258
{
244259
libraryPath = DownloadFile(libraryInfo);
@@ -256,24 +271,13 @@ private string GetLibraryPath(DebugLibraryKind kind)
256271
private string GetLocalPath(DebugLibraryInfo libraryInfo)
257272
{
258273
string localFilePath;
259-
if (libraryInfo.Kind == DebugLibraryKind.CDac)
274+
if (!string.IsNullOrEmpty(RuntimeModuleDirectory))
260275
{
261-
// The cDAC ships next to the native sos module. Ask the host asset resolver where it
262-
// is rather than reasoning about layouts here (ClrMD's DebuggingLibraries entry points
263-
// at the managed-assembly base directory, so it is ignored). The shared existence
264-
// check below verifies the path, so the in-box DAC is used when the cDAC isn't bundled.
265-
localFilePath = _hostAssetResolver?.GetCDacPath();
276+
localFilePath = Path.Combine(RuntimeModuleDirectory, Path.GetFileName(libraryInfo.FileName));
266277
}
267278
else
268279
{
269-
if (!string.IsNullOrEmpty(RuntimeModuleDirectory))
270-
{
271-
localFilePath = Path.Combine(RuntimeModuleDirectory, Path.GetFileName(libraryInfo.FileName));
272-
}
273-
else
274-
{
275-
localFilePath = Path.Combine(Path.GetDirectoryName(RuntimeModule.FileName), Path.GetFileName(libraryInfo.FileName));
276-
}
280+
localFilePath = Path.Combine(Path.GetDirectoryName(RuntimeModule.FileName), Path.GetFileName(libraryInfo.FileName));
277281
}
278282
if (localFilePath is null || !File.Exists(localFilePath))
279283
{
@@ -411,11 +415,6 @@ public override string ToString()
411415
string verify = _verifySignature ? "(verify)" : "(don't verify)";
412416
sb.Append($" DAC: {_dacFilePath} {verify}");
413417
}
414-
if (_cdacFilePath is not null)
415-
{
416-
sb.AppendLine();
417-
sb.Append($" CDAC: {_cdacFilePath}");
418-
}
419418
if (_dbiFilePath is not null)
420419
{
421420
sb.AppendLine();
Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,55 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
namespace Microsoft.Diagnostics.DebugServices
4+
using System;
5+
6+
namespace Microsoft.Diagnostics.DebugServices;
7+
8+
/// <summary>
9+
/// Controls cDAC activation and DAC fallback policies.
10+
/// </summary>
11+
public enum CDacLoadPolicy
512
{
613
/// <summary>
7-
/// Controls whether the cDAC is used in place of the in-box DAC.
14+
/// Attempt cDAC activation and allow DAC fallback.
15+
/// </summary>
16+
PreferCDac = 0,
17+
18+
/// <summary>
19+
/// Require cDAC activation without DAC fallback.
20+
/// </summary>
21+
OnlyUseCDac = 1,
22+
23+
/// <summary>
24+
/// Use the DAC without attempting cDAC activation.
825
/// </summary>
9-
public enum CDacLoadPolicy
26+
UseLegacyDac = 2,
27+
28+
}
29+
30+
/// <summary>
31+
/// Evaluates cDAC activation policy.
32+
/// </summary>
33+
public static class CDacPolicy
34+
{
35+
/// <summary>
36+
/// Returns whether cDAC activation should be attempted.
37+
/// </summary>
38+
/// <param name="policy">The requested activation policy.</param>
39+
/// <returns><see langword="true"/> if cDAC activation should be attempted.</returns>
40+
public static bool ShouldTryCDac(CDacLoadPolicy policy)
1041
{
11-
/// <summary>
12-
/// Evaluate policy and fall back. The cDAC is used when the target runtime supports it
13-
/// and a matching cDAC is available next to the diagnostics tool; otherwise the in-box
14-
/// DAC is used.
15-
/// </summary>
16-
Default,
17-
18-
/// <summary>
19-
/// Always use the cDAC. Runtime construction fails if no matching cDAC is available.
20-
/// </summary>
21-
UseCDac,
22-
23-
/// <summary>
24-
/// Always use the in-box DAC. The cDAC is never loaded.
25-
/// </summary>
26-
UseLegacyDac,
42+
if (policy == CDacLoadPolicy.OnlyUseCDac)
43+
{
44+
return true;
45+
}
46+
if (policy == CDacLoadPolicy.UseLegacyDac)
47+
{
48+
return false;
49+
}
50+
51+
// These variables select the in-box DAC's cDAC integration.
52+
return Environment.GetEnvironmentVariable("DOTNET_ENABLE_CDAC") != "1"
53+
&& Environment.GetEnvironmentVariable("COMPlus_ENABLE_CDAC") != "1";
2754
}
2855
}

0 commit comments

Comments
 (0)