Skip to content

Add cDac runtime enumeration support #1311

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 1, 2025
Merged
Show file tree
Hide file tree
Changes from 2 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
45 changes: 25 additions & 20 deletions src/Microsoft.Diagnostics.Runtime/ClrInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ internal ClrInfo(DataTarget dt, ModuleInfo module, Version clrVersion, IClrInfoP
/// </summary>
public ModuleInfo ModuleInfo { get; }

/// <summary>
/// The CDAC contract export address
/// </summary>
public ulong ContractDescriptor { get; set; }

/// <summary>
/// The timestamp under which this CLR is is archived (0 if this module is indexed under
/// a BuildId instead). Note that this may be a different value from ModuleInfo.IndexTimeStamp.
Expand Down Expand Up @@ -88,49 +93,49 @@ internal ClrInfo(DataTarget dt, ModuleInfo module, Version clrVersion, IClrInfoP
/// <returns>A version string for this CLR.</returns>
public override string ToString() => Version.ToString();

/// <summary>
/// Creates a runtime by searching for the correct dac to load.
/// </summary>
/// <returns>The runtime associated with this CLR.</returns>
public ClrRuntime CreateRuntime() => CreateRuntimeWorker(null, ignoreMismatch: false, verifySignature: false);

/// <summary>
/// Creates a runtime from the given DAC file on disk. This is equivalent to
/// CreateRuntime(dacPath, ignoreMismatch: false).
/// </summary>
/// <param name="dacPath">A full path to the matching DAC dll for this process.</param>
/// <returns>The runtime associated with this CLR.</returns>
public ClrRuntime CreateRuntime(string dacPath)
{
if (string.IsNullOrEmpty(dacPath))
throw new ArgumentNullException(nameof(dacPath));

if (!File.Exists(dacPath))
throw new FileNotFoundException(dacPath);

return CreateRuntimeWorker(dacPath, ignoreMismatch: false);
}
public ClrRuntime CreateRuntime(string dacPath) => CreateRuntime(dacPath, ignoreMismatch: false, verifySignature: false);

/// <summary>
/// Creates a runtime from the given DAC file on disk.
/// </summary>
/// <param name="dacPath">A full path to the matching DAC dll for this process.</param>
/// <param name="ignoreMismatch">Whether or not to ignore mismatches between. </param>
/// <returns>The runtime associated with this CLR.</returns>
public ClrRuntime CreateRuntime(string dacPath, bool ignoreMismatch)
public ClrRuntime CreateRuntime(string dacPath, bool ignoreMismatch) => CreateRuntime(dacPath, ignoreMismatch, verifySignature: false);

/// <summary>
/// Creates a runtime from the given DAC file on disk.
/// </summary>
/// <param name="dacPath">A full path to the matching DAC dll for this process.</param>
/// <param name="ignoreMismatch">Whether or not to ignore mismatches between.</param>
/// <param name="verifySignature">If true, verify the DAC signature</param>
/// <returns>The runtime associated with this CLR.</returns>
public ClrRuntime CreateRuntime(string dacPath, bool ignoreMismatch, bool verifySignature)
{
if (string.IsNullOrEmpty(dacPath))
throw new ArgumentNullException(nameof(dacPath));

if (!File.Exists(dacPath))
throw new FileNotFoundException(dacPath);

return CreateRuntimeWorker(dacPath, ignoreMismatch);
return CreateRuntimeWorker(dacPath, ignoreMismatch, verifySignature);
}

/// <summary>
/// Creates a runtime by searching for the correct dac to load.
/// </summary>
/// <returns>The runtime associated with this CLR.</returns>
public ClrRuntime CreateRuntime() => CreateRuntimeWorker(null, ignoreMismatch: false);

private ClrRuntime CreateRuntimeWorker(string? dacPath, bool ignoreMismatch)
private ClrRuntime CreateRuntimeWorker(string? dacPath, bool ignoreMismatch, bool verifySignature)
{
IServiceProvider services = ClrInfoProvider.GetDacServices(this, dacPath, ignoreMismatch);
IServiceProvider services = ClrInfoProvider.GetDacServices(this, dacPath, ignoreMismatch, verifySignature);
return new ClrRuntime(this, services);
}

Expand Down
5 changes: 0 additions & 5 deletions src/Microsoft.Diagnostics.Runtime/CustomDataTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,6 @@ public class CustomDataTarget : IDisposable
/// </summary>
public bool ForceCompleteRuntimeEnumeration { get; set; }

/// <summary>
/// If true, enforces the proper DAC certificate signing
/// </summary>
public bool DacSignatureVerificationEnabled { get; set; }

/// <summary>
/// The TokenCredential to use for any Azure based symbol servers (set to null if not using one).
/// </summary>
Expand Down
22 changes: 5 additions & 17 deletions src/Microsoft.Diagnostics.Runtime/DacInterface/DacDataTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ internal sealed unsafe class DacDataTarget
internal static readonly Guid IID_IDacDataTarget = new("3E11CCEE-D08B-43e5-AF01-32717A64DA03");
internal static readonly Guid IID_IMetadataLocator = new("aa8fa804-bc05-4642-b2c5-c353ed22fc63");
internal static readonly Guid IID_ICLRRuntimeLocator = new("b760bf44-9377-4597-8be7-58083bdc5146");
internal static readonly Guid IID_ICLRContractLocator = new("17d5b8c6-34a9-407f-af4f-a930201d4e02");

public const ulong MagicCallbackConstant = 0x43;

Expand All @@ -27,11 +28,14 @@ internal sealed unsafe class DacDataTarget

public ulong RuntimeBaseAddress { get; }

public DacDataTarget(DataTarget dataTarget, ulong runtimeBaseAddress = 0)
public ulong ContractDescriptor { get; }

public DacDataTarget(DataTarget dataTarget, ulong runtimeBaseAddress = 0, ulong contractDescriptor = 0)
{
_dataTarget = dataTarget;
_dataReader = _dataTarget.DataReader;
RuntimeBaseAddress = runtimeBaseAddress;
ContractDescriptor = contractDescriptor;
}

public void EnterMagicCallbackContext() => Interlocked.Increment(ref _callbackContext);
Expand Down Expand Up @@ -204,21 +208,5 @@ public int GetMetadata(

return HResult.S_OK;
}

private delegate int GetMetadataDelegate(IntPtr self, [In][MarshalAs(UnmanagedType.LPWStr)] string fileName, int imageTimestamp, int imageSize,
IntPtr mvid, uint mdRva, uint flags, uint bufferSize, IntPtr buffer, int* dataSize);
private delegate int GetMachineTypeDelegate(IntPtr self, out IMAGE_FILE_MACHINE machineType);
private delegate int GetPointerSizeDelegate(IntPtr self, out int pointerSize);
private delegate int GetImageBaseDelegate(IntPtr self, [In][MarshalAs(UnmanagedType.LPWStr)] string imagePath, out ulong baseAddress);
private delegate int ReadVirtualDelegate(IntPtr self, ClrDataAddress address, IntPtr buffer, int bytesRequested, out int bytesRead);
private delegate int WriteVirtualDelegate(IntPtr self, ClrDataAddress address, IntPtr buffer, uint bytesRequested, out uint bytesWritten);
private delegate int GetTLSValueDelegate(IntPtr self, uint threadID, uint index, out ulong value);
private delegate int SetTLSValueDelegate(IntPtr self, uint threadID, uint index, ClrDataAddress value);
private delegate int GetCurrentThreadIDDelegate(IntPtr self, out uint threadID);
private delegate int GetThreadContextDelegate(IntPtr self, uint threadID, uint contextFlags, int contextSize, IntPtr context);
private delegate int SetThreadContextDelegate(IntPtr self, uint threadID, uint contextSize, IntPtr context);
private delegate int RequestDelegate(IntPtr self, uint reqCode, uint inBufferSize, IntPtr inBuffer, IntPtr outBufferSize, out IntPtr outBuffer);

private delegate int GetRuntimeBaseDelegate([In] IntPtr self, [Out] out ulong address);
}
}
37 changes: 35 additions & 2 deletions src/Microsoft.Diagnostics.Runtime/DacInterface/DacDataTargetCOM.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public static IntPtr CreateIDacDataTarget(DacDataTarget dacData)
{
GetIUnknownImpl(out IntPtr qi, out IntPtr addRef, out IntPtr release);

ComInterfaceEntry* wrappers = (ComInterfaceEntry*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(DacDataTargetCOM), sizeof(ComInterfaceEntry) * 3);
ComInterfaceEntry* wrappers = (ComInterfaceEntry*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(DacDataTargetCOM), sizeof(ComInterfaceEntry) * 4);
wrappers[0].IID = DacDataTarget.IID_IDacDataTarget;
wrappers[0].Vtable = IDacDataTargetVtbl.Create(qi, addRef, release);

Expand All @@ -46,6 +46,9 @@ public static IntPtr CreateIDacDataTarget(DacDataTarget dacData)
wrappers[2].IID = DacDataTarget.IID_ICLRRuntimeLocator;
wrappers[2].Vtable = ICLRRuntimeLocatorVtbl.Create(qi, addRef, release);

wrappers[3].IID = DacDataTarget.IID_ICLRContractLocator;
wrappers[3].Vtable = ICLRContractLocatorVtbl.Create(qi, addRef, release);

return wrappers;
}

Expand Down Expand Up @@ -222,9 +225,39 @@ public static IntPtr Create(IntPtr qi, IntPtr addRef, IntPtr release)
[UnmanagedCallersOnly]
private static int GetRuntimeBase(IntPtr self, ulong* address)
{
if (address is null)
{
return HResult.E_INVALIDARG;
}
DacDataTarget dacDataTarget = ComInterfaceDispatch.GetInstance<DacDataTarget>((ComInterfaceDispatch*)self);
*address = dacDataTarget.RuntimeBaseAddress;
return dacDataTarget.RuntimeBaseAddress != 0 ? HResult.S_OK : HResult.E_FAIL;
return *address != 0 ? HResult.S_OK : HResult.E_FAIL;
}
}

private static unsafe class ICLRContractLocatorVtbl
{
public static IntPtr Create(IntPtr qi, IntPtr addRef, IntPtr release)
{
IntPtr* vtblRaw = (IntPtr*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(ICLRContractLocatorVtbl), IntPtr.Size * 4);
vtblRaw[0] = qi;
vtblRaw[1] = addRef;
vtblRaw[2] = release;
vtblRaw[3] = (IntPtr)(delegate* unmanaged<IntPtr, ulong*, int>)&GetContractDescriptor;

return (IntPtr)vtblRaw;
}

[UnmanagedCallersOnly]
private static int GetContractDescriptor(IntPtr self, ulong* address)
{
if (address is null)
{
return HResult.E_INVALIDARG;
}
DacDataTarget dacDataTarget = ComInterfaceDispatch.GetInstance<DacDataTarget>((ComInterfaceDispatch*)self);
*address = dacDataTarget.ContractDescriptor;
return *address != 0 ? HResult.S_OK : HResult.E_FAIL;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal sealed unsafe class LegacyDacDataTargetWrapper : COMCallableIUnknown

public IntPtr IDacDataTarget { get; }

public LegacyDacDataTargetWrapper(DacDataTarget dacDataTarget, bool implementRuntimeLocator)
public LegacyDacDataTargetWrapper(DacDataTarget dacDataTarget)
{
_dacDataTarget = dacDataTarget;

Expand All @@ -37,12 +37,13 @@ public LegacyDacDataTargetWrapper(DacDataTarget dacDataTarget, bool implementRun
builder.AddMethod(new GetMetadataDelegate(GetMetadata));
builder.Complete();

if (implementRuntimeLocator)
{
builder = AddInterface(DacDataTarget.IID_ICLRRuntimeLocator, false);
builder.AddMethod(new GetRuntimeBaseDelegate(GetRuntimeBase));
builder.Complete();
}
builder = AddInterface(DacDataTarget.IID_ICLRRuntimeLocator, false);
builder.AddMethod(new GetRuntimeBaseDelegate(GetRuntimeBase));
builder.Complete();

builder = AddInterface(DacDataTarget.IID_ICLRContractLocator, false);
builder.AddMethod(new GetRuntimeBaseDelegate(GetContractDescriptor));
builder.Complete();
}

private int GetMachineType(IntPtr self, out IMAGE_FILE_MACHINE machineType)
Expand Down Expand Up @@ -106,8 +107,14 @@ private int GetRuntimeBase(IntPtr _, out ulong address)
return address == 0 ? HResult.E_FAIL : HResult.S_OK;
}

private int GetContractDescriptor(IntPtr _, out ulong address)
{
address = _dacDataTarget.ContractDescriptor;
return address == 0 ? HResult.E_FAIL : HResult.S_OK;
}

private delegate int GetMetadataDelegate(IntPtr self, [In][MarshalAs(UnmanagedType.LPWStr)] string fileName, int imageTimestamp, int imageSize,
IntPtr mvid, uint mdRva, uint flags, uint bufferSize, IntPtr buffer, int* dataSize);
IntPtr mvid, uint mdRva, uint flags, uint bufferSize, IntPtr buffer, int* dataSize);
private delegate int GetMachineTypeDelegate(IntPtr self, out IMAGE_FILE_MACHINE machineType);
private delegate int GetPointerSizeDelegate(IntPtr self, out int pointerSize);
private delegate int GetImageBaseDelegate(IntPtr self, [In][MarshalAs(UnmanagedType.LPWStr)] string imagePath, out ulong baseAddress);
Expand All @@ -121,5 +128,7 @@ private delegate int GetMetadataDelegate(IntPtr self, [In][MarshalAs(UnmanagedTy
private delegate int RequestDelegate(IntPtr self, uint reqCode, uint inBufferSize, IntPtr inBuffer, IntPtr outBufferSize, ref IntPtr outBuffer);

private delegate int GetRuntimeBaseDelegate([In] IntPtr self, [Out] out ulong address);

private delegate int GetContractLocatorDelegate([In] IntPtr self, [Out] out ulong address);
}
}
8 changes: 4 additions & 4 deletions src/Microsoft.Diagnostics.Runtime/DacLibrary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ internal sealed class DacLibrary : IDisposable

public ClrDataProcess CreateClrDataProcess() => new(this, _clrDataProcess);

public unsafe DacLibrary(DataTarget dataTarget, string dacPath, ulong runtimeBaseAddress)
public unsafe DacLibrary(DataTarget dataTarget, string dacPath, ulong runtimeBaseAddress, ulong contractDescriptor, bool verifySignature)
{
if (dataTarget is null)
throw new ArgumentNullException(nameof(dataTarget));
Expand All @@ -32,7 +32,7 @@ public unsafe DacLibrary(DataTarget dataTarget, string dacPath, ulong runtimeBas
IntPtr dacLibrary;
try
{
if (dataTarget.CustomDataTarget.DacSignatureVerificationEnabled)
if (verifySignature)
{
if (!AuthenticodeUtil.VerifyDacDll(dacPath, out fileLock))
{
Expand Down Expand Up @@ -74,7 +74,7 @@ public unsafe DacLibrary(DataTarget dataTarget, string dacPath, ulong runtimeBas
if (addr == IntPtr.Zero)
throw new ClrDiagnosticsException("Failed to obtain Dac CLRDataCreateInstance");

DacDataTarget = new DacDataTarget(dataTarget, runtimeBaseAddress);
DacDataTarget = new DacDataTarget(dataTarget, runtimeBaseAddress, contractDescriptor);

delegate* unmanaged[Stdcall]<in Guid, IntPtr, out IntPtr, int> func = (delegate* unmanaged[Stdcall]<in Guid, IntPtr, out IntPtr, int>)addr;
Guid guid = new("5c552ab6-fc09-4cb3-8e36-22fa03c798b7");
Expand All @@ -84,7 +84,7 @@ public unsafe DacLibrary(DataTarget dataTarget, string dacPath, ulong runtimeBas
int res = func(guid, iDacDataTarget, out nint iUnk);
Marshal.Release(iDacDataTarget);
#else
LegacyDacDataTargetWrapper wrapper = new(DacDataTarget, DacDataTarget.RuntimeBaseAddress != 0);
LegacyDacDataTargetWrapper wrapper = new(DacDataTarget);
int res = func(guid, wrapper.IDacDataTarget, out nint iUnk);
GC.KeepAlive(wrapper);
#endif
Expand Down
1 change: 1 addition & 0 deletions src/Microsoft.Diagnostics.Runtime/DebugLibraryKind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ public enum DebugLibraryKind
{
Dac,
Dbi,
CDac
}
}
3 changes: 2 additions & 1 deletion src/Microsoft.Diagnostics.Runtime/IClrInfoProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ public interface IClrInfoProvider
/// <param name="clrInfo">A ClrInfo previously returned by this same instance.</param>
/// <param name="providedPath">The path provided to DataTarget.CreateRuntime.</param>
/// <param name="ignorePathMismatch">The ignore mismatch parameter provided to DataTarget.CreateRuntime.</param>
/// <param name="verifySignature">If true, verify the DAC signature</param>
/// <returns>An <see cref="IServiceProvider"/> interface to use with the specified clr runtime that provides
/// <see cref="IAbstractRuntime"/> related services.</returns>
IServiceProvider GetDacServices(ClrInfo clrInfo, string? providedPath, bool ignorePathMismatch);
IServiceProvider GetDacServices(ClrInfo clrInfo, string? providedPath, bool ignorePathMismatch, bool verifySignature);
}
}
Loading