// 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 Microsoft.Diagnostics.DataContractReader.Legacy;
namespace Microsoft.Diagnostics.DataContractReader;
internal static class Entrypoints
{
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(target);
*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;
Target? target = GCHandle.FromIntPtr(handle).Target as Target;
if (target == null)
return HResults.E_INVALIDARG;
object? legacyImpl = legacyImplPtr != IntPtr.Zero
? ComInterfaceMarshaller<ISOSDacInterface>.ConvertToManaged((void*)legacyImplPtr)
: null;
// Without a legacy implementation to absorb individually-unimplemented APIs, validate
// the complete data-access contract set before publishing the interface.
if (legacyImpl is null)
Contracts.CoreCLRContracts.ValidateForDataAccess(target);
Legacy.SOSDacImpl impl = new(target, legacyImpl);
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;
}
Target? target = GCHandle.FromIntPtr(handle).Target as Target;
if (target is null)
{
*obj = IntPtr.Zero;
return HResults.E_INVALIDARG;
}
if (legacyImplPtr != IntPtr.Zero)
{
legacyObj = UniqueComInterfaceMarshaller<IDacDbiInterface>.ConvertToManaged((void*)legacyImplPtr);
}
Legacy.DacDbiImpl impl = new(target, legacyObj);
*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 CLRDataCreateInstanceImpl(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);
Legacy.DacDbiImpl impl = new(target, legacyObj: null, 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 CLRDataCreateInstanceImpl(pIID, pLegacyTarget, IntPtr.Zero, iface);
}
// 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 (pLegacyTarget == IntPtr.Zero || contractDescriptorAddr == 0 || iface == null)
return HResults.E_INVALIDARG;
*iface = null;
try
{
object legacyTarget = ComInterfaceMarshaller<ICLRDataTarget>.ConvertToManaged((void*)pLegacyTarget)!;
return CreateInstanceFromContractDescriptorCore(pIID, legacyTarget, contractDescriptorAddr, legacyImpl: null, iface);
}
catch (Exception ex)
{
int hr = ex.HResult;
return hr < 0 ? hr : HResults.E_FAIL;
}
}
private static unsafe int CLRDataCreateInstanceImpl(Guid* pIID, IntPtr /*ICLRDataTarget*/ pLegacyTarget, IntPtr pLegacyImpl, void** iface)
{
if (pLegacyTarget == IntPtr.Zero || iface == null)
return HResults.E_INVALIDARG;
*iface = null;
try
{
return CLRDataCreateInstanceCore(pIID, pLegacyTarget, pLegacyImpl, iface);
}
catch (Exception ex)
{
int hr = ex.HResult;
return hr < 0 ? hr : HResults.E_FAIL;
}
}
private static unsafe int CLRDataCreateInstanceCore(Guid* pIID, IntPtr /*ICLRDataTarget*/ pLegacyTarget, IntPtr pLegacyImpl, void** iface)
{
object legacyTarget = ComInterfaceMarshaller<ICLRDataTarget>.ConvertToManaged((void*)pLegacyTarget)!;
object? legacyImpl = pLegacyImpl != IntPtr.Zero ?
ComInterfaceMarshaller<ISOSDacInterface>.ConvertToManaged((void*)pLegacyImpl) : null;
ICLRContractLocator contractLocator = legacyTarget as ICLRContractLocator ?? throw new ArgumentException(
$"{nameof(pLegacyTarget)} does not implement {nameof(ICLRContractLocator)}", nameof(pLegacyTarget));
ulong contractAddress;
int hr = contractLocator.GetContractDescriptor(&contractAddress);
if (hr != 0)
{
throw new InvalidOperationException(
$"{nameof(ICLRContractLocator)} failed to fetch the contract descriptor with HRESULT: 0x{hr:x}.")
{
HResult = CdacHResults.CDAC_E_DESCRIPTOR_NOT_FOUND
};
}
return CreateInstanceFromContractDescriptorCore(pIID, legacyTarget, contractAddress, legacyImpl, iface);
}
private static unsafe int CreateInstanceFromContractDescriptorCore(Guid* pIID, object legacyTarget, ulong contractAddress, object? legacyImpl, void** iface)
{
ICLRDataTarget dataTarget = legacyTarget as ICLRDataTarget ?? throw new ArgumentException(
$"Data target does not implement {nameof(ICLRDataTarget)}", nameof(legacyTarget));
// Try to get ICLRDataTarget2 for memory allocation support (optional)
ICLRDataTarget2? dataTarget2 = legacyTarget as ICLRDataTarget2;
// Build the allocVirtual delegate if the target supports ICLRDataTarget2
ContractDescriptorTarget.AllocVirtualDelegate allocVirtual = (ulong size, out ulong allocatedAddress) =>
{
allocatedAddress = 0;
return HResults.E_NOTIMPL;
};
if (dataTarget2 is not null)
{
// Windows virtual memory allocation flags used by ICLRDataTarget2::AllocVirtual.
const uint MEM_COMMIT = 0x1000;
const uint PAGE_READWRITE = 0x04;
allocVirtual = (ulong size, out ulong allocatedAddress) =>
{
ClrDataAddress addr;
int result = dataTarget2.AllocVirtual(0, (uint)size, MEM_COMMIT, PAGE_READWRITE, &addr);
allocatedAddress = (ulong)addr;
return result;
};
}
ContractDescriptorTarget target = ContractDescriptorTarget.Create(
contractAddress,
(address, buffer) =>
{
fixed (byte* bufferPtr = buffer)
{
uint bytesRead;
return dataTarget.ReadVirtual(address, bufferPtr, (uint)buffer.Length, &bytesRead);
}
},
(address, buffer) =>
{
fixed (byte* bufferPtr = buffer)
{
uint bytesWritten;
return dataTarget.WriteVirtual(address, bufferPtr, (uint)buffer.Length, &bytesWritten);
}
},
(threadId, contextFlags, bufferToFill) =>
{
fixed (byte* bufferPtr = bufferToFill)
{
return dataTarget.GetThreadContext(threadId, contextFlags, (uint)bufferToFill.Length, bufferPtr);
}
},
(threadId, context) =>
{
fixed (byte* contextPtr = context)
{
if (((nuint)contextPtr & (ContextAlignment - 1)) == 0)
{
return dataTarget.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 dataTarget.SetThreadContext(threadId, (uint)context.Length, alignedBuffer);
}
finally
{
NativeMemory.AlignedFree(alignedBuffer);
}
}
},
allocVirtual,
[Contracts.CoreCLRContracts.Register]);
// Without a legacy implementation to absorb individually-unimplemented APIs, validate
// the complete data-access contract set before publishing the interface.
if (legacyImpl is null)
Contracts.CoreCLRContracts.ValidateForDataAccess(target);
Legacy.SOSDacImpl impl = new(target, legacyImpl);
void* ccw = ComInterfaceMarshaller<IXCLRDataProcess>.ConvertToUnmanaged(impl);
int hrQI = Marshal.QueryInterface((nint)ccw, *pIID, out nint ptrToIface);
// Decrement reference count on ccw because QI incremented it
ComInterfaceMarshaller<IXCLRDataProcess>.Free(ccw);
if (hrQI < 0)
return hrQI;
*iface = (void*)ptrToIface;
return 0;
}
private static unsafe ContractDescriptorTarget CreateTargetFromCorDebugDataTarget(object targetObject, ulong contractAddress)
{
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]);
}
}