File: Dbi\DacDbiImpl.cs
Web Access
Project: src\src\runtime\src\native\managed\cdac\Microsoft.Diagnostics.DataContractReader.Legacy\Microsoft.Diagnostics.DataContractReader.Legacy.csproj (Microsoft.Diagnostics.DataContractReader.Legacy)
// 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.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using Microsoft.Diagnostics.DataContractReader.Contracts;
using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers;
using DACF = Microsoft.Diagnostics.DataContractReader.Contracts.DebuggerAssemblyControlFlags;

namespace Microsoft.Diagnostics.DataContractReader.Legacy;

[GeneratedComClass]
public sealed unsafe partial class DacDbiImpl : IDacDbiInterface
{
    private readonly Target _target;
    private readonly IDacDbiInterface? _legacy;

    // IStringHolder is a native C++ abstract class (not COM) with a single virtual method:
    //   virtual HRESULT AssignCopy(const WCHAR* psz) = 0;
    // The nint we receive is a pointer to the object, whose first field is the vtable pointer.
    // The vtable has a single entry: a function pointer for AssignCopy.
    // Use Thiscall because this is a C++ virtual method (thiscall on x86, no-op on x64/arm64).
    private delegate* unmanaged[Thiscall]<nint, char*, int> GetAssignCopyFnPtr(nint stringHolder)
    {
        // stringHolder -> vtable ptr -> first slot is AssignCopy
        nint vtable = *(nint*)stringHolder;
        return (delegate* unmanaged[Thiscall]<nint, char*, int>)(*(nint*)vtable);
    }

    private int StringHolderAssignCopy(nint stringHolder, string str)
    {
        fixed (char* pStr = str)
        {
            return GetAssignCopyFnPtr(stringHolder)(stringHolder, pStr);
        }
    }

    private bool CORProfilerPresent()
    {
        if (!_target.TryReadGlobalPointer(Constants.Globals.ProfilerControlBlock, out TargetPointer? profControlBlockAddress))
            return false;

        Target.TypeInfo type = _target.GetTypeInfo(DataType.ProfControlBlock);
        TargetPointer mainProfInterface = _target.ReadPointerField(profControlBlockAddress.Value, type, "MainProfilerProfInterface");
        int notificationCount = _target.ReadField<int>(profControlBlockAddress.Value, type, "NotificationProfilerCount");
        return mainProfInterface != TargetPointer.Null || notificationCount > 0;
    }

    public DacDbiImpl(Target target, object? legacyObj)
    {
        _target = target;
        _legacy = legacyObj as IDacDbiInterface;
    }

    public int CheckDbiVersion(DbiVersion* pVersion)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.CheckDbiVersion(pVersion) : HResults.E_NOTIMPL;

    public int FlushCache()
    {
        _target.Flush();
        return _legacy is not null ? _legacy.FlushCache() : HResults.S_OK;
    }

    public int DacSetTargetConsistencyChecks(Interop.BOOL fEnableAsserts)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.DacSetTargetConsistencyChecks(fEnableAsserts) : HResults.E_NOTIMPL;

    public int IsLeftSideInitialized(Interop.BOOL* pResult)
    {
        *pResult = Interop.BOOL.FALSE;
        int hr = HResults.S_OK;
        try
        {
            *pResult = _target.Contracts.Debugger.TryGetDebuggerData(out Contracts.DebuggerData data) && data.IsLeftSideInitialized ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacy is not null)
        {
            Interop.BOOL resultLocal;
            int hrLocal = _legacy.IsLeftSideInitialized(&resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pResult == resultLocal);
        }
#endif

        return hr;
    }

    public int GetAppDomainId(ulong vmAppDomain, uint* pRetVal)
    {
        *pRetVal = 0;
        int hr = HResults.S_OK;
        try
        {
            *pRetVal = vmAppDomain == 0 ? 0u : _target.ReadGlobal<uint>(Constants.Globals.DefaultADID);
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            uint retValLocal;
            int hrLocal = _legacy.GetAppDomainId(vmAppDomain, &retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}");
        }
#endif
        return hr;
    }

    public int GetAppDomainFullName(ulong vmAppDomain, nint pStrName)
    {
        int hr = HResults.S_OK;
        try
        {
            string name = _target.Contracts.Loader.GetAppDomainFriendlyName();
            hr = StringHolderAssignCopy(pStrName, name);
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            int hrLocal = _legacy.GetAppDomainFullName(vmAppDomain, pStrName);
            Debug.ValidateHResult(hr, hrLocal);
        }
#endif
        return hr;
    }

    public int GetModuleSimpleName(ulong vmModule, nint pStrFilename)
    {
        int hr = HResults.S_OK;
        string? cdacSimpleName = null;
        try
        {
            Contracts.ILoader loader = _target.Contracts.Loader;
            Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(new TargetPointer(vmModule));
            cdacSimpleName = loader.GetSimpleName(handle);
            hr = StringHolderAssignCopy(pStrFilename, cdacSimpleName);
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            using var legacyHolder = new NativeStringHolder();
            int hrLocal = _legacy.GetModuleSimpleName(vmModule, legacyHolder.Ptr);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                Debug.Assert(
                    string.Equals(cdacSimpleName, legacyHolder.Value, System.StringComparison.Ordinal),
                    $"GetModuleSimpleName string mismatch - cDAC: '{cdacSimpleName}', DAC: '{legacyHolder.Value}'");
            }
        }
#endif
        return hr;
    }

    public int GetAssemblyPath(ulong vmAssembly, nint pStrFilename, Interop.BOOL* pResult)
    {
        *pResult = Interop.BOOL.FALSE;
        int hr = HResults.S_OK;
        try
        {
            Contracts.ILoader loader = _target.Contracts.Loader;
            Contracts.ModuleHandle handle = loader.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmAssembly));
            string path = loader.GetPath(handle);
            if (string.IsNullOrEmpty(path))
            {
                *pResult = Interop.BOOL.FALSE;
            }
            else
            {
                hr = StringHolderAssignCopy(pStrFilename, path);
                *pResult = Interop.BOOL.TRUE;
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            Interop.BOOL resultLocal;
            int hrLocal = _legacy.GetAssemblyPath(vmAssembly, pStrFilename, &resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}");
        }
#endif
        return hr;
    }

    public int ResolveTypeReference(DacDbiTypeRefData* pTypeRefInfo, DacDbiTypeRefData* pTargetRefInfo)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.ResolveTypeReference(pTypeRefInfo, pTargetRefInfo) : HResults.E_NOTIMPL;

    public int GetModulePath(ulong vmModule, nint pStrFilename, Interop.BOOL* pResult)
    {
        *pResult = Interop.BOOL.FALSE;
        int hr = HResults.S_OK;
        try
        {
            Contracts.ILoader loader = _target.Contracts.Loader;
            Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(new TargetPointer(vmModule));
            string path = loader.GetPath(handle);
            if (string.IsNullOrEmpty(path))
            {
                *pResult = Interop.BOOL.FALSE;
            }
            else
            {
                hr = StringHolderAssignCopy(pStrFilename, path);
                *pResult = Interop.BOOL.TRUE;
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            Interop.BOOL resultLocal;
            int hrLocal = _legacy.GetModulePath(vmModule, pStrFilename, &resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}");
        }
#endif
        return hr;
    }

    public int GetMetadata(ulong vmModule, DacDbiTargetBuffer* pTargetBuffer)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetMetadata(vmModule, pTargetBuffer) : HResults.E_NOTIMPL;

    public int GetSymbolsBuffer(ulong vmModule, DacDbiTargetBuffer* pTargetBuffer, int* pSymbolFormat)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetSymbolsBuffer(vmModule, pTargetBuffer, pSymbolFormat) : HResults.E_NOTIMPL;

    public int GetModuleData(ulong vmModule, DacDbiModuleInfo* pData)
    {
        int hr = HResults.S_OK;
        try
        {
            if (vmModule == 0)
                throw new ArgumentException("Module pointer must be non-zero.", nameof(vmModule));

            if (pData == null)
                throw new ArgumentNullException(nameof(pData));

            Contracts.ILoader loader = _target.Contracts.Loader;
            Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(new TargetPointer(vmModule));

            *pData = default;
            pData->vmAssembly = loader.GetAssembly(handle).Value;
            pData->vmPEAssembly = loader.GetPEAssembly(handle).Value;
            bool isDynamic = loader.IsDynamic(handle);
            pData->fIsDynamic = isDynamic ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
            string path = loader.GetPath(handle);
            pData->fInMemory = string.IsNullOrEmpty(path) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
            if (!isDynamic && loader.TryGetLoadedImageContents(handle, out TargetPointer baseAddress, out uint size, out uint _))
            {
                pData->pPEBaseAddress = baseAddress.Value;
                pData->nPESize = size;
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            DacDbiModuleInfo dataLocal;
            int hrLocal = _legacy.GetModuleData(vmModule, &dataLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                Debug.Assert(pData->vmAssembly == dataLocal.vmAssembly, $"vmAssembly: cDAC: {pData->vmAssembly:x}, DAC: {dataLocal.vmAssembly:x}");
                Debug.Assert(pData->vmPEAssembly == dataLocal.vmPEAssembly, $"vmPEAssembly: cDAC: {pData->vmPEAssembly:x}, DAC: {dataLocal.vmPEAssembly:x}");
                Debug.Assert(pData->fIsDynamic == dataLocal.fIsDynamic, $"fIsDynamic: cDAC: {pData->fIsDynamic}, DAC: {dataLocal.fIsDynamic}");
                Debug.Assert(pData->fInMemory == dataLocal.fInMemory, $"fInMemory: cDAC: {pData->fInMemory}, DAC: {dataLocal.fInMemory}");
                Debug.Assert(pData->pPEBaseAddress == dataLocal.pPEBaseAddress, $"pPEBaseAddress: cDAC: {pData->pPEBaseAddress:x}, DAC: {dataLocal.pPEBaseAddress:x}");
                Debug.Assert(pData->nPESize == dataLocal.nPESize, $"nPESize: cDAC: {pData->nPESize}, DAC: {dataLocal.nPESize}");
            }
        }
#endif
        return hr;
    }
    public int GetModuleForAssembly(ulong vmAssembly, ulong* pModule)
    {
        *pModule = 0;
        int hr = HResults.S_OK;
        try
        {
            Contracts.ILoader loader = _target.Contracts.Loader;
            Contracts.ModuleHandle handle = loader.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmAssembly));
            TargetPointer modulePtr = loader.GetModule(handle);
            *pModule = modulePtr.Value;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            ulong moduleLocal;
            int hrLocal = _legacy.GetModuleForAssembly(vmAssembly, &moduleLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pModule == moduleLocal, $"cDAC: {*pModule:x}, DAC: {moduleLocal:x}");
        }
#endif
        return hr;
    }

    public int GetAddressType(ulong address, int* pRetVal)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetAddressType(address, pRetVal) : HResults.E_NOTIMPL;

    public int GetCompilerFlags(ulong vmAssembly, Interop.BOOL* pfAllowJITOpts, Interop.BOOL* pfEnableEnC)
    {
        *pfAllowJITOpts = Interop.BOOL.FALSE;
        *pfEnableEnC = Interop.BOOL.FALSE;
        int hr = HResults.S_OK;
        try
        {
            Contracts.ILoader loader = _target.Contracts.Loader;
            Contracts.ModuleHandle handle = loader.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmAssembly));
            Contracts.ModuleFlags flags = loader.GetFlags(handle);
            *pfAllowJITOpts = (flags & Contracts.ModuleFlags.JitOptimizationDisabled) == 0
                ? Interop.BOOL.TRUE
                : Interop.BOOL.FALSE;
            *pfEnableEnC = (flags & Contracts.ModuleFlags.EditAndContinue) != 0
                ? Interop.BOOL.TRUE
                : Interop.BOOL.FALSE;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            Interop.BOOL allowJITOptsLocal;
            Interop.BOOL enableEnCLocal;
            int hrLocal = _legacy.GetCompilerFlags(vmAssembly, &allowJITOptsLocal, &enableEnCLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                Debug.Assert(*pfAllowJITOpts == allowJITOptsLocal, $"cDAC: {*pfAllowJITOpts}, DAC: {allowJITOptsLocal}");
                Debug.Assert(*pfEnableEnC == enableEnCLocal, $"cDAC: {*pfEnableEnC}, DAC: {enableEnCLocal}");
            }
        }
#endif
        return hr;
    }

    public int SetCompilerFlags(ulong vmAssembly, Interop.BOOL fAllowJitOpts, Interop.BOOL fEnableEnC)
    {
        int hr = HResults.S_OK;
        try
        {
            Contracts.ILoader loader = _target.Contracts.Loader;
            Contracts.ModuleHandle handle = loader.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmAssembly));

            DACF debuggerInfoBits = loader.GetDebuggerInfoBits(handle);
            DACF controlFlags = debuggerInfoBits & ~(DACF.DACF_ALLOW_JIT_OPTS | DACF.DACF_ENC_ENABLED);
            controlFlags &= DACF.DACF_CONTROL_FLAGS_MASK;

            if (fAllowJitOpts != Interop.BOOL.FALSE)
            {
                controlFlags |= DACF.DACF_ALLOW_JIT_OPTS;
            }

            if (fEnableEnC != Interop.BOOL.FALSE)
            {
                bool fIgnorePdbs = (debuggerInfoBits & DACF.DACF_IGNORE_PDBS) != 0;
                bool canSetEnC = (loader.GetFlags(handle) & Contracts.ModuleFlags.EncCapable) != 0 && !CORProfilerPresent() && fIgnorePdbs;
                if (canSetEnC)
                {
                    controlFlags |= DACF.DACF_ENC_ENABLED;
                }
                else
                {
                    hr = CorDbgHResults.CORDBG_S_NOT_ALL_BITS_SET;
                }
            }

            loader.SetDebuggerInfoBits(handle, controlFlags);
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            int hrLocal = _legacy.SetCompilerFlags(vmAssembly, fAllowJitOpts, fEnableEnC);
            Debug.ValidateHResult(hr, hrLocal);
        }
#endif
        return hr;
    }

    public int EnumerateAssembliesInAppDomain(ulong vmAppDomain, delegate* unmanaged<ulong, nint, void> fpCallback, nint pUserData)
    {
        int hr = HResults.S_OK;
#if DEBUG
        List<ulong>? cdacAssemblies = _legacy is not null ? new() : null;
#endif
        try
        {
            if (fpCallback == null)
            {
                throw new ArgumentNullException(nameof(fpCallback));
            }

            if (vmAppDomain == 0)
            {
                return hr;
            }

            Contracts.ILoader loader = _target.Contracts.Loader;
            foreach (Contracts.ModuleHandle handle in loader.GetModuleHandles(
                new TargetPointer(vmAppDomain),
                AssemblyIterationFlags.IncludeLoading | AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution))
            {
                TargetPointer assembly = loader.GetAssembly(handle);
                fpCallback(assembly.Value, pUserData);
#if DEBUG
                cdacAssemblies?.Add(assembly.Value);
#endif
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null && fpCallback != null)
        {
            List<ulong> dacAssemblies = new();
            GCHandle dacHandle = GCHandle.Alloc(dacAssemblies);
            try
            {
                int hrLocal = _legacy.EnumerateAssembliesInAppDomain(vmAppDomain, &CollectEnumerationCallback, GCHandle.ToIntPtr(dacHandle));
                Debug.ValidateHResult(hr, hrLocal);
                if (hr == HResults.S_OK)
                {
                    Debug.Assert(
                        cdacAssemblies!.SequenceEqual(dacAssemblies),
                        $"Assembly enumeration mismatch - "
                        + $"cDAC: [{string.Join(",", cdacAssemblies!.Select(a => $"0x{a:x}"))}], "
                        + $"DAC: [{string.Join(",", dacAssemblies.Select(a => $"0x{a:x}"))}]");
                }
            }
            finally
            {
                dacHandle.Free();
            }
        }
#endif
        return hr;
    }

    public int EnumerateModulesInAssembly(ulong vmAssembly, nint fpCallback, nint pUserData)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.EnumerateModulesInAssembly(vmAssembly, fpCallback, pUserData) : HResults.E_NOTIMPL;

    public int RequestSyncAtEvent()
    {
        int hr = HResults.S_OK;
        try
        {
            _target.Contracts.Debugger.RequestSyncAtEvent();
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            int hrLocal = _legacy.RequestSyncAtEvent();
            Debug.ValidateHResult(hr, hrLocal);
        }
#endif
        return hr;
    }

    public int SetSendExceptionsOutsideOfJMC(Interop.BOOL sendExceptionsOutsideOfJMC)
    {
        int hr = HResults.S_OK;
        try
        {
            _target.Contracts.Debugger.SetSendExceptionsOutsideOfJMC(sendExceptionsOutsideOfJMC != Interop.BOOL.FALSE);
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            int hrLocal = _legacy.SetSendExceptionsOutsideOfJMC(sendExceptionsOutsideOfJMC);
            Debug.ValidateHResult(hr, hrLocal);
        }
#endif
        return hr;
    }

    public int MarkDebuggerAttachPending()
    {
        int hr = HResults.S_OK;
        try
        {
            Contracts.IDebugger debugger = _target.Contracts.Debugger;
            if (debugger.TryGetDebuggerData(out _))
            {
                debugger.MarkDebuggerAttachPending();
            }
            else
            {
                throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_NOTREADY)!;
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacy is not null)
        {
            int hrLocal = _legacy.MarkDebuggerAttachPending();
            Debug.ValidateHResult(hr, hrLocal);
        }
#endif

        return hr;
    }

    public int MarkDebuggerAttached(Interop.BOOL fAttached)
    {
        int hr = HResults.S_OK;
        try
        {
            Contracts.IDebugger debugger = _target.Contracts.Debugger;
            if (debugger.TryGetDebuggerData(out _))
            {
                debugger.MarkDebuggerAttached(fAttached != Interop.BOOL.FALSE);
            }
            else if (fAttached != Interop.BOOL.FALSE)
            {
                throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_NOTREADY)!;
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacy is not null)
        {
            int hrLocal = _legacy.MarkDebuggerAttached(fAttached);
            Debug.ValidateHResult(hr, hrLocal);
        }
#endif

        return hr;
    }

    public int Hijack(ulong vmThread, uint dwThreadId, nint pRecord, nint pOriginalContext, uint cbSizeContext, int reason, nint pUserData, ulong* pRemoteContextAddr)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.Hijack(vmThread, dwThreadId, pRecord, pOriginalContext, cbSizeContext, reason, pUserData, pRemoteContextAddr) : HResults.E_NOTIMPL;

    public int EnumerateThreads(delegate* unmanaged<ulong, nint, void> fpCallback, nint pUserData)
    {
        int hr = HResults.S_OK;
#if DEBUG
        List<ulong>? cdacThreads = _legacy is not null ? new() : null;
#endif
        try
        {
            if (fpCallback == null)
                throw new ArgumentNullException(nameof(fpCallback));
            Contracts.IThread threadContract = _target.Contracts.Thread;
            Contracts.ThreadStoreData threadStore = threadContract.GetThreadStoreData();
            TargetPointer currentThread = threadStore.FirstThread;
            while (currentThread != TargetPointer.Null)
            {
                Contracts.ThreadData threadData = threadContract.GetThreadData(currentThread);
                // Match native: skip stopped and unstarted threads
                if ((threadData.State & (Contracts.ThreadState.Stopped | Contracts.ThreadState.Unstarted)) == 0)
                {
                    fpCallback(currentThread.Value, pUserData);
#if DEBUG
                    cdacThreads?.Add(currentThread.Value);
#endif
                }
                currentThread = threadData.NextThread;
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            List<ulong> dacThreads = new();
            GCHandle dacHandle = GCHandle.Alloc(dacThreads);
            int hrLocal = _legacy.EnumerateThreads(&CollectEnumerationCallback, GCHandle.ToIntPtr(dacHandle));
            dacHandle.Free();
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                Debug.Assert(
                    cdacThreads!.SequenceEqual(dacThreads),
                    $"Thread enumeration mismatch - cDAC: [{string.Join(",", cdacThreads!.Select(t => $"0x{t:x}"))}], DAC: [{string.Join(",", dacThreads.Select(t => $"0x{t:x}"))}]");
            }
        }
#endif
        return hr;
    }

#if DEBUG
    [UnmanagedCallersOnly]
    private static void CollectEnumerationCallback(ulong value, nint pUserData)
    {
        GCHandle handle = GCHandle.FromIntPtr(pUserData);
        ((List<ulong>)handle.Target!).Add(value);
    }
#endif

    public int IsThreadMarkedDead(ulong vmThread, Interop.BOOL* pResult)
    {
        *pResult = Interop.BOOL.FALSE;
        int hr = HResults.S_OK;
        try
        {
            Contracts.ThreadData threadData = _target.Contracts.Thread.GetThreadData(new TargetPointer(vmThread));
            *pResult = (threadData.State & Contracts.ThreadState.Stopped) != 0 ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            Interop.BOOL resultLocal;
            int hrLocal = _legacy.IsThreadMarkedDead(vmThread, &resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}");
        }
#endif
        return hr;
    }

    public int GetThreadHandle(ulong vmThread, nint pRetVal)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetThreadHandle(vmThread, pRetVal) : HResults.E_NOTIMPL;

    public int GetThreadObject(ulong vmThread, ulong* pRetVal)
    {
        *pRetVal = 0;
        int hr = HResults.S_OK;
        try
        {
            Contracts.ThreadData threadData = _target.Contracts.Thread.GetThreadData(new TargetPointer(vmThread));
            if ((threadData.State & (Contracts.ThreadState.Stopped | Contracts.ThreadState.Unstarted | Contracts.ThreadState.Detached)) != 0)
                throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_BAD_THREAD_STATE)!;

            *pRetVal = threadData.ExposedObjectHandle.Value;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            ulong retValLocal;
            int hrLocal = _legacy.GetThreadObject(vmThread, &retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal:x}, DAC: {retValLocal:x}");
        }
#endif
        return hr;
    }

    public int GetThreadAllocInfo(ulong vmThread, DacDbiThreadAllocInfo* pThreadAllocInfo)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetThreadAllocInfo(vmThread, pThreadAllocInfo) : HResults.E_NOTIMPL;

    public int SetDebugState(ulong vmThread, int debugState)
    {
        int hr = HResults.S_OK;
        try
        {
            TargetPointer threadPtr = new TargetPointer(vmThread);

            if (debugState == (int)CorDebugThreadState.ThreadSuspend)
            {
                _target.Contracts.Thread.SetDebuggerControlledThreadState(threadPtr, Contracts.DebuggerControlledThreadState.UserSuspend);
            }
            else if (debugState == (int)CorDebugThreadState.ThreadRun)
            {
                _target.Contracts.Thread.ResetDebuggerControlledThreadState(threadPtr, Contracts.DebuggerControlledThreadState.UserSuspend);
            }
            else
            {
                throw new ArgumentException("Invalid debug state value.", nameof(debugState));
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            int hrLocal = _legacy.SetDebugState(vmThread, debugState);
            Debug.ValidateHResult(hr, hrLocal);
        }
#endif
        return hr;
    }

    public int HasUnhandledException(ulong vmThread, Interop.BOOL* pResult)
    {
        *pResult = Interop.BOOL.FALSE;
        int hr = HResults.S_OK;
        try
        {
            Contracts.ThreadData threadData = _target.Contracts.Thread.GetThreadData(new TargetPointer(vmThread));
            *pResult = threadData.HasUnhandledException ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            Interop.BOOL resultLocal;
            int hrLocal = _legacy.HasUnhandledException(vmThread, &resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}");
        }
#endif
        return hr;
    }

    public int GetUserState(ulong vmThread, int* pRetVal)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetUserState(vmThread, pRetVal) : HResults.E_NOTIMPL;

    public int GetPartialUserState(ulong vmThread, CorDebugUserState* pRetVal)
    {
        *pRetVal = default;
        int hr = HResults.S_OK;
        try
        {
            TargetPointer threadPtr = new TargetPointer(vmThread);
            Contracts.ThreadData threadData = _target.Contracts.Thread.GetThreadData(threadPtr);
            Contracts.ThreadState threadState = threadData.State;

            CorDebugUserState result = default;
            if ((threadState & Contracts.ThreadState.Background) != 0)
                result |= CorDebugUserState.USER_BACKGROUND;

            if ((threadState & Contracts.ThreadState.Unstarted) != 0)
                result |= CorDebugUserState.USER_UNSTARTED;

            if ((threadState & Contracts.ThreadState.Stopped) != 0)
                result |= CorDebugUserState.USER_STOPPED;

            if ((threadState & Contracts.ThreadState.WaitSleepJoin) != 0)
                result |= CorDebugUserState.USER_WAIT_SLEEP_JOIN;

            if ((threadState & Contracts.ThreadState.ThreadPoolWorker) != 0)
                result |= CorDebugUserState.USER_THREADPOOL;

            *pRetVal = result;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            CorDebugUserState retValLocal;
            int hrLocal = _legacy.GetPartialUserState(vmThread, &retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}");
        }
#endif
        return hr;
    }

    public int GetConnectionID(ulong vmThread, uint* pRetVal)
    {
        *pRetVal = 0;
        int hr = HResults.S_OK;
#if DEBUG
        if (_legacy is not null)
        {
            uint retValLocal;
            int hrLocal = _legacy.GetConnectionID(vmThread, &retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}");
        }
#endif
        return hr;
    }

    public int GetTaskID(ulong vmThread, ulong* pRetVal)
    {
        *pRetVal = 0;
        int hr = HResults.S_OK;
#if DEBUG
        if (_legacy is not null)
        {
            ulong retValLocal;
            int hrLocal = _legacy.GetTaskID(vmThread, &retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}");
        }
#endif
        return hr;
    }

    public int TryGetVolatileOSThreadID(ulong vmThread, uint* pRetVal)
    {
        *pRetVal = 0;
        int hr = HResults.S_OK;
        try
        {
            Contracts.ThreadData threadData = _target.Contracts.Thread.GetThreadData(new TargetPointer(vmThread));
            uint osId = (uint)threadData.OSId.Value;
            // Match native: SWITCHED_OUT_FIBER_OSID (0xbaadf00d) means thread is switched out
            const uint SWITCHED_OUT_FIBER_OSID = 0xbaadf00d;
            *pRetVal = osId == SWITCHED_OUT_FIBER_OSID ? 0 : osId;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            uint retValLocal;
            int hrLocal = _legacy.TryGetVolatileOSThreadID(vmThread, &retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}");
        }
#endif
        return hr;
    }

    public int GetUniqueThreadID(ulong vmThread, uint* pRetVal)
    {
        *pRetVal = 0;
        int hr = HResults.S_OK;
        try
        {
            Contracts.ThreadData threadData = _target.Contracts.Thread.GetThreadData(new TargetPointer(vmThread));
            *pRetVal = (uint)threadData.OSId.Value;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            uint retValLocal;
            int hrLocal = _legacy.GetUniqueThreadID(vmThread, &retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}");
        }
#endif
        return hr;
    }

    public int GetCurrentException(ulong vmThread, ulong* pRetVal)
    {
        *pRetVal = 0;
        int hr = HResults.S_OK;
        try
        {
            TargetPointer threadPtr = new TargetPointer(vmThread);
            TargetPointer exceptionHandle = _target.Contracts.Thread.GetCurrentExceptionHandle(threadPtr);
            if (exceptionHandle == TargetPointer.Null)
            {
                ThreadData data = _target.Contracts.Thread.GetThreadData(threadPtr);
                if (data.LastThrownObjectIsUnhandled)
                    exceptionHandle = data.LastThrownObjectHandle;
            }
            *pRetVal = exceptionHandle.Value;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            ulong retValLocal;
            int hrLocal = _legacy.GetCurrentException(vmThread, &retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal:x}, DAC: {retValLocal:x}");
        }
#endif
        return hr;
    }

    public int GetObjectForCCW(ulong ccwPtr, ulong* pRetVal)
    {
        *pRetVal = 0;
        int hr = HResults.S_OK;
        try
        {
            TargetPointer objectHandle = TargetPointer.Null;
            TargetPointer ccwAddress = new(ccwPtr);
            bool comWrappersSuccess = false;

            if (_target.Contracts.TryGetContract<IComWrappers>(out IComWrappers? comWrappers))
            {
                TargetPointer managedObjectWrapper = comWrappers.GetManagedObjectWrapperFromCCW(ccwAddress);
                if (managedObjectWrapper != TargetPointer.Null)
                {
                    comWrappersSuccess = _target.TryReadPointer(managedObjectWrapper, out objectHandle);
                }
            }

            if (!comWrappersSuccess && _target.Contracts.TryGetContract<IBuiltInCOM>(out IBuiltInCOM? builtInCOM))
            {
                TargetPointer ccw = builtInCOM.GetCCWFromInterfacePointer(ccwAddress);
                if (ccw == TargetPointer.Null)
                {
                    ccw = ccwAddress;
                }
                ccw = builtInCOM.GetStartWrapper(ccw);
                objectHandle = builtInCOM.GetObjectHandle(ccw);
            }

            *pRetVal = objectHandle.Value;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            ulong retValLocal;
            int hrLocal = _legacy.GetObjectForCCW(ccwPtr, &retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal:x}, DAC: {retValLocal:x}");
        }
#endif
        return hr;
    }

    public int GetCurrentCustomDebuggerNotification(ulong vmThread, ulong* pRetVal)
    {
        *pRetVal = 0;
        int hr = HResults.S_OK;
        try
        {
            Contracts.ThreadData threadData = _target.Contracts.Thread.GetThreadData(new TargetPointer(vmThread));
            *pRetVal = threadData.CurrentCustomDebuggerNotificationHandle.Value;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            ulong retValLocal;
            int hrLocal = _legacy.GetCurrentCustomDebuggerNotification(vmThread, &retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal:x}, DAC: {retValLocal:x}");
        }
#endif
        return hr;
    }

    public int GetCurrentAppDomain(ulong* pRetVal)
    {
        *pRetVal = 0;
        int hr = HResults.S_OK;
        try
        {
            TargetPointer appDomainPtr = _target.ReadGlobalPointer(Constants.Globals.AppDomain);
            *pRetVal = _target.ReadPointer(appDomainPtr);
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            ulong retValLocal;
            int hrLocal = _legacy.GetCurrentAppDomain(&retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal:x}, DAC: {retValLocal:x}");
        }
#endif
        return hr;
    }

    public int ResolveAssembly(ulong vmScope, uint tkAssemblyRef, ulong* pRetVal)
    {
        *pRetVal = 0;
        int hr = HResults.S_OK;
        try
        {
            Contracts.ILoader loader = _target.Contracts.Loader;
            Contracts.ModuleHandle scopeModule = loader.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmScope));
            Contracts.ModuleLookupTables lookupTables = loader.GetLookupTables(scopeModule);
            TargetPointer referencedModule = loader.GetModuleLookupMapElement(lookupTables.ManifestModuleReferences, tkAssemblyRef, out _);
            if (referencedModule != TargetPointer.Null)
            {
                Contracts.ModuleHandle referencedModuleHandle = loader.GetModuleHandleFromModulePtr(referencedModule);
                *pRetVal = loader.GetAssembly(referencedModuleHandle).Value;
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            ulong retValLocal;
            int hrLocal = _legacy.ResolveAssembly(vmScope, tkAssemblyRef, &retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}");
        }
#endif
        return hr;
    }

    public int GetNativeCodeSequencePointsAndVarInfo(ulong vmMethodDesc, ulong startAddress, Interop.BOOL fCodeAvailable, nint pNativeVarData, nint pSequencePoints)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetNativeCodeSequencePointsAndVarInfo(vmMethodDesc, startAddress, fCodeAvailable, pNativeVarData, pSequencePoints) : HResults.E_NOTIMPL;

    public int GetManagedStoppedContext(ulong vmThread, ulong* pRetVal)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetManagedStoppedContext(vmThread, pRetVal) : HResults.E_NOTIMPL;

    public int CreateStackWalk(ulong vmThread, nint pInternalContextBuffer, nuint* ppSFIHandle)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.CreateStackWalk(vmThread, pInternalContextBuffer, ppSFIHandle) : HResults.E_NOTIMPL;

    public int DeleteStackWalk(nuint ppSFIHandle)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.DeleteStackWalk(ppSFIHandle) : HResults.E_NOTIMPL;

    public int GetStackWalkCurrentContext(nuint pSFIHandle, nint pContext)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetStackWalkCurrentContext(pSFIHandle, pContext) : HResults.E_NOTIMPL;

    public int SetStackWalkCurrentContext(ulong vmThread, nuint pSFIHandle, int flag, nint pContext)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.SetStackWalkCurrentContext(vmThread, pSFIHandle, flag, pContext) : HResults.E_NOTIMPL;

    public int UnwindStackWalkFrame(nuint pSFIHandle, Interop.BOOL* pResult)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.UnwindStackWalkFrame(pSFIHandle, pResult) : HResults.E_NOTIMPL;

    public int CheckContext(ulong vmThread, nint pContext)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.CheckContext(vmThread, pContext) : HResults.E_NOTIMPL;

    public int GetStackWalkCurrentFrameInfo(nuint pSFIHandle, nint pFrameData, int* pRetVal)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetStackWalkCurrentFrameInfo(pSFIHandle, pFrameData, pRetVal) : HResults.E_NOTIMPL;

    public int GetCountOfInternalFrames(ulong vmThread, uint* pRetVal)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetCountOfInternalFrames(vmThread, pRetVal) : HResults.E_NOTIMPL;

    public int EnumerateInternalFrames(ulong vmThread, nint fpCallback, nint pUserData)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.EnumerateInternalFrames(vmThread, fpCallback, pUserData) : HResults.E_NOTIMPL;

    public int GetStackParameterSize(ulong controlPC, uint* pRetVal)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetStackParameterSize(controlPC, pRetVal) : HResults.E_NOTIMPL;

    public int GetFramePointer(nuint pSFIHandle, ulong* pRetVal)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetFramePointer(pSFIHandle, pRetVal) : HResults.E_NOTIMPL;

    public int IsLeafFrame(ulong vmThread, byte* pContext, Interop.BOOL* pResult)
    {
        *pResult = Interop.BOOL.FALSE;
        int hr = HResults.S_OK;
        try
        {
            IPlatformAgnosticContext leafCtx = IPlatformAgnosticContext.GetContextForPlatform(_target);
            uint allFlags = leafCtx.AllContextFlags;
            byte[] leafContext = _target.Contracts.Thread.GetContext(new TargetPointer(vmThread), ThreadContextSource.None, allFlags);
            leafCtx.FillFromBuffer(leafContext);

            // Read the given context from the native buffer.
            IPlatformAgnosticContext givenCtx = IPlatformAgnosticContext.GetContextForPlatform(_target);
            givenCtx.FillFromBuffer(new Span<byte>(pContext, leafContext.Length));

            *pResult = givenCtx.StackPointer == leafCtx.StackPointer
                && givenCtx.InstructionPointer == leafCtx.InstructionPointer
                ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            Interop.BOOL resultLocal;
            int hrLocal = _legacy.IsLeafFrame(vmThread, pContext, &resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}");
        }
#endif
        return hr;
    }

    public int GetContext(ulong vmThread, byte* pContextBuffer)
    {
        int hr = HResults.S_OK;
        try
        {
            uint allFlags = IPlatformAgnosticContext.GetContextForPlatform(_target).AllContextFlags;
            byte[] context = _target.Contracts.Thread.GetContext(new TargetPointer(vmThread), ThreadContextSource.Debugger, allFlags);

            context.AsSpan().CopyTo(new Span<byte>(pContextBuffer, context.Length));
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            uint contextSize = IPlatformAgnosticContext.GetContextForPlatform(_target).Size;
            byte[] localContextBuf = new byte[contextSize];
            fixed (byte* pLocal = localContextBuf)
            {
                int hrLocal = _legacy.GetContext(vmThread, pLocal);
                Debug.ValidateHResult(hr, hrLocal);

                if (hr == HResults.S_OK)
                {
                    IPlatformAgnosticContext contextStruct = IPlatformAgnosticContext.GetContextForPlatform(_target);
                    IPlatformAgnosticContext localContextStruct = IPlatformAgnosticContext.GetContextForPlatform(_target);
                    contextStruct.FillFromBuffer(new Span<byte>(pContextBuffer, (int)contextSize));
                    localContextStruct.FillFromBuffer(localContextBuf);

                    Debug.Assert(contextStruct.Equals(localContextStruct));
                }
            }
        }
#endif
        return hr;
    }

    public int ConvertContextToDebuggerRegDisplay(nint pInContext, nint pOutDRD, Interop.BOOL fActive)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.ConvertContextToDebuggerRegDisplay(pInContext, pOutDRD, fActive) : HResults.E_NOTIMPL;

    public int IsDiagnosticsHiddenOrLCGMethod(ulong vmMethodDesc, int* pRetVal)
    {
        *pRetVal = (int)DynamicMethodType.kNone;
        int hr = HResults.S_OK;
        try
        {
            Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
            Contracts.MethodDescHandle md = rts.GetMethodDescHandle(new TargetPointer(vmMethodDesc));
            if (rts.IsILStub(md) || rts.IsAsyncThunkMethod(md) || rts.IsWrapperStub(md))
            {
                *pRetVal = (int)DynamicMethodType.kDiagnosticHidden;
            }
            else if (rts.IsDynamicMethod(md))
            {
                *pRetVal = (int)DynamicMethodType.kLCGMethod;
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            int resultLocal;
            int hrLocal = _legacy.IsDiagnosticsHiddenOrLCGMethod(vmMethodDesc, &resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == resultLocal, $"cDAC: {*pRetVal}, DAC: {resultLocal}");
        }
#endif
        return hr;
    }

    public int GetVarArgSig(ulong VASigCookieAddr, ulong* pArgBase, DacDbiTargetBuffer* pRetVal)
    {
        *pArgBase = 0;
        *pRetVal = default;
        int hr = HResults.S_OK;
        try
        {
            Contracts.ISignature signature = _target.Contracts.Signature;
            TargetPointer argBase = signature.GetVarArgArgsBase(new TargetPointer(VASigCookieAddr));
            signature.GetVarArgSignature(new TargetPointer(VASigCookieAddr), out TargetPointer sigAddr, out uint sigLen);

            *pArgBase = argBase.Value;
            *pRetVal = new DacDbiTargetBuffer { pAddress = sigAddr.Value, cbSize = sigLen };
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            ulong argBaseLocal;
            DacDbiTargetBuffer retValLocal = default;
            int hrLocal = _legacy.GetVarArgSig(VASigCookieAddr, &argBaseLocal, &retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                Debug.Assert(*pArgBase == argBaseLocal, $"cDAC argBase: 0x{*pArgBase:X}, DAC argBase: 0x{argBaseLocal:X}");
                Debug.Assert(pRetVal->pAddress == retValLocal.pAddress, $"cDAC sigAddr: 0x{pRetVal->pAddress:X}, DAC sigAddr: 0x{retValLocal.pAddress:X}");
                Debug.Assert(pRetVal->cbSize == retValLocal.cbSize, $"cDAC sigLen: {pRetVal->cbSize}, DAC sigLen: {retValLocal.cbSize}");
            }
        }
#endif
        return hr;
    }

    public int RequiresAlign8(ulong thExact, Interop.BOOL* pResult)
    {
        *pResult = Interop.BOOL.FALSE;
        int hr = HResults.S_OK;
        RuntimeInfoArchitecture arch = _target.Contracts.RuntimeInfo.GetTargetArchitecture();
        try
        {
            // Some 32-bit platform ABIs require 64-bit alignment (FEATURE_64BIT_ALIGNMENT).
            if (arch == RuntimeInfoArchitecture.Arm || arch == RuntimeInfoArchitecture.Wasm)
            {
                Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
                Contracts.TypeHandle th = rts.GetTypeHandle(new TargetPointer(thExact));
                *pResult = rts.RequiresAlign8(th) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
            }
            else
            {
                throw new NotImplementedException();
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            Interop.BOOL resultLocal;
            int hrLocal = _legacy.RequiresAlign8(thExact, &resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}");
        }
#endif
        return hr;
    }

    public int ResolveExactGenericArgsToken(uint dwExactGenericArgsTokenIndex, ulong rawToken, ulong* pRetVal)
    {
        *pRetVal = 0;
        int hr = HResults.S_OK;
        try
        {
            if (dwExactGenericArgsTokenIndex == 0)
            {
                // In a rare case of VS4Mac debugging VS4Mac ARM64 optimized code we get a null
                // generics argument token. We aren't sure why the token is null, it may be a bug
                // or it may be by design in the runtime. In the interest of time we are working
                // around the issue rather than investigating the root cause.
                if (rawToken == 0)
                {
                    *pRetVal = rawToken;
                }
                else
                {
                    // The real generics type token is the MethodTable of the "this" object.
                    // The incoming rawToken is a target address of the 'this' Object.
                    *pRetVal = _target.Contracts.Object.GetMethodTableAddress(new TargetPointer(rawToken)).Value;
                }
            }
            else if (dwExactGenericArgsTokenIndex == unchecked((uint)IlNum.TYPECTXT_ILNUM))
            {
                // rawToken is already the real generics type token. Nothing to do.
                *pRetVal = rawToken;
            }
            else
            {
                // The index of the generics type token should not be anything else.
                Debug.Fail($"Unexpected generics type token index: {dwExactGenericArgsTokenIndex}");
                throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_TARGET_INCONSISTENT)!;
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            ulong retValLocal;
            int hrLocal = _legacy.ResolveExactGenericArgsToken(dwExactGenericArgsTokenIndex, rawToken, &retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}");
        }
#endif
        return hr;
    }

    public int GetILCodeAndSig(ulong vmAssembly, uint functionToken, DacDbiTargetBuffer* pTargetBuffer, uint* pLocalSigToken)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetILCodeAndSig(vmAssembly, functionToken, pTargetBuffer, pLocalSigToken) : HResults.E_NOTIMPL;

    public int GetNativeCodeInfo(ulong vmAssembly, uint functionToken, nint pJitManagerList)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetNativeCodeInfo(vmAssembly, functionToken, pJitManagerList) : HResults.E_NOTIMPL;

    public int GetNativeCodeInfoForAddr(ulong codeAddress, nint pCodeInfo, ulong* pVmModule, uint* pFunctionToken)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetNativeCodeInfoForAddr(codeAddress, pCodeInfo, pVmModule, pFunctionToken) : HResults.E_NOTIMPL;

    public int IsValueType(ulong vmTypeHandle, Interop.BOOL* pResult)
    {
        *pResult = Interop.BOOL.FALSE;
        int hr = HResults.S_OK;
        try
        {
            Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
            Contracts.TypeHandle th = rts.GetTypeHandle(new TargetPointer(vmTypeHandle));
            *pResult = rts.IsValueType(th) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            Interop.BOOL resultLocal;
            int hrLocal = _legacy.IsValueType(vmTypeHandle, &resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}");
        }
#endif
        return hr;
    }

    public int HasTypeParams(ulong vmTypeHandle, Interop.BOOL* pResult)
    {
        *pResult = Interop.BOOL.FALSE;
        int hr = HResults.S_OK;
        try
        {
            Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
            TypeHandle typeHandle = rts.GetTypeHandle(new TargetPointer(vmTypeHandle));
            *pResult = rts.ContainsGenericVariables(typeHandle) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            Interop.BOOL resultLocal;
            int hrLocal = _legacy.HasTypeParams(vmTypeHandle, &resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}");
        }
#endif
        return hr;
    }

    public int GetClassInfo(ulong thExact, nint pData)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetClassInfo(thExact, pData) : HResults.E_NOTIMPL;

    public int GetInstantiationFieldInfo(ulong vmAssembly, ulong vmTypeHandle, ulong vmExactMethodTable, nint pFieldList, nuint* pObjectSize)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetInstantiationFieldInfo(vmAssembly, vmTypeHandle, vmExactMethodTable, pFieldList, pObjectSize) : HResults.E_NOTIMPL;

    public int TypeHandleToExpandedTypeInfo(int boxed, ulong vmTypeHandle, nint pData)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.TypeHandleToExpandedTypeInfo(boxed, vmTypeHandle, pData) : HResults.E_NOTIMPL;

    public int GetObjectExpandedTypeInfo(int boxed, ulong addr, nint pTypeInfo)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetObjectExpandedTypeInfo(boxed, addr, pTypeInfo) : HResults.E_NOTIMPL;

    public int GetObjectExpandedTypeInfoFromID(int boxed, COR_TYPEID id, nint pTypeInfo)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetObjectExpandedTypeInfoFromID(boxed, id, pTypeInfo) : HResults.E_NOTIMPL;

    public int GetTypeHandle(ulong vmModule, uint metadataToken, ulong* pRetVal)
    {
        *pRetVal = 0;
        int hr = HResults.S_OK;
        try
        {
            Contracts.ILoader loader = _target.Contracts.Loader;
            TargetPointer module = new TargetPointer(vmModule);
            Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(module);
            Contracts.ModuleLookupTables lookupTables = loader.GetLookupTables(moduleHandle);
            switch ((EcmaMetadataUtils.TokenType)(metadataToken & EcmaMetadataUtils.TokenTypeMask))
            {
                case EcmaMetadataUtils.TokenType.mdtTypeDef:
                    *pRetVal = loader.GetModuleLookupMapElement(lookupTables.TypeDefToMethodTable, metadataToken, out var _).Value;
                    break;
                case EcmaMetadataUtils.TokenType.mdtTypeRef:
                    *pRetVal = loader.GetModuleLookupMapElement(lookupTables.TypeRefToMethodTable, metadataToken, out var _).Value;
                    break;
                default:
                    throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!;
            }
            if (*pRetVal == 0)
                throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            ulong retValLocal;
            int hrLocal = _legacy.GetTypeHandle(vmModule, metadataToken, &retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}");
        }
#endif
        return hr;
    }

    public int GetApproxTypeHandle(nint pTypeData, ulong* pRetVal)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetApproxTypeHandle(pTypeData, pRetVal) : HResults.E_NOTIMPL;

    public int GetExactTypeHandle(nint pTypeData, nint pArgInfo, ulong* pVmTypeHandle)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetExactTypeHandle(pTypeData, pArgInfo, pVmTypeHandle) : HResults.E_NOTIMPL;

    public int GetMethodDescParams(ulong vmMethodDesc, ulong genericsToken, uint* pcGenericClassTypeParams, nint pGenericTypeParams)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetMethodDescParams(vmMethodDesc, genericsToken, pcGenericClassTypeParams, pGenericTypeParams) : HResults.E_NOTIMPL;

    public int GetThreadStaticAddress(ulong vmField, ulong vmRuntimeThread, ulong* pRetVal)
    {
        *pRetVal = 0;
        int hr = HResults.S_OK;
        try
        {
            Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
            TargetPointer fd = new TargetPointer(vmField);
            if (vmRuntimeThread == 0)
                throw new ArgumentException("vmRuntimeThread cannot be null for thread static fields");
            if (!rts.IsFieldDescThreadStatic(fd))
            {
                throw new NotImplementedException();
            }
            *pRetVal = rts.GetFieldDescThreadStaticAddress(fd, new TargetPointer(vmRuntimeThread)).Value;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            ulong retValLocal;
            int hrLocal = _legacy.GetThreadStaticAddress(vmField, vmRuntimeThread, &retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}");
        }
#endif
        return hr;
    }

    public int GetCollectibleTypeStaticAddress(ulong vmField, ulong* pRetVal)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetCollectibleTypeStaticAddress(vmField, pRetVal) : HResults.E_NOTIMPL;

    public int GetEnCHangingFieldInfo(nint pEnCFieldInfo, nint pFieldData, Interop.BOOL* pfStatic)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetEnCHangingFieldInfo(pEnCFieldInfo, pFieldData, pfStatic) : HResults.E_NOTIMPL;

    public int GetTypeHandleParams(ulong vmTypeHandle, nint pParams)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetTypeHandleParams(vmTypeHandle, pParams) : HResults.E_NOTIMPL;

    public int GetSimpleType(int simpleType, uint* pMetadataToken, ulong* pVmModule)
    {
        Debug.Assert(pVmModule != null);
        *pVmModule = 0;
        int hr = HResults.S_OK;
        try
        {
            Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
            Contracts.TypeHandle typeHandle = rts.GetPrimitiveType((CorElementType)simpleType);

            if (typeHandle.IsNull)
            {
                throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!;
            }

            Debug.Assert(pMetadataToken != null);
            *pMetadataToken = rts.GetTypeDefToken(typeHandle);

            TargetPointer module = rts.GetModule(typeHandle);
            if (module == TargetPointer.Null)
                throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_TARGET_INCONSISTENT)!;

            *pVmModule = module.Value;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            uint metadataTokenLocal;
            ulong vmModuleLocal;
            int hrLocal = _legacy.GetSimpleType(simpleType, &metadataTokenLocal, &vmModuleLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                Debug.Assert(*pMetadataToken == metadataTokenLocal, $"cDAC: {*pMetadataToken}, DAC: {metadataTokenLocal}");
                Debug.Assert(*pVmModule == vmModuleLocal, $"cDAC: {*pVmModule}, DAC: {vmModuleLocal}");
            }
        }
#endif
        return hr;
    }

    public int IsExceptionObject(ulong vmObject, Interop.BOOL* pResult)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.IsExceptionObject(vmObject, pResult) : HResults.E_NOTIMPL;

    public int GetStackFramesFromException(ulong vmObject, nint pDacStackFrames)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetStackFramesFromException(vmObject, pDacStackFrames) : HResults.E_NOTIMPL;

    public int IsRcw(ulong vmObject, Interop.BOOL* pResult)
    {
        *pResult = Interop.BOOL.FALSE;
        int hr = HResults.S_OK;
        try
        {
            IObject obj = _target.Contracts.Object;
            _ = obj.GetBuiltInComData(new TargetPointer(vmObject), out TargetPointer rcw, out _, out _);
            *pResult = rcw != TargetPointer.Null ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            Interop.BOOL resultLocal;
            int hrLocal = _legacy.IsRcw(vmObject, &resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}");
        }
#endif
        return hr;
    }

    public int GetRcwCachedInterfacePointers(ulong vmObject, Interop.BOOL bIInspectableOnly, nint pDacItfPtrs)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetRcwCachedInterfacePointers(vmObject, bIInspectableOnly, pDacItfPtrs) : HResults.E_NOTIMPL;

    public int GetTypedByRefInfo(ulong pTypedByRef, nint pObjectData)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetTypedByRefInfo(pTypedByRef, pObjectData) : HResults.E_NOTIMPL;

    public int GetStringData(ulong objectAddress, nint pObjectData)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetStringData(objectAddress, pObjectData) : HResults.E_NOTIMPL;

    public int GetArrayData(ulong objectAddress, nint pObjectData)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetArrayData(objectAddress, pObjectData) : HResults.E_NOTIMPL;

    public int GetBasicObjectInfo(ulong objectAddress, int type, nint pObjectData)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetBasicObjectInfo(objectAddress, type, pObjectData) : HResults.E_NOTIMPL;

    public int GetDebuggerControlBlockAddress(ulong* pRetVal)
    {
        *pRetVal = 0;
        int hr = HResults.S_OK;
        try
        {
            *pRetVal = _target.Contracts.Debugger.GetDebuggerControlBlockAddress().Value;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            ulong retValLocal;
            int hrLocal = _legacy.GetDebuggerControlBlockAddress(&retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal:x}, DAC: {retValLocal:x}");
        }
#endif
        return hr;
    }

    public int GetObjectFromRefPtr(ulong ptr, ulong* pRetVal)
    {
        *pRetVal = 0;
        int hr = HResults.S_OK;
        try
        {
            *pRetVal = _target.ReadPointer(new TargetPointer(ptr)).Value;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            ulong retValLocal;
            int hrLocal = _legacy.GetObjectFromRefPtr(ptr, &retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal:x}, DAC: {retValLocal:x}");
        }
#endif
        return hr;
    }

    public int GetObject(ulong ptr, ulong* pRetVal)
    {
        // Native GetObject wraps the address directly in a VMPTR_Object without dereferencing.
        *pRetVal = ptr;
        int hr = HResults.S_OK;
#if DEBUG
        if (_legacy is not null)
        {
            ulong retValLocal;
            int hrLocal = _legacy.GetObject(ptr, &retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal:x}, DAC: {retValLocal:x}");
        }
#endif
        return hr;
    }

    public int GetVmObjectHandle(ulong handleAddress, ulong* pRetVal)
    {
        *pRetVal = handleAddress;
        int hr = HResults.S_OK;
#if DEBUG
        if (_legacy is not null)
        {
            ulong retValLocal;
            int hrLocal = _legacy.GetVmObjectHandle(handleAddress, &retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal:x}, DAC: {retValLocal:x}");
        }
#endif
        return hr;
    }

    public int IsVmObjectHandleValid(ulong vmHandle, Interop.BOOL* pResult)
    {
        *pResult = Interop.BOOL.FALSE;
        int hr = HResults.S_OK;
        try
        {
            TargetPointer obj = _target.ReadPointer(new TargetPointer(vmHandle));
            *pResult = obj != TargetPointer.Null ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            Interop.BOOL resultLocal;
            int hrLocal = _legacy.IsVmObjectHandleValid(vmHandle, &resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}");
        }
#endif
        return hr;
    }

    public int GetHandleAddressFromVmHandle(ulong vmHandle, ulong* pRetVal)
    {
        *pRetVal = vmHandle;
        int hr = HResults.S_OK;
#if DEBUG
        if (_legacy is not null)
        {
            ulong retValLocal;
            int hrLocal = _legacy.GetHandleAddressFromVmHandle(vmHandle, &retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal:x}, DAC: {retValLocal:x}");
        }
#endif
        return hr;
    }

    public int GetObjectContents(ulong obj, DacDbiTargetBuffer* pRetVal)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetObjectContents(obj, pRetVal) : HResults.E_NOTIMPL;

    public int GetThreadOwningMonitorLock(ulong vmObject, DacDbiMonitorLockInfo* pRetVal)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetThreadOwningMonitorLock(vmObject, pRetVal) : HResults.E_NOTIMPL;

    public int EnumerateMonitorEventWaitList(ulong vmObject, nint fpCallback, nint pUserData)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.EnumerateMonitorEventWaitList(vmObject, fpCallback, pUserData) : HResults.E_NOTIMPL;

    public int GetAttachStateFlags(int* pRetVal)
    {
        *pRetVal = 0;
        int hr = HResults.S_OK;
        try
        {
            *pRetVal = _target.Contracts.Debugger.GetAttachStateFlags();
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacy is not null)
        {
            int resultLocal;
            int hrLocal = _legacy.GetAttachStateFlags(&resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == resultLocal);
        }
#endif

        return hr;
    }

    public int GetMetaDataFileInfoFromPEFile(ulong vmPEAssembly, uint* dwTimeStamp, uint* dwImageSize, nint pStrFilename, Interop.BOOL* pResult)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetMetaDataFileInfoFromPEFile(vmPEAssembly, dwTimeStamp, dwImageSize, pStrFilename, pResult) : HResults.E_NOTIMPL;

    public int IsThreadSuspendedOrHijacked(ulong vmThread, Interop.BOOL* pResult)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.IsThreadSuspendedOrHijacked(vmThread, pResult) : HResults.E_NOTIMPL;

    public int CreateHeapWalk(nuint* pHandle)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.CreateHeapWalk(pHandle) : HResults.E_NOTIMPL;

    public int DeleteHeapWalk(nuint handle)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.DeleteHeapWalk(handle) : HResults.E_NOTIMPL;

    public int WalkHeap(nuint handle, uint count, COR_HEAPOBJECT* objects, uint* fetched)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.WalkHeap(handle, count, objects, fetched) : HResults.E_NOTIMPL;

#if DEBUG
    [ThreadStatic]
    private static List<(ulong Start, ulong End, int Generation, uint Heap)>? _debugEnumerateHeapSegments;

    private static List<(ulong Start, ulong End, int Generation, uint Heap)> DebugEnumerateHeapSegments
        => _debugEnumerateHeapSegments ??= new();

    [UnmanagedCallersOnly]
    private static void EnumerateHeapSegmentsDebugCallback(ulong start, ulong end, int generation, uint heap, nint _)
    {
        DebugEnumerateHeapSegments.Add((start, end, generation, heap));
    }
#endif

    public int EnumerateHeapSegments(delegate* unmanaged<ulong, ulong, int, uint, nint, void> fpCallback, nint pUserData)
    {
        int hr = HResults.S_OK;
        List<(ulong Start, ulong End, int Generation, uint Heap)> segments = new();
        try
        {
            if (fpCallback is null)
                throw new ArgumentNullException(nameof(fpCallback));

            IGC gc = _target.Contracts.GC;
            string[] gcIdentifiers = gc.GetGCIdentifiers();
            bool regions = gcIdentifiers.Contains(GCIdentifiers.Regions);
            bool isWorkstation = gcIdentifiers.Contains(GCIdentifiers.Workstation);

            uint heapIndex = 0;
            foreach (GCHeapData heapData in EnumerateHeaps(gc, isWorkstation))
            {
                TargetPointer gen0AllocStart = heapData.GenerationTable[0].AllocationStart;
                TargetPointer gen1AllocStart = heapData.GenerationTable[1].AllocationStart;

                // In segments mode, Gen0 lives outside the segment list - synthesize it as a
                // heap-level entry bracketed by [gen0.AllocationStart, alloc_allocated).
                if (!regions)
                    segments.Add((gen0AllocStart.Value, heapData.AllocAllocated.Value, (int)CorDebugGenerationTypes.CorDebug_Gen0, heapIndex));

                foreach (GCHeapSegmentInfo raw in gc.EnumerateHeapSegments(heapData))
                {
                    if (raw.Generation != GCSegmentClassification.Ephemeral)
                    {
                        segments.Add((raw.Start.Value, raw.End.Value, (int)ToCorDebugGenerationType(raw.Generation), heapIndex));
                    }
                    else
                    {
                        // Segments mode only: split the ephemeral marker into the Gen1 piece
                        // ([gen1.AllocationStart, gen0.AllocationStart)) plus an optional Gen2
                        // prefix ([raw.Start, gen1.AllocationStart)).
                        segments.Add((gen1AllocStart.Value, gen0AllocStart.Value, (int)CorDebugGenerationTypes.CorDebug_Gen1, heapIndex));
                        if (raw.Start != gen1AllocStart)
                            segments.Add((raw.Start.Value, gen1AllocStart.Value, (int)CorDebugGenerationTypes.CorDebug_Gen2, heapIndex));
                    }
                }

                heapIndex++;
            }

            foreach ((ulong start, ulong end, int generation, uint heap) in segments)
                fpCallback(start, end, generation, heap, pUserData);
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacy is not null)
        {
            DebugEnumerateHeapSegments.Clear();
            delegate* unmanaged<ulong, ulong, int, uint, nint, void> debugCallbackPtr = &EnumerateHeapSegmentsDebugCallback;
            int hrLocal = _legacy.EnumerateHeapSegments(debugCallbackPtr, 0);
            Debug.ValidateHResult(hr, hrLocal);

            if (hr == HResults.S_OK && hrLocal == HResults.S_OK)
            {
                List<(ulong Start, ulong End, int Generation, uint Heap)> legacySegments = DebugEnumerateHeapSegments;
                if (!segments.SequenceEqual(legacySegments))
                {
                    Debug.Assert(segments.Count == legacySegments.Count,
                        $"cDAC: {segments.Count} segments, DAC: {legacySegments.Count} segments");

                    int compareCount = Math.Min(segments.Count, legacySegments.Count);
                    for (int i = 0; i < compareCount; i++)
                    {
                        Debug.Assert(segments[i] == legacySegments[i],
                            $"Segment {i} mismatch - cDAC: (0x{segments[i].Start:x}, 0x{segments[i].End:x}, gen={segments[i].Generation}, heap={segments[i].Heap}), DAC: (0x{legacySegments[i].Start:x}, 0x{legacySegments[i].End:x}, gen={legacySegments[i].Generation}, heap={legacySegments[i].Heap})");
                    }
                }
            }
            DebugEnumerateHeapSegments.Clear();
        }
#endif
        return hr;
    }

    private static IEnumerable<GCHeapData> EnumerateHeaps(IGC gc, bool isWorkstation)
    {
        if (isWorkstation)
        {
            yield return gc.GetHeapData();
        }
        else
        {
            foreach (TargetPointer heapAddress in gc.GetGCHeaps())
                yield return gc.GetHeapData(heapAddress);
        }
    }

    private static CorDebugGenerationTypes ToCorDebugGenerationType(GCSegmentClassification generation) => generation switch
    {
        GCSegmentClassification.Gen0 => CorDebugGenerationTypes.CorDebug_Gen0,
        GCSegmentClassification.Gen1 => CorDebugGenerationTypes.CorDebug_Gen1,
        GCSegmentClassification.Gen2 => CorDebugGenerationTypes.CorDebug_Gen2,
        GCSegmentClassification.LOH => CorDebugGenerationTypes.CorDebug_LOH,
        GCSegmentClassification.POH => CorDebugGenerationTypes.CorDebug_POH,
        GCSegmentClassification.NonGC => CorDebugGenerationTypes.CorDebug_NonGC,
        // Ephemeral is an internal marker that must be split by the caller; it never appears in
        // emitted output.
        _ => throw new ArgumentOutOfRangeException(nameof(generation), generation, null),
    };

    public int IsValidObject(ulong obj, Interop.BOOL* pResult)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.IsValidObject(obj, pResult) : HResults.E_NOTIMPL;

    public int CreateRefWalk(nuint* pHandle, Interop.BOOL walkStacks, Interop.BOOL walkFQ, uint handleWalkMask)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.CreateRefWalk(pHandle, walkStacks, walkFQ, handleWalkMask) : HResults.E_NOTIMPL;

    public int DeleteRefWalk(nuint handle)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.DeleteRefWalk(handle) : HResults.E_NOTIMPL;

    public int WalkRefs(nuint handle, uint count, nint refs, uint* pFetched)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.WalkRefs(handle, count, refs, pFetched) : HResults.E_NOTIMPL;

    public int GetTypeID(ulong obj, COR_TYPEID* pType)
    {
        *pType = default;
        int hr = HResults.S_OK;
        try
        {
            TargetPointer mt = _target.Contracts.Object.GetMethodTableAddress(new TargetPointer(obj));
            pType->token1 = mt.Value;
            pType->token2 = 0;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacy is not null)
        {
            COR_TYPEID resultLocal;
            int hrLocal = _legacy.GetTypeID(obj, &resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                Debug.Assert(pType->token1 == resultLocal.token1);
                Debug.Assert(pType->token2 == resultLocal.token2);
            }
        }
#endif

        return hr;
    }

    public int GetTypeIDForType(ulong vmTypeHandle, COR_TYPEID* pId)
    {
        *pId = default;
        int hr = HResults.S_OK;
        try
        {
            pId->token1 = vmTypeHandle;
            pId->token2 = 0;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacy is not null)
        {
            COR_TYPEID resultLocal;
            int hrLocal = _legacy.GetTypeIDForType(vmTypeHandle, &resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                Debug.Assert(pId->token1 == resultLocal.token1);
                Debug.Assert(pId->token2 == resultLocal.token2);
            }
        }
#endif

        return hr;
    }

    public int GetObjectFields(nint id, uint celt, COR_FIELD* layout, uint* pceltFetched)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetObjectFields(id, celt, layout, pceltFetched) : HResults.E_NOTIMPL;

    public int GetTypeLayout(ulong id, COR_TYPE_LAYOUT* pLayout)
    {
        int hr = HResults.S_OK;
        try
        {
            if (pLayout is null)
                throw new NullReferenceException(nameof(pLayout));

            if (id == 0)
                throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!;

            IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
            TypeHandle typeHandle = rts.GetTypeHandle(new TargetPointer((ulong)id));

            TargetPointer parentMT = rts.GetParentMethodTable(typeHandle);
            pLayout->parentID.token1 = parentMT.Value;
            pLayout->parentID.token2 = 0;
            pLayout->objectSize = rts.GetBaseSize(typeHandle);
            ushort numInstanceFields = rts.GetNumInstanceFields(typeHandle);
            if (parentMT != TargetPointer.Null)
            {
                TypeHandle parentHandle = rts.GetTypeHandle(parentMT);
                numInstanceFields -= rts.GetNumInstanceFields(parentHandle);
            }
            pLayout->numFields = numInstanceFields;
            pLayout->boxOffset = rts.IsObjRef(typeHandle) ? 0u : (uint)_target.PointerSize;
            pLayout->type = (int)(rts.IsString(typeHandle) ? CorElementType.String : rts.GetInternalCorElementType(typeHandle));
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacy is not null)
        {
            COR_TYPE_LAYOUT resultLocal;
            int hrLocal = _legacy.GetTypeLayout(id, &resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                Debug.Assert(pLayout->parentID.token1 == resultLocal.parentID.token1, $"cDAC: {pLayout->parentID.token1:x}, DAC: {resultLocal.parentID.token1:x}");
                Debug.Assert(pLayout->parentID.token2 == resultLocal.parentID.token2, $"cDAC: {pLayout->parentID.token2:x}, DAC: {resultLocal.parentID.token2:x}");
                Debug.Assert(pLayout->objectSize == resultLocal.objectSize, $"cDAC: {pLayout->objectSize}, DAC: {resultLocal.objectSize}");
                Debug.Assert(pLayout->numFields == resultLocal.numFields, $"cDAC: {pLayout->numFields}, DAC: {resultLocal.numFields}");
                Debug.Assert(pLayout->boxOffset == resultLocal.boxOffset, $"cDAC: {pLayout->boxOffset}, DAC: {resultLocal.boxOffset}");
                Debug.Assert(pLayout->type == resultLocal.type, $"cDAC: {pLayout->type}, DAC: {resultLocal.type}");
            }
        }
#endif

        return hr;
    }

    public int GetArrayLayout(ulong id, COR_ARRAY_LAYOUT* pLayout)
    {
        int hr = HResults.S_OK;
        try
        {
            if (pLayout is null)
                throw new NullReferenceException(nameof(pLayout));

            if (id == 0)
                throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!;
            IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
            TypeHandle arrayOrStringTypeHandle = rts.GetTypeHandle(new TargetPointer(id));
            uint pointerSize = (uint)_target.PointerSize;

            if (rts.IsString(arrayOrStringTypeHandle))
            {
                TypeHandle charTypeHandle = rts.GetPrimitiveType(CorElementType.Char);
                pLayout->componentID.token1 = charTypeHandle.Address.Value;
                pLayout->componentID.token2 = 0;
                pLayout->componentType = CorElementType.Char;
                pLayout->firstElementOffset = pointerSize + 4;
                pLayout->elementSize = sizeof(char);
                pLayout->countOffset = pointerSize;
                pLayout->rankSize = 4;
                pLayout->numRanks = 1;
                pLayout->rankOffset = pointerSize;
            }
            else
            {
                if (!rts.IsArray(arrayOrStringTypeHandle, out uint rank))
                    throw Marshal.GetExceptionForHR(HResults.E_INVALIDARG)!;

                TypeHandle componentTypeHandle = rts.GetTypeParam(arrayOrStringTypeHandle);
                CorElementType componentType = rts.IsString(componentTypeHandle) ? CorElementType.String : rts.GetInternalCorElementType(componentTypeHandle);
                pLayout->componentID.token1 = componentTypeHandle.Address.Value;
                pLayout->componentID.token2 = 0;
                pLayout->componentType = componentType;
                Target.TypeInfo objectHeaderTypeInfo = _target.GetTypeInfo(DataType.ObjectHeader);
                uint objectHeaderSize = (uint)objectHeaderTypeInfo.Size!.Value;
                pLayout->firstElementOffset = rts.GetBaseSize(arrayOrStringTypeHandle) - objectHeaderSize;
                pLayout->elementSize = rts.GetComponentSize(arrayOrStringTypeHandle);
                pLayout->countOffset = pointerSize;
                pLayout->rankSize = 4;
                pLayout->numRanks = rank;
                pLayout->rankOffset = rank > 1 ? pointerSize * 2 : pointerSize;
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacy is not null)
        {
            COR_ARRAY_LAYOUT resultLocal;
            int hrLocal = _legacy.GetArrayLayout(id, &resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                Debug.Assert(pLayout->componentID.token1 == resultLocal.componentID.token1, $"cDAC: {pLayout->componentID.token1:x}, DAC: {resultLocal.componentID.token1:x}");
                Debug.Assert(pLayout->componentID.token2 == resultLocal.componentID.token2, $"cDAC: {pLayout->componentID.token2:x}, DAC: {resultLocal.componentID.token2:x}");
                Debug.Assert(pLayout->componentType == resultLocal.componentType, $"cDAC: {pLayout->componentType}, DAC: {resultLocal.componentType}");
                Debug.Assert(pLayout->firstElementOffset == resultLocal.firstElementOffset, $"cDAC: {pLayout->firstElementOffset}, DAC: {resultLocal.firstElementOffset}");
                Debug.Assert(pLayout->elementSize == resultLocal.elementSize, $"cDAC: {pLayout->elementSize}, DAC: {resultLocal.elementSize}");
                Debug.Assert(pLayout->countOffset == resultLocal.countOffset, $"cDAC: {pLayout->countOffset}, DAC: {resultLocal.countOffset}");
                Debug.Assert(pLayout->rankSize == resultLocal.rankSize, $"cDAC: {pLayout->rankSize}, DAC: {resultLocal.rankSize}");
                Debug.Assert(pLayout->numRanks == resultLocal.numRanks, $"cDAC: {pLayout->numRanks}, DAC: {resultLocal.numRanks}");
                Debug.Assert(pLayout->rankOffset == resultLocal.rankOffset, $"cDAC: {pLayout->rankOffset}, DAC: {resultLocal.rankOffset}");
            }
        }
#endif

        return hr;
    }

    public int GetGCHeapInformation(COR_HEAPINFO* pHeapInfo)
    {
        *pHeapInfo = default;
        int hr = HResults.S_OK;
        try
        {
            Contracts.IGC gc = _target.Contracts.GC;
            pHeapInfo->areGCStructuresValid = gc.GetGCStructuresValid() ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
            pHeapInfo->numHeaps = gc.GetGCHeapCount();
            pHeapInfo->pointerSize = (uint)_target.PointerSize;

            string[] identifiers = gc.GetGCIdentifiers();
            bool isServer = identifiers.Contains(GCIdentifiers.Server);
            pHeapInfo->gcType = isServer ? 1 : 0; // CorDebugServerGC = 1, CorDebugWorkstationGC = 0
            pHeapInfo->concurrent = identifiers.Contains(GCIdentifiers.Background) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacy is not null)
        {
            COR_HEAPINFO resultLocal;
            int hrLocal = _legacy.GetGCHeapInformation(&resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                Debug.Assert(pHeapInfo->areGCStructuresValid == resultLocal.areGCStructuresValid);
                Debug.Assert(pHeapInfo->numHeaps == resultLocal.numHeaps);
                Debug.Assert(pHeapInfo->pointerSize == resultLocal.pointerSize);
                Debug.Assert(pHeapInfo->gcType == resultLocal.gcType);
                Debug.Assert(pHeapInfo->concurrent == resultLocal.concurrent);
            }
        }
#endif

        return hr;
    }

    public int GetPEFileMDInternalRW(ulong vmPEAssembly, ulong* pAddrMDInternalRW)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetPEFileMDInternalRW(vmPEAssembly, pAddrMDInternalRW) : HResults.E_NOTIMPL;

    public int AreOptimizationsDisabled(ulong vmModule, uint methodTk, Interop.BOOL* pOptimizationsDisabled)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.AreOptimizationsDisabled(vmModule, methodTk, pOptimizationsDisabled) : HResults.E_NOTIMPL;

    public int GetDefinesBitField(uint* pDefines)
    {
        *pDefines = 0;
        int hr = HResults.S_OK;
        try
        {
            if (!_target.Contracts.Debugger.TryGetDebuggerData(out Contracts.DebuggerData data))
                throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_NOTREADY)!;
            *pDefines = data.DefinesBitField;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacy is not null)
        {
            uint resultLocal;
            int hrLocal = _legacy.GetDefinesBitField(&resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pDefines == resultLocal);
        }
#endif

        return hr;
    }

    public int GetMDStructuresVersion(uint* pMDStructuresVersion)
    {
        *pMDStructuresVersion = 0;
        int hr = HResults.S_OK;
        try
        {
            if (!_target.Contracts.Debugger.TryGetDebuggerData(out Contracts.DebuggerData data))
                throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_NOTREADY)!;
            *pMDStructuresVersion = data.MDStructuresVersion;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacy is not null)
        {
            uint resultLocal;
            int hrLocal = _legacy.GetMDStructuresVersion(&resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pMDStructuresVersion == resultLocal);
        }
#endif

        return hr;
    }

    public int GetActiveRejitILCodeVersionNode(ulong vmModule, uint methodTk, ulong* pVmILCodeVersionNode)
    {
        int hr = HResults.S_OK;
        try
        {
            if (pVmILCodeVersionNode is null)
                throw new ArgumentException("Output pointer cannot be null.", nameof(pVmILCodeVersionNode));

            *pVmILCodeVersionNode = 0;

            if (!_target.Contracts.TryGetContract<IReJIT>(out IReJIT rejit))
                return hr;

            ILoader loader = _target.Contracts.Loader;
            Contracts.ModuleHandle module = loader.GetModuleHandleFromModulePtr(new TargetPointer(vmModule));
            ModuleLookupTables lookupTables = loader.GetLookupTables(module);
            TargetPointer methodDesc = TargetPointer.Null;

            if ((EcmaMetadataUtils.TokenType)(methodTk & EcmaMetadataUtils.TokenTypeMask) != EcmaMetadataUtils.TokenType.mdtMethodDef)
                throw new ArgumentException("methodTk must be a MethodDef token.", nameof(methodTk));
            methodDesc = loader.GetModuleLookupMapElement(lookupTables.MethodDefToDesc, methodTk, out _);

            if (methodDesc != TargetPointer.Null)
            {
                ICodeVersions codeVersions = _target.Contracts.CodeVersions;
                ILCodeVersionHandle ilCodeVersion = codeVersions.GetActiveILCodeVersion(methodDesc);
                if (ilCodeVersion.IsValid
                    && ilCodeVersion.IsExplicit
                    && rejit.GetRejitState(ilCodeVersion) == RejitState.Active)
                {
                    *pVmILCodeVersionNode = ilCodeVersion.ILCodeVersionNode.Value;
                }
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacy is not null)
        {
            ulong resultLocal;
            int hrLocal = _legacy.GetActiveRejitILCodeVersionNode(vmModule, methodTk, &resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pVmILCodeVersionNode == resultLocal, $"cDAC: {*pVmILCodeVersionNode:x}, DAC: {resultLocal:x}");
        }
#endif

        return hr;
    }

    public int GetNativeCodeVersionNode(ulong vmMethod, ulong codeStartAddress, ulong* pVmNativeCodeVersionNode)
    {
        int hr = HResults.S_OK;
        try
        {
            if (pVmNativeCodeVersionNode is null)
                throw new ArgumentException("Output pointer cannot be null.", nameof(pVmNativeCodeVersionNode));

            *pVmNativeCodeVersionNode = 0;

            TargetCodePointer codeAddress = new TargetCodePointer(codeStartAddress);
            ICodeVersions codeVersions = _target.Contracts.CodeVersions;

            NativeCodeVersionHandle nativeCodeVersion = codeVersions.GetNativeCodeVersionForIP(codeAddress);
            if (nativeCodeVersion.Valid && nativeCodeVersion.IsExplicit)
            {
                *pVmNativeCodeVersionNode = nativeCodeVersion.CodeVersionNodeAddress.Value;
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacy is not null)
        {
            ulong resultLocal;
            int hrLocal = _legacy.GetNativeCodeVersionNode(vmMethod, codeStartAddress, &resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pVmNativeCodeVersionNode == resultLocal, $"cDAC: {*pVmNativeCodeVersionNode:x}, DAC: {resultLocal:x}");
        }
#endif

        return hr;
    }

    public int GetILCodeVersionNode(ulong vmNativeCodeVersionNode, ulong* pVmILCodeVersionNode)
    {
        int hr = HResults.S_OK;
        try
        {
            if (pVmILCodeVersionNode is null)
                throw new ArgumentException("Output pointer cannot be null.", nameof(pVmILCodeVersionNode));

            *pVmILCodeVersionNode = 0;

            ICodeVersions codeVersions = _target.Contracts.CodeVersions;
            NativeCodeVersionHandle nativeCodeVersion = NativeCodeVersionHandle.CreateExplicit(new TargetPointer(vmNativeCodeVersionNode));
            ILCodeVersionHandle ilCodeVersion = codeVersions.GetILCodeVersion(nativeCodeVersion);
            if (ilCodeVersion.IsValid && ilCodeVersion.IsExplicit)
            {
                *pVmILCodeVersionNode = ilCodeVersion.ILCodeVersionNode.Value;
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacy is not null)
        {
            ulong resultLocal;
            int hrLocal = _legacy.GetILCodeVersionNode(vmNativeCodeVersionNode, &resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pVmILCodeVersionNode == resultLocal, $"cDAC: {*pVmILCodeVersionNode:x}, DAC: {resultLocal:x}");
        }
#endif

        return hr;
    }

    public int GetILCodeVersionNodeData(ulong ilCodeVersionNode, DacDbiSharedReJitInfo* pData)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetILCodeVersionNodeData(ilCodeVersionNode, pData) : HResults.E_NOTIMPL;

    public int EnableGCNotificationEvents(Interop.BOOL fEnable)
    {
        int hr = HResults.S_OK;
        try
        {
            _target.Contracts.Debugger.EnableGCNotificationEvents(fEnable != Interop.BOOL.FALSE);
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            int hrLocal = _legacy.EnableGCNotificationEvents(fEnable);
            Debug.ValidateHResult(hr, hrLocal);
        }
#endif
        return hr;
    }

    public int IsDelegate(ulong vmObject, Interop.BOOL* pResult)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.IsDelegate(vmObject, pResult) : HResults.E_NOTIMPL;

    public int GetDelegateType(ulong delegateObject, int* delegateType)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetDelegateType(delegateObject, delegateType) : HResults.E_NOTIMPL;

    public int GetDelegateFunctionData(int delegateType, ulong delegateObject, ulong* ppFunctionAssembly, uint* pMethodDef)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetDelegateFunctionData(delegateType, delegateObject, ppFunctionAssembly, pMethodDef) : HResults.E_NOTIMPL;

    public int GetDelegateTargetObject(int delegateType, ulong delegateObject, ulong* ppTargetObj, ulong* ppTargetAppDomain)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetDelegateTargetObject(delegateType, delegateObject, ppTargetObj, ppTargetAppDomain) : HResults.E_NOTIMPL;

    public int IsModuleMapped(ulong pModule, Interop.BOOL* isModuleMapped)
    {
        int hr = HResults.S_FALSE;
        try
        {
            if (pModule == 0)
                throw new ArgumentException("Module pointer must not be zero.", nameof(pModule));

            if (isModuleMapped == null)
                throw new ArgumentNullException(nameof(isModuleMapped));

            *isModuleMapped = Interop.BOOL.FALSE;
            Contracts.ILoader loader = _target.Contracts.Loader;
            Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(new TargetPointer(pModule));
            if (loader.TryGetLoadedImageContents(handle, out _, out _, out _))
            {
                *isModuleMapped = loader.IsModuleMapped(handle) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
                hr = HResults.S_OK;
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            Interop.BOOL isModuleMappedLocal;
            int hrLocal = _legacy.IsModuleMapped(pModule, &isModuleMappedLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*isModuleMapped == isModuleMappedLocal, $"cDAC: {*isModuleMapped}, DAC: {isModuleMappedLocal}");
        }
#endif
        return hr;
    }

    public int MetadataUpdatesApplied(Interop.BOOL* pResult)
    {
        *pResult = Interop.BOOL.FALSE;
        int hr = HResults.S_OK;
        try
        {
            *pResult = _target.Contracts.Debugger.MetadataUpdatesApplied() ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacy is not null)
        {
            Interop.BOOL resultLocal;
            int hrLocal = _legacy.MetadataUpdatesApplied(&resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pResult == resultLocal);
        }
#endif

        return hr;
    }

    public int GetAssemblyFromModule(ulong vmModule, ulong* pVmAssembly)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetAssemblyFromModule(vmModule, pVmAssembly) : HResults.E_NOTIMPL;

    public int ParseContinuation(ulong continuationAddress, ulong* pDiagnosticIP, ulong* pNextContinuation, uint* pState)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.ParseContinuation(continuationAddress, pDiagnosticIP, pNextContinuation, pState) : HResults.E_NOTIMPL;

    public int GetAsyncLocals(ulong vmMethod, ulong codeAddr, uint state, nint pAsyncLocals)
        => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetAsyncLocals(vmMethod, codeAddr, state, pAsyncLocals) : HResults.E_NOTIMPL;

    public int GetGenericArgTokenIndex(ulong vmMethod, uint* pIndex)
    {
        int hr = HResults.S_OK;
        try
        {
            if (vmMethod == 0)
                throw new ArgumentException("vmMethod must not be zero.", nameof(vmMethod));
            if (pIndex is null)
                throw new ArgumentException("pIndex must not be null.", nameof(pIndex));
            Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
            Contracts.MethodDescHandle md = rts.GetMethodDescHandle(new TargetPointer(vmMethod));

            switch (rts.GetGenericContextLoc(md))
            {
                case GenericContextLoc.None:
                    hr = HResults.S_FALSE;
                    break;
                case GenericContextLoc.InstArg:
                    *pIndex = unchecked((uint)IlNum.TYPECTXT_ILNUM);
                    break;
                case GenericContextLoc.ThisPtr:
                    *pIndex = 0u;
                    break;
                default:
                    throw new InvalidOperationException($"Unexpected generic context location: {rts.GetGenericContextLoc(md)}");
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            uint indexLocal;
            int hrLocal = _legacy.GetGenericArgTokenIndex(vmMethod, &indexLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pIndex == indexLocal, $"cDAC: {*pIndex}, DAC: {indexLocal}");
        }
#endif
        return hr;
    }

}