// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using System.Threading;
using Microsoft.Diagnostics.DataContractReader.Legacy;
namespace Microsoft.Diagnostics.DataContractReader;
/// <summary>
/// Provides native entrypoints and debugger target construction for the universal cDAC.
/// </summary>
public static class Entrypoints
{
private sealed class CdacHandle
{
internal Target Target { get; }
internal Lock ApiLock { get; }
internal CdacHandle(Target target, Lock apiLock)
{
Target = target;
ApiLock = apiLock;
}
}
private const string CDAC = "cdac_reader_";
// Native CONTEXT and DT_CONTEXT declarations require 16-byte alignment.
private const nuint ContextAlignment = 16;
[UnmanagedCallersOnly(EntryPoint = $"{CDAC}init")]
private static unsafe int Init(
ulong descriptor,
delegate* unmanaged<ulong, byte*, uint, void*, int> readFromTarget,
delegate* unmanaged<ulong, byte*, uint, void*, int> writeToTarget,
delegate* unmanaged<uint, uint, uint, byte*, void*, int> readThreadContext,
delegate* unmanaged<uint, uint, byte*, void*, int> writeThreadContext,
delegate* unmanaged<uint, ulong*, void*, int> allocVirtual,
void* delegateContext,
IntPtr* handle)
{
try
{
if (handle == null)
return HResults.E_INVALIDARG;
*handle = IntPtr.Zero;
// Build the allocVirtual delegate if the caller provided a callback
ContractDescriptorTarget.AllocVirtualDelegate allocDelegate = (ulong size, out ulong allocatedAddress) =>
{
allocatedAddress = 0;
return HResults.E_NOTIMPL;
};
if (allocVirtual != null)
{
allocDelegate = (ulong size, out ulong allocatedAddress) =>
{
if (size > uint.MaxValue)
{
allocatedAddress = 0;
return HResults.E_INVALIDARG;
}
fixed (ulong* addrPtr = &allocatedAddress)
{
return allocVirtual((uint)size, addrPtr, delegateContext);
}
};
}
// Build the setThreadContext delegate if the caller provided a callback
ContractDescriptorTarget.SetTargetThreadContextDelegate setThreadContextDelegate =
(uint threadId, ReadOnlySpan<byte> context) => HResults.E_NOTIMPL;
if (writeThreadContext != null)
{
setThreadContextDelegate = (uint threadId, ReadOnlySpan<byte> context) =>
{
fixed (byte* contextPtr = context)
{
if (((nuint)contextPtr & (ContextAlignment - 1)) == 0)
{
return writeThreadContext(threadId, (uint)context.Length, contextPtr, delegateContext);
}
byte* alignedBuffer = (byte*)NativeMemory.AlignedAlloc((nuint)context.Length, ContextAlignment);
try
{
context.CopyTo(new Span<byte>(alignedBuffer, context.Length));
return writeThreadContext(threadId, (uint)context.Length, alignedBuffer, delegateContext);
}
finally
{
NativeMemory.AlignedFree(alignedBuffer);
}
}
};
}
ContractDescriptorTarget target = ContractDescriptorTarget.Create(
descriptor,
(address, buffer) =>
{
fixed (byte* bufferPtr = buffer)
{
return readFromTarget(address, bufferPtr, (uint)buffer.Length, delegateContext);
}
},
(address, buffer) =>
{
fixed (byte* bufferPtr = buffer)
{
return writeToTarget(address, bufferPtr, (uint)buffer.Length, delegateContext);
}
},
(threadId, contextFlags, buffer) =>
{
fixed (byte* bufferPtr = buffer)
{
if (((nuint)bufferPtr & (ContextAlignment - 1)) == 0)
{
return readThreadContext(threadId, contextFlags, (uint)buffer.Length, bufferPtr, delegateContext);
}
byte* alignedBuffer = (byte*)NativeMemory.AlignedAlloc((nuint)buffer.Length, ContextAlignment);
NativeMemory.Clear(alignedBuffer, (nuint)buffer.Length);
try
{
int hr = readThreadContext(threadId, contextFlags, (uint)buffer.Length, alignedBuffer, delegateContext);
if (hr >= 0)
{
new ReadOnlySpan<byte>(alignedBuffer, buffer.Length).CopyTo(buffer);
}
return hr;
}
finally
{
NativeMemory.AlignedFree(alignedBuffer);
}
}
},
setThreadContextDelegate,
allocDelegate,
[Contracts.CoreCLRContracts.Register]);
GCHandle gcHandle = GCHandle.Alloc(new CdacHandle(target, new Lock()));
*handle = GCHandle.ToIntPtr(gcHandle);
return 0;
}
catch (Exception ex)
{
int hr = ex.HResult;
return hr < 0 ? hr : HResults.E_FAIL;
}
}
[UnmanagedCallersOnly(EntryPoint = $"{CDAC}free")]
private static unsafe int Free(IntPtr handle)
{
try
{
GCHandle h = GCHandle.FromIntPtr(handle);
h.Free();
return 0;
}
catch (Exception ex)
{
int hr = ex.HResult;
return hr < 0 ? hr : HResults.E_FAIL;
}
}
/// <summary>
/// Create the SOS-DAC interface implementation.
/// </summary>
/// <param name="handle">Handle crated via cdac initialization</param>
/// <param name="legacyImplPtr">Optional. Pointer to legacy implementation of ISOSDacInterface*</param>
/// <param name="obj"><c>IUnknown</c> pointer that can be queried for ISOSDacInterface*</param>
/// <returns></returns>
[UnmanagedCallersOnly(EntryPoint = $"{CDAC}create_sos_interface")]
private static unsafe int CreateSosInterface(IntPtr handle, IntPtr legacyImplPtr, nint* obj)
{
try
{
if (obj == null)
return HResults.E_INVALIDARG;
*obj = IntPtr.Zero;
CdacHandle? cdacHandle = GCHandle.FromIntPtr(handle).Target as CdacHandle;
if (cdacHandle is null)
return HResults.E_INVALIDARG;
Contracts.CoreCLRContracts.ValidateForDataAccess(cdacHandle.Target, cdacHandle.ApiLock);
object? legacyImpl = legacyImplPtr != IntPtr.Zero
? ComInterfaceMarshaller<ISOSDacInterface>.ConvertToManaged((void*)legacyImplPtr)
: null;
Legacy.SOSDacImpl impl = new(cdacHandle.Target, legacyImpl, cdacHandle.ApiLock);
nint ptr = (nint)ComInterfaceMarshaller<ISOSDacInterface>.ConvertToUnmanaged(impl);
*obj = ptr;
return 0;
}
catch (Exception ex)
{
if (obj != null)
*obj = IntPtr.Zero;
int hr = ex.HResult;
return hr < 0 ? hr : HResults.E_FAIL;
}
}
/// <summary>
/// Create the DacDbi interface implementation.
/// </summary>
/// <param name="handle">Handle created via cdac initialization</param>
/// <param name="legacyImplPtr">Optional. Pointer to legacy implementation of IDacDbiInterface</param>
/// <param name="obj"><c>IUnknown</c> pointer that can be queried for IDacDbiInterface</param>
[UnmanagedCallersOnly(EntryPoint = $"{CDAC}create_dacdbi_interface")]
private static unsafe int CreateDacDbiInterface(IntPtr handle, IntPtr legacyImplPtr, nint* obj)
{
object? legacyObj = null;
try
{
if (obj == null)
return HResults.E_INVALIDARG;
if (handle == IntPtr.Zero)
{
*obj = IntPtr.Zero;
return HResults.E_NOTIMPL;
}
CdacHandle? cdacHandle = GCHandle.FromIntPtr(handle).Target as CdacHandle;
if (cdacHandle is null)
{
*obj = IntPtr.Zero;
return HResults.E_INVALIDARG;
}
if (legacyImplPtr != IntPtr.Zero)
{
legacyObj = UniqueComInterfaceMarshaller<IDacDbiInterface>.ConvertToManaged((void*)legacyImplPtr);
}
Legacy.DacDbiImpl impl = new(cdacHandle.Target, legacyObj, cdacHandle.ApiLock);
*obj = (nint)ComInterfaceMarshaller<IDacDbiInterface>.ConvertToUnmanaged(impl);
legacyObj = null;
return HResults.S_OK;
}
catch (Exception ex)
{
if (obj != null)
*obj = IntPtr.Zero;
int hr = ex.HResult;
return hr < 0 ? hr : HResults.E_FAIL;
}
finally
{
(legacyObj as ComObject)?.FinalRelease();
}
}
[UnmanagedCallersOnly(EntryPoint = "CLRDataCreateInstanceWithFallback")]
private static unsafe int CLRDataCreateInstanceWithFallback(Guid* pIID, IntPtr /*ICLRDataTarget*/ pLegacyTarget, IntPtr pLegacyImpl, void** iface)
{
return CreateInstance(pIID, pLegacyTarget, pLegacyImpl, iface);
}
[UnmanagedCallersOnly(EntryPoint = "DacDbiInterfaceInstance")]
private static unsafe int DacDbiInterfaceInstance(
IntPtr /*ICorDebugDataTarget*/ pTarget,
ulong runtimeBase,
ulong contractDescriptorAddress,
IntPtr /*IDacDbiInterface::IAllocator*/ pAllocator,
IntPtr /*IDacDbiInterface::IMetaDataLookup*/ pMetaDataLookup,
void** iface,
void** legacyDac)
{
// The allocator and metadata lookup pointers are not used by the managed implementation,
// so, unlike the native DAC export, they are not required.
if (pTarget == IntPtr.Zero
|| runtimeBase == 0
|| contractDescriptorAddress == 0
|| iface == null
|| legacyDac == null)
{
return HResults.E_INVALIDARG;
}
*iface = null;
*legacyDac = null;
ComObject? dataTargetComObject = null;
try
{
ICorDebugDataTarget dataTarget =
UniqueComInterfaceMarshaller<ICorDebugDataTarget>.ConvertToManaged((void*)pTarget)!;
dataTargetComObject = (ComObject)(object)dataTarget;
ContractDescriptorTarget target = CreateTargetFromCorDebugDataTarget(dataTarget, contractDescriptorAddress, runtimeBase);
Legacy.DacDbiImpl impl = new(target, legacyObj: null, apiLock: new Lock(), dataTargetComObject: dataTargetComObject);
*iface = ComInterfaceMarshaller<IDacDbiInterface>.ConvertToUnmanaged(impl);
dataTargetComObject = null;
return HResults.S_OK;
}
catch (Exception ex)
{
if (iface != null)
*iface = null;
if (legacyDac != null)
*legacyDac = null;
int hr = ex.HResult;
return hr < 0 ? hr : HResults.E_FAIL;
}
finally
{
dataTargetComObject?.FinalRelease();
}
}
// Same export name and signature as DAC CLRDataCreateInstance in daccess.cpp
[UnmanagedCallersOnly(EntryPoint = "CLRDataCreateInstance")]
private static unsafe int CLRDataCreateInstance(Guid* pIID, IntPtr /*ICLRDataTarget*/ pLegacyTarget, void** iface)
{
return CreateInstance(pIID, pLegacyTarget, IntPtr.Zero, iface);
}
private static unsafe int CreateInstance(Guid* pIID, IntPtr pDataTarget, IntPtr legacyImplPtr, void** iface, TargetPointer contractAddress = default)
{
if (iface == null)
return HResults.E_INVALIDARG;
*iface = null;
if (pIID == null || pDataTarget == IntPtr.Zero)
return HResults.E_INVALIDARG;
try
{
ICLRDataTarget dataTarget = ComInterfaceMarshaller<ICLRDataTarget>.ConvertToManaged((void*)pDataTarget)!;
if (contractAddress == TargetPointer.Null && !EntrypointHelpers.TryGetContractDescriptorAddress(dataTarget, out contractAddress))
{
return CdacHResults.CDAC_E_DESCRIPTOR_NOT_FOUND;
}
ulong runtimeImageBase = 0;
if (dataTarget is ICLRRuntimeLocator runtimeLocator && runtimeLocator.GetRuntimeBase(&runtimeImageBase) != HResults.S_OK)
runtimeImageBase = 0;
return EntrypointHelpers.CreateInstance(pIID, pDataTarget, legacyImplPtr, iface, contractAddress, runtimeImageBase);
}
catch (Exception ex)
{
int hr = ex.HResult;
return hr < 0 ? hr : HResults.E_FAIL;
}
}
// Creates a cDAC data-access instance from an explicit contract descriptor address,
// so the data target does not need to implement ICLRContractLocator.
[UnmanagedCallersOnly(EntryPoint = "DbgShimCreateInstanceFromContractDescriptor")]
private static unsafe int DbgShimCreateInstanceFromContractDescriptor(Guid* pIID, IntPtr /*ICLRDataTarget*/ pLegacyTarget, ulong contractDescriptorAddr, void** iface)
{
if (iface != null)
*iface = null;
if (contractDescriptorAddr == 0)
return HResults.E_INVALIDARG;
return CreateInstance(pIID, pLegacyTarget, IntPtr.Zero, iface, contractDescriptorAddr);
}
/// <summary>
/// Creates a contract descriptor target backed by a debugger data target.
/// </summary>
/// <param name="targetObject">The object implementing <see cref="ICorDebugDataTarget"/>.</param>
/// <param name="contractAddress">The target contract descriptor address.</param>
/// <param name="runtimeBase">The runtime image base address, or zero when unavailable.</param>
/// <returns>A target registered with the CoreCLR contracts.</returns>
/// <exception cref="ArgumentException"><paramref name="targetObject"/> does not implement <see cref="ICorDebugDataTarget"/>.</exception>
public static unsafe ContractDescriptorTarget CreateTargetFromCorDebugDataTarget(object targetObject, ulong contractAddress, ulong runtimeBase)
{
ICorDebugDataTarget dataTarget = targetObject as ICorDebugDataTarget ?? throw new ArgumentException(
$"Data target does not implement {nameof(ICorDebugDataTarget)}", nameof(targetObject));
ICorDebugMutableDataTarget? mutableDataTarget = targetObject as ICorDebugMutableDataTarget;
return ContractDescriptorTarget.Create(
contractAddress,
(address, buffer) =>
{
fixed (byte* bufferPtr = buffer)
{
uint bytesRead;
int hr = dataTarget.ReadVirtual(address, bufferPtr, (uint)buffer.Length, &bytesRead);
return hr >= 0 && bytesRead != (uint)buffer.Length
? CorDbgHResults.CORDBG_E_READVIRTUAL_FAILURE
: hr;
}
},
(address, buffer) =>
{
if (mutableDataTarget is null)
return CorDbgHResults.CORDBG_E_TARGET_READONLY;
fixed (byte* bufferPtr = buffer)
{
return mutableDataTarget.WriteVirtual(address, bufferPtr, (uint)buffer.Length);
}
},
(threadId, contextFlags, bufferToFill) =>
{
fixed (byte* bufferPtr = bufferToFill)
{
if (((nuint)bufferPtr & (ContextAlignment - 1)) == 0)
{
return dataTarget.GetThreadContext(threadId, contextFlags, (uint)bufferToFill.Length, bufferPtr);
}
byte* alignedBuffer = (byte*)NativeMemory.AlignedAlloc((nuint)bufferToFill.Length, ContextAlignment);
NativeMemory.Clear(alignedBuffer, (nuint)bufferToFill.Length);
try
{
int hr = dataTarget.GetThreadContext(
threadId,
contextFlags,
(uint)bufferToFill.Length,
alignedBuffer);
if (hr >= 0)
{
new ReadOnlySpan<byte>(alignedBuffer, bufferToFill.Length).CopyTo(bufferToFill);
}
return hr;
}
finally
{
NativeMemory.AlignedFree(alignedBuffer);
}
}
},
(threadId, context) =>
{
if (mutableDataTarget is null)
return CorDbgHResults.CORDBG_E_TARGET_READONLY;
fixed (byte* contextPtr = context)
{
if (((nuint)contextPtr & (ContextAlignment - 1)) == 0)
{
return mutableDataTarget.SetThreadContext(threadId, (uint)context.Length, contextPtr);
}
byte* alignedBuffer = (byte*)NativeMemory.AlignedAlloc((nuint)context.Length, ContextAlignment);
try
{
context.CopyTo(new Span<byte>(alignedBuffer, context.Length));
return mutableDataTarget.SetThreadContext(threadId, (uint)context.Length, alignedBuffer);
}
finally
{
NativeMemory.AlignedFree(alignedBuffer);
}
}
},
(ulong size, out ulong allocatedAddress) =>
{
allocatedAddress = 0;
return HResults.E_NOTIMPL;
},
[Contracts.CoreCLRContracts.Register],
runtimeBase);
}
}