File: Dbi\DacDbiImpl.cs
Web Access
Project: 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.Buffers.Binary;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using Microsoft.Diagnostics.DataContractReader.Contracts;
using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers;
using Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers;
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(FlushScope.All);
        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, SymbolFormat* pSymbolFormat)
    {
        int hr = HResults.S_OK;
        try
        {
            if (pTargetBuffer == null)
                throw new ArgumentNullException(nameof(pTargetBuffer));

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

            *pTargetBuffer = default;
            *pSymbolFormat = SymbolFormat.None;

            if (vmModule == 0)
                throw new ArgumentException("Module pointer must be non-zero.", nameof(vmModule));

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

            if (loader.TryGetSymbolStream(handle, out TargetPointer buffer, out uint size) && size != 0)
            {
                pTargetBuffer->pAddress = buffer.Value;
                pTargetBuffer->cbSize = size;
                *pSymbolFormat = SymbolFormat.Pdb;
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            DacDbiTargetBuffer bufferLocal;
            SymbolFormat formatLocal;
            int hrLocal = _legacy.GetSymbolsBuffer(vmModule, &bufferLocal, &formatLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                Debug.Assert(pTargetBuffer->pAddress == bufferLocal.pAddress, $"pAddress: cDAC: {pTargetBuffer->pAddress:x}, DAC: {bufferLocal.pAddress:x}");
                Debug.Assert(pTargetBuffer->cbSize == bufferLocal.cbSize, $"cbSize: cDAC: {pTargetBuffer->cbSize}, DAC: {bufferLocal.cbSize}");
                Debug.Assert(*pSymbolFormat == formatLocal, $"pSymbolFormat: cDAC: {*pSymbolFormat}, DAC: {formatLocal}");
            }
        }
#endif
        return hr;
    }

    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, Interop.BOOL* pIsModuleLoaded)
    {
        int hr = HResults.S_OK;
        try
        {
            if (pModule == null)
                throw new ArgumentNullException(nameof(pModule));

            *pModule = 0;
            if (pIsModuleLoaded != null)
                *pIsModuleLoaded = Interop.BOOL.FALSE;

            Contracts.ILoader loader = _target.Contracts.Loader;
            Contracts.ModuleHandle handle = loader.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmAssembly));
            TargetPointer modulePtr = loader.GetModule(handle);
            *pModule = modulePtr.Value;

            if (pIsModuleLoaded != null)
                *pIsModuleLoaded = loader.IsAssemblyLoaded(handle) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            ulong moduleLocal;
            Interop.BOOL isModuleLoadedLocal;
            int hrLocal = _legacy.GetModuleForAssembly(vmAssembly, &moduleLocal, &isModuleLoadedLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                Debug.Assert(*pModule == moduleLocal, $"cDAC: {*pModule:x}, DAC: {moduleLocal:x}");
                if (pIsModuleLoaded != null)
                    Debug.Assert(*pIsModuleLoaded == isModuleLoadedLocal, $"cDAC: {*pIsModuleLoaded}, DAC: {isModuleLoadedLocal}");
            }
        }
#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 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, void** pRetVal)
    {
        int hr = HResults.S_OK;
        try
        {
            Contracts.ThreadData threadData = _target.Contracts.Thread.GetThreadData(new TargetPointer(vmThread));
            *pRetVal = (void*)threadData.ThreadHandle.Value;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            void* retValLocal = null;
            int hrLocal = _legacy.GetThreadHandle(vmThread, &retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == retValLocal, $"cDAC: {(nuint)(*pRetVal):x}, DAC: {(nuint)retValLocal:x}");
        }
#endif
        return hr;
    }

    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)
    {
        int hr = HResults.S_OK;
        try
        {
            TargetPointer threadPtr = new TargetPointer(vmThread);
            Contracts.ThreadData threadData = _target.Contracts.Thread.GetThreadData(threadPtr);
            _target.Contracts.Thread.GetThreadAllocContext(threadPtr, out long allocBytes, out long allocBytesLoh);

            ulong limit = threadData.AllocContextLimit.Value;
            ulong pointer = threadData.AllocContextPointer.Value;
            pThreadAllocInfo->allocBytesSOH = (ulong)allocBytes - (limit - pointer);
            pThreadAllocInfo->allocBytesUOH = (ulong)allocBytesLoh;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            DacDbiThreadAllocInfo allocInfoLocal = default;
            int hrLocal = _legacy.GetThreadAllocInfo(vmThread, &allocInfoLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                Debug.Assert(pThreadAllocInfo->allocBytesSOH == allocInfoLocal.allocBytesSOH, $"cDAC: {pThreadAllocInfo->allocBytesSOH}, DAC: {allocInfoLocal.allocBytesSOH}");
                Debug.Assert(pThreadAllocInfo->allocBytesUOH == allocInfoLocal.allocBytesUOH, $"cDAC: {pThreadAllocInfo->allocBytesUOH}, DAC: {allocInfoLocal.allocBytesUOH}");
            }
        }
#endif
        return hr;
    }

    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)
    {
        int hr = HResults.S_OK;
        try
        {
            *pRetVal = 0;
            Contracts.IThread threadContract = _target.Contracts.Thread;
            Contracts.ThreadData threadData = threadContract.GetThreadData(vmThread);

            if (!threadData.IsInteropDebuggingHijacked)
            {
                TargetPointer filterContext = threadData.DebuggerFilterContext;
                if (filterContext != TargetPointer.Null)
                {
                    *pRetVal = filterContext.Value;
                }
                else
                {
                    IStackWalk sw = _target.Contracts.StackWalk;
                    TargetPointer redirectedContext = sw.GetRedirectedContextPointer(threadData);
                    if (redirectedContext != TargetPointer.Null)
                    {
                        *pRetVal = redirectedContext.Value;
                    }
                }
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            ulong pRetValLocal;
            int hrLocal = _legacy.GetManagedStoppedContext(vmThread, &pRetValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == pRetValLocal, $"cDAC: {*pRetVal:x}, DAC: {pRetValLocal:x}");
        }
#endif
        return hr;
    }

    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)
    {
        int hr = HResults.S_OK;
        try
        {
            IPlatformAgnosticContext ctx = IPlatformAgnosticContext.GetContextForPlatform(_target);
            ctx.FillFromBuffer(new Span<byte>((void*)pContext, (int)ctx.Size));

            if ((ctx.RawContextFlags & ctx.ContextControlFlags) != 0)
            {
                _target.Contracts.Thread.GetStackLimitData(new TargetPointer(vmThread), out TargetPointer stackBase, out TargetPointer stackLimit, out _);
                TargetPointer sp = ctx.StackPointer;
                if (sp < stackLimit || stackBase <= sp)
                {
                    hr = CorDbgHResults.CORDBG_E_NON_MATCHING_CONTEXT;
                }
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            int hrLocal = _legacy.CheckContext(vmThread, pContext);
            Debug.ValidateHResult(hr, hrLocal);
        }
#endif
        return hr;
    }

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

    // Filter used by GetCountOfInternalFrames and EnumerateInternalFrames to decide
    // which entries from IStackWalk.GetFrames should be surfaced to the DBI as internal frames.
    private static bool IsReportedInternalFrame(IStackWalk stackwalk, Contracts.StackFrameData frame)
    {
        return frame.InternalFrameType != Contracts.InternalFrameType.None
            && stackwalk.GetFrameName(frame.FrameIdentifier) != "InterpreterFrame"
            && !stackwalk.IsExceptionHandlingHelperInlinedCallFrame(frame.FrameAddress);
    }

    public int GetCountOfInternalFrames(ulong vmThread, uint* pRetVal)
    {
        int hr = HResults.S_OK;
        try
        {
            if (pRetVal == null)
                throw new ArgumentNullException(nameof(pRetVal));

            uint count = 0;
            IStackWalk stackwalk = _target.Contracts.StackWalk;
            foreach (Contracts.StackFrameData frame in stackwalk.GetFrames(new TargetPointer(vmThread)))
            {
                if (IsReportedInternalFrame(stackwalk, frame))
                    count++;
            }
            *pRetVal = count;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            uint dacCount;
            int hrLocal = _legacy.GetCountOfInternalFrames(vmThread, &dacCount);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == dacCount, $"Internal frame count mismatch - cDAC: {*pRetVal}, DAC: {dacCount}");
        }
#endif
        return hr;
    }

    public int EnumerateInternalFrames(ulong vmThread, delegate* unmanaged<Debugger_STRData*, void*, void> fpCallback, nint pUserData)
    {
        int hr = HResults.S_OK;
#if DEBUG
        List<Debugger_STRData>? cdacFrames = _legacy is not null ? new() : null;
#endif
        try
        {
            TargetPointer threadPtr = new TargetPointer(vmThread);
            TargetPointer currentAppDomain = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.AppDomain));
            IStackWalk stackwalk = _target.Contracts.StackWalk;

            foreach (Contracts.StackFrameData frame in stackwalk.GetFrames(threadPtr))
            {
                if (!IsReportedInternalFrame(stackwalk, frame))
                    continue;

                TargetPointer vmAssembly;
                uint funcMetadataToken;
                TargetPointer vmMethodDesc;
                if (frame.InternalFrameType == Contracts.InternalFrameType.FuncEval)
                {
                    Contracts.DebuggerEvalData evalData = stackwalk.GetDebuggerEvalData(frame.FrameAddress);
                    funcMetadataToken = evalData.MethodToken;
                    vmAssembly = evalData.AssemblyPtr;
                    vmMethodDesc = TargetPointer.Null;
                }
                else
                {
                    TargetPointer methodDescPtr = stackwalk.GetMethodDescPtr(frame.FrameAddress);
                    ResolveStubFrameAssemblyAndToken(methodDescPtr, out vmAssembly, out funcMetadataToken);
                    vmMethodDesc = methodDescPtr;
                }

                // ctx and rd are intentionally left as 0 (consumer does not read for cStubFrame).
                Debugger_STRData data = default;
                data.fp = frame.FrameAddress.Value;
                data.vmCurrentAppDomainToken = currentAppDomain.Value;
                data.eType = Debugger_STRData.EType.cStubFrame;
                data.stubFrame.funcMetadataToken = funcMetadataToken;
                data.stubFrame.vmAssembly = vmAssembly.Value;
                data.stubFrame.vmMethodDesc = vmMethodDesc.Value;
                data.stubFrame.frameType = (int)ToCorDebugInternalFrameType(frame.InternalFrameType);
#if DEBUG
                cdacFrames?.Add(data);
#endif
                fpCallback(&data, (void*)pUserData);
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            List<Debugger_STRData> dacFrames = new();
            GCHandle dacHandle = GCHandle.Alloc(dacFrames);
            int hrLocal = _legacy.EnumerateInternalFrames(vmThread, (delegate* unmanaged<Debugger_STRData*, void*, void>)&CollectStubFrameCallback, GCHandle.ToIntPtr(dacHandle));
            dacHandle.Free();
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                Debug.Assert(cdacFrames!.Count == dacFrames.Count, $"Internal frame count mismatch - cDAC: {cdacFrames!.Count}, DAC: {dacFrames.Count}");
                int n = Math.Min(cdacFrames!.Count, dacFrames.Count);
                for (int i = 0; i < n; i++)
                {
                    Debugger_STRData c = cdacFrames![i];
                    Debugger_STRData d = dacFrames[i];
                    Debug.Assert(c.fp == d.fp, $"Frame[{i}] fp mismatch - cDAC: 0x{c.fp:x}, DAC: 0x{d.fp:x}");
                    Debug.Assert(c.vmCurrentAppDomainToken == d.vmCurrentAppDomainToken, $"Frame[{i}] vmCurrentAppDomainToken mismatch - cDAC: 0x{c.vmCurrentAppDomainToken:x}, DAC: 0x{d.vmCurrentAppDomainToken:x}");
                    Debug.Assert(c.eType == d.eType, $"Frame[{i}] eType mismatch - cDAC: {c.eType}, DAC: {d.eType}");
                    Debug.Assert(c.stubFrame.funcMetadataToken == d.stubFrame.funcMetadataToken, $"Frame[{i}] funcMetadataToken mismatch - cDAC: 0x{c.stubFrame.funcMetadataToken:x}, DAC: 0x{d.stubFrame.funcMetadataToken:x}");
                    Debug.Assert(c.stubFrame.vmAssembly == d.stubFrame.vmAssembly, $"Frame[{i}] vmAssembly mismatch - cDAC: 0x{c.stubFrame.vmAssembly:x}, DAC: 0x{d.stubFrame.vmAssembly:x}");
                    Debug.Assert(c.stubFrame.vmMethodDesc == d.stubFrame.vmMethodDesc, $"Frame[{i}] vmMethodDesc mismatch - cDAC: 0x{c.stubFrame.vmMethodDesc:x}, DAC: 0x{d.stubFrame.vmMethodDesc:x}");
                    Debug.Assert(c.stubFrame.frameType == d.stubFrame.frameType, $"Frame[{i}] frameType mismatch - cDAC: {c.stubFrame.frameType}, DAC: {d.stubFrame.frameType}");
                }
            }
        }
#endif
        return hr;
    }

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

    private void ResolveStubFrameAssemblyAndToken(TargetPointer methodDescPtr, out TargetPointer vmAssembly, out uint funcMetadataToken)
    {
        vmAssembly = TargetPointer.Null;
        funcMetadataToken = 0; // mdTokenNil
        if (methodDescPtr == TargetPointer.Null)
            return;

        IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
        MethodDescHandle mdHandle = rts.GetMethodDescHandle(methodDescPtr);
        funcMetadataToken = rts.GetMethodToken(mdHandle);

        TargetPointer mtPtr = rts.GetMethodTable(mdHandle);
        if (mtPtr == TargetPointer.Null)
            return;
        TypeHandle typeHandle = rts.GetTypeHandle(mtPtr);
        TargetPointer modulePtr = rts.GetModule(typeHandle);
        if (modulePtr == TargetPointer.Null)
            return;

        ILoader loader = _target.Contracts.Loader;
        Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePtr);
        vmAssembly = loader.GetAssembly(moduleHandle);
    }

    private static CorDebugInternalFrameType ToCorDebugInternalFrameType(Contracts.InternalFrameType frameType)
        => frameType switch
        {
            Contracts.InternalFrameType.None => CorDebugInternalFrameType.STUBFRAME_NONE,
            Contracts.InternalFrameType.M2U => CorDebugInternalFrameType.STUBFRAME_M2U,
            Contracts.InternalFrameType.U2M => CorDebugInternalFrameType.STUBFRAME_U2M,
            Contracts.InternalFrameType.FuncEval => CorDebugInternalFrameType.STUBFRAME_FUNC_EVAL,
            Contracts.InternalFrameType.InternalCall => CorDebugInternalFrameType.STUBFRAME_INTERNALCALL,
            Contracts.InternalFrameType.ClassInit => CorDebugInternalFrameType.STUBFRAME_CLASS_INIT,
            Contracts.InternalFrameType.Exception => CorDebugInternalFrameType.STUBFRAME_EXCEPTION,
            Contracts.InternalFrameType.JitCompilation => CorDebugInternalFrameType.STUBFRAME_JIT_COMPILATION,
            _ => CorDebugInternalFrameType.STUBFRAME_NONE,
        };

    public int GetStackParameterSize(ulong controlPC, uint* pRetVal)
    {
        int hr = HResults.S_OK;
        try
        {
            if (pRetVal == null)
                throw new ArgumentNullException(nameof(pRetVal));

            *pRetVal = 0;
            IExecutionManager eman = _target.Contracts.ExecutionManager;
            if (eman.GetCodeBlockHandle(new TargetCodePointer(controlPC)) is not CodeBlockHandle cbh)
                throw new InvalidOperationException($"No code block found for controlPC 0x{controlPC:x}");

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

    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;
            ThreadData threadData = _target.Contracts.Thread.GetThreadData(new TargetPointer(vmThread));
            byte[] leafContext = _target.Contracts.StackWalk.GetContext(threadData, 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;
            ThreadData threadData = _target.Contracts.Thread.GetThreadData(new TargetPointer(vmThread));
            byte[] context = _target.Contracts.StackWalk.GetContext(threadData, 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)
    {
        int hr = HResults.S_OK;
        try
        {
            *pTargetBuffer = default;
            *pLocalSigToken = (uint)EcmaMetadataUtils.TokenType.mdtSignature;
            ILoader loader = _target.Contracts.Loader;
            Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmAssembly));

            MetadataReader mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle)
                ?? throw new InvalidOperationException("Module has no metadata.");
            MethodDefinitionHandle mdMethodHandle = MetadataTokens.MethodDefinitionHandle((int)EcmaMetadataUtils.GetRowId(functionToken));
            MethodDefinition methodDef = mdReader.GetMethodDefinition(mdMethodHandle);

            // Reject anything whose metadata CodeType isn't IL.
            if ((methodDef.ImplAttributes & MethodImplAttributes.CodeTypeMask) != MethodImplAttributes.IL)
                throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_FUNCTION_NOT_IL)!;

            ModuleLookupTables lookupTables = loader.GetLookupTables(moduleHandle);
            TargetPointer methodDescPtr = loader.GetModuleLookupMapElement(lookupTables.MethodDefToDesc, functionToken, out _);
            if (methodDescPtr != TargetPointer.Null)
            {
                IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
                MethodDescHandle mdHandle = rts.GetMethodDescHandle(methodDescPtr);
                if (!rts.IsIL(mdHandle))
                    throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_FUNCTION_NOT_IL)!;
            }
            else if (methodDef.RelativeVirtualAddress == 0)
                throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_FUNCTION_NOT_IL)!;

            TargetPointer headerPtr = loader.GetILHeader(moduleHandle, functionToken);
            if (headerPtr != TargetPointer.Null)
            {
                int headerSize = HeaderReaderHelpers.GetHeaderSize(_target, headerPtr);
                int codeSize = HeaderReaderHelpers.GetCodeSize(_target, headerPtr);

                if (HeaderReaderHelpers.TryGetLocalVarSigToken(_target, headerPtr, out int localToken) && localToken != 0)
                {
                    *pLocalSigToken = (uint)localToken;
                }

                pTargetBuffer->pAddress = headerPtr.Value + (ulong)headerSize;
                pTargetBuffer->cbSize = (uint)codeSize;
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            DacDbiTargetBuffer bufferLocal = default;
            uint sigLocal;
            int hrLocal = _legacy.GetILCodeAndSig(vmAssembly, functionToken, &bufferLocal, &sigLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                Debug.Assert(pTargetBuffer->pAddress == bufferLocal.pAddress, $"cDAC ILAddr: 0x{pTargetBuffer->pAddress:X}, DAC ILAddr: 0x{bufferLocal.pAddress:X}");
                Debug.Assert(pTargetBuffer->cbSize == bufferLocal.cbSize, $"cDAC ILSize: {pTargetBuffer->cbSize}, DAC ILSize: {bufferLocal.cbSize}");
                Debug.Assert(*pLocalSigToken == sigLocal, $"cDAC LocalSig: 0x{*pLocalSigToken:X}, DAC LocalSig: 0x{sigLocal:X}");
            }
        }
#endif
        return hr;
    }

    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 EnumerateClassFields(ulong thExact, nuint* pObjectSize, delegate* unmanaged<FieldData*, void*, void> fpCallback, nint pUserData)
    {
        if (pObjectSize != null)
            *pObjectSize = 0;

        nuint cdacObjectSize = 0;
        List<FieldData>? cdacFields = null;
#if DEBUG
        if (_legacy is not null)
            cdacFields = new();
#endif
        int hr = HResults.S_OK;
        try
        {
            if (fpCallback == null)
                throw new ArgumentNullException(nameof(fpCallback));

            IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
            TypeHandle thExactHandle = rts.GetTypeHandle(thExact);
            // Native semantics: thApprox is the same TypeHandle that was passed in.
            if (thExactHandle.IsNull)
                throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!;

            TypeHandle thApprox = thExactHandle;

            // For Generic classes the object size only comes through with an instantiated type.
            cdacObjectSize = 0;
            if (rts.GetInstantiation(thApprox).Length == 0)
            {
                cdacObjectSize = rts.GetNumInstanceFieldBytes(thApprox);
            }

            CollectFieldsForDbi(rts, thExactHandle, thApprox, fpCallback, pUserData, cdacFields);
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

        if (hr == HResults.S_OK && pObjectSize != null)
            *pObjectSize = cdacObjectSize;

#if DEBUG
        if (_legacy is not null)
        {
            ValidateEnumerateFieldsAgainstLegacy(
                nameof(IDacDbiInterface.EnumerateClassFields),
                cdacObjectSize,
                cdacFields,
                hr,
                (pSize, pUser) => _legacy!.EnumerateClassFields(thExact, pSize, (delegate* unmanaged<FieldData*, void*, void>)&CollectFieldDataCallback, pUser));
        }
#endif
        return hr;
    }

    public int EnumerateInstantiationFields(ulong vmAssembly, ulong vmThExact, ulong vmThApprox, nuint* pObjectSize, delegate* unmanaged<FieldData*, void*, void> fpCallback, nint pUserData)
    {
        if (pObjectSize != null)
            *pObjectSize = 0;

        nuint cdacObjectSize = 0;
        List<FieldData>? cdacFields = null;
#if DEBUG
        if (_legacy is not null)
            cdacFields = new();
#endif
        int hr = HResults.S_OK;
        try
        {
            if (fpCallback == null)
                throw new ArgumentNullException(nameof(fpCallback));

            IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
            TypeHandle thExactHandle = rts.GetTypeHandle(vmThExact);
            TypeHandle thApproxHandle = rts.GetTypeHandle(vmThApprox);
            if (thApproxHandle.IsNull)
                throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!;

            cdacObjectSize = rts.GetNumInstanceFieldBytes(thApproxHandle);

            CollectFieldsForDbi(rts, thExactHandle, thApproxHandle, fpCallback, pUserData, cdacFields);
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

        if (hr == HResults.S_OK && pObjectSize != null)
            *pObjectSize = cdacObjectSize;

#if DEBUG
        if (_legacy is not null)
        {
            ValidateEnumerateFieldsAgainstLegacy(
                nameof(IDacDbiInterface.EnumerateInstantiationFields),
                cdacObjectSize,
                cdacFields,
                hr,
                (pSize, pUser) => _legacy!.EnumerateInstantiationFields(vmAssembly, vmThExact, vmThApprox, pSize, (delegate* unmanaged<FieldData*, void*, void>)&CollectFieldDataCallback, pUser));
        }
#endif
        return hr;
    }

    // Mirrors native DacDbiInterfaceImpl::CollectFields. Iterates the regular FieldDescs first
    // then EnC-added instance fields, then EnC-added static fields.
    private void CollectFieldsForDbi(
        IRuntimeTypeSystem rts,
        TypeHandle thExact,
        TypeHandle thApprox,
        delegate* unmanaged<FieldData*, void*, void> fpCallback,
        nint pUserData,
        List<FieldData>? cdacFields)
    {
        TargetPointer gcStaticsBase = TargetPointer.Null;
        TargetPointer nonGCStaticsBase = TargetPointer.Null;
        if (!thExact.IsNull && !rts.IsCollectible(thExact))
        {
            gcStaticsBase = rts.GetGCStaticsBasePointer(thExact);
            nonGCStaticsBase = rts.GetNonGCStaticsBasePointer(thExact);
        }

        IRuntimeMutableTypeSystem? mts = _target.Contracts.TryGetContract<IRuntimeMutableTypeSystem>(out IRuntimeMutableTypeSystem enc) ? enc : null;

        foreach (TargetPointer fdPtr in rts.GetFieldDescList(thApprox))
            EmitFieldData(rts, mts, fdPtr, gcStaticsBase, nonGCStaticsBase, fpCallback, pUserData, cdacFields);

        if (mts is not null)
        {
            foreach (TargetPointer fdPtr in mts.EnumerateAddedFieldDescs(thApprox, staticFields: false))
                EmitFieldData(rts, mts, fdPtr, gcStaticsBase, nonGCStaticsBase, fpCallback, pUserData, cdacFields);
            foreach (TargetPointer fdPtr in mts.EnumerateAddedFieldDescs(thApprox, staticFields: true))
                EmitFieldData(rts, mts, fdPtr, gcStaticsBase, nonGCStaticsBase, fpCallback, pUserData, cdacFields);
        }
    }

    // Mirrors native DacDbiInterfaceImpl::ComputeFieldData for one FieldDesc and then invokes the
    // user callback.
    private static void EmitFieldData(
        IRuntimeTypeSystem rts,
        IRuntimeMutableTypeSystem? mts,
        TargetPointer fdPtr,
        TargetPointer gcStaticsBase,
        TargetPointer nonGCStaticsBase,
        delegate* unmanaged<FieldData*, void*, void> fpCallback,
        nint pUserData,
        List<FieldData>? cdacFields)
    {
        bool isStatic = rts.IsFieldDescStatic(fdPtr);
        CorElementType type = rts.GetFieldDescType(fdPtr);
        bool isPrimitive = IsPrimitiveType(type);

        FieldData fd = default;
        fd.m_fldMetadataToken = rts.GetFieldDescMemberDef(fdPtr);
        fd.m_fFldIsStatic = isStatic ? (byte)1 : (byte)0;
        fd.m_fFldIsPrimitive = isPrimitive ? (byte)1 : (byte)0;
        fd.m_fldSignatureCache = 0;
        fd.m_fldSignatureCacheSize = 0;
        fd.m_vmFieldDesc = fdPtr.Value;

        bool isEnCNew = mts?.IsFieldDescEnCNew(fdPtr) ?? false;
        if (isEnCNew)
        {
            // Mirrors native: storage not yet available; carry the FieldDesc pointer through
            // m_vmFieldDesc and clear the address-related flags (TLS/RVA/collectible).
            fd.m_fFldStorageAvailable = Interop.BOOL.FALSE;
            fd.m_fFldIsTLS = 0;
            fd.m_fFldIsRVA = 0;
            fd.m_fFldIsCollectibleStatic = 0;
        }
        if (!isEnCNew)
        {
            fd.m_fFldStorageAvailable = Interop.BOOL.TRUE;
            bool isTLS = rts.IsFieldDescThreadStatic(fdPtr);
            bool isRVA = rts.IsFieldDescRVA(fdPtr);
            bool isCollectibleStatic = false;
            if (isStatic)
            {
                TargetPointer enclosingMT = rts.GetMTOfEnclosingClass(fdPtr);
                if (enclosingMT != TargetPointer.Null)
                {
                    TypeHandle enclosingTh = rts.GetTypeHandle(enclosingMT);
                    isCollectibleStatic = rts.IsCollectible(enclosingTh);
                }
            }

            fd.m_fFldIsTLS = isTLS ? (byte)1 : (byte)0;
            fd.m_fFldIsRVA = isRVA ? (byte)1 : (byte)0;
            fd.m_fFldIsCollectibleStatic = isCollectibleStatic ? (byte)1 : (byte)0;

            if (isStatic)
            {
                if (isRVA)
                {
                    TargetPointer addr = rts.GetFieldDescStaticAddress(fdPtr, unboxValueTypes: false);
                    fd.m_pFldStaticAddress = addr.Value;
                }
                else if (!isTLS && !isCollectibleStatic)
                {
                    TargetPointer baseAddr = isPrimitive ? nonGCStaticsBase : gcStaticsBase;
                    if (baseAddr != TargetPointer.Null)
                    {
                        uint offset = rts.GetFieldDescOffset(fdPtr, null);
                        fd.m_pFldStaticAddress = baseAddr + offset;
                    }
                }
            }
            else
            {
                // instance: store the offset
                uint offset = rts.GetFieldDescOffset(fdPtr, null);
                fd.m_fldInstanceOffset = offset;
            }
        }

#if DEBUG
        cdacFields?.Add(fd);
#endif
        fpCallback(&fd, (void*)pUserData);
    }

    private static bool IsPrimitiveType(CorElementType type)
    {
        return (type < CorElementType.Ptr)
            || type == CorElementType.I
            || type == CorElementType.U;
    }

#if DEBUG
    [UnmanagedCallersOnly]
    private static void CollectFieldDataCallback(FieldData* data, void* pUserData)
    {
        GCHandle handle = GCHandle.FromIntPtr((nint)pUserData);
        ((List<FieldData>)handle.Target!).Add(*data);
    }

    private delegate int LegacyEnumerateFieldsFn(nuint* pObjectSize, nint pUserData);

    private static void ValidateEnumerateFieldsAgainstLegacy(string label, nuint cdacObjectSize, List<FieldData>? cdacFields, int hr, LegacyEnumerateFieldsFn legacyEnumerate)
    {
        List<FieldData> dacFields = new();
        GCHandle dacHandle = GCHandle.Alloc(dacFields);
        nuint dacObjectSize = 0;
        int hrLocal = legacyEnumerate(&dacObjectSize, GCHandle.ToIntPtr(dacHandle));
        dacHandle.Free();
        Debug.ValidateHResult(hr, hrLocal);
        if (hr == HResults.S_OK)
        {
            Debug.Assert(cdacObjectSize == dacObjectSize, $"{label} object size mismatch - cDAC: {cdacObjectSize}, DAC: {dacObjectSize}");
            AssertFieldListsEqual(cdacFields, dacFields, label);
        }
    }

    private static void AssertFieldListsEqual(List<FieldData>? cdacFields, List<FieldData> dacFields, string label)
    {
        Debug.Assert(cdacFields!.Count == dacFields.Count, $"{label} field count mismatch - cDAC: {cdacFields!.Count}, DAC: {dacFields.Count}");
        int n = Math.Min(cdacFields!.Count, dacFields.Count);
        for (int i = 0; i < n; i++)
        {
            FieldData c = cdacFields![i];
            FieldData d = dacFields[i];
            Debug.Assert(c.m_fldMetadataToken == d.m_fldMetadataToken, $"{label} field[{i}] m_fldMetadataToken mismatch - cDAC: 0x{c.m_fldMetadataToken:x}, DAC: 0x{d.m_fldMetadataToken:x}");
            Debug.Assert(c.m_fFldStorageAvailable == d.m_fFldStorageAvailable, $"{label} field[{i}] m_fFldStorageAvailable mismatch - cDAC: {c.m_fFldStorageAvailable}, DAC: {d.m_fFldStorageAvailable}");
            Debug.Assert(c.m_fFldIsStatic == d.m_fFldIsStatic, $"{label} field[{i}] m_fFldIsStatic mismatch - cDAC: {c.m_fFldIsStatic}, DAC: {d.m_fFldIsStatic}");
            Debug.Assert(c.m_fFldIsRVA == d.m_fFldIsRVA, $"{label} field[{i}] m_fFldIsRVA mismatch - cDAC: {c.m_fFldIsRVA}, DAC: {d.m_fFldIsRVA}");
            Debug.Assert(c.m_fFldIsTLS == d.m_fFldIsTLS, $"{label} field[{i}] m_fFldIsTLS mismatch - cDAC: {c.m_fFldIsTLS}, DAC: {d.m_fFldIsTLS}");
            Debug.Assert(c.m_fFldIsPrimitive == d.m_fFldIsPrimitive, $"{label} field[{i}] m_fFldIsPrimitive mismatch - cDAC: {c.m_fFldIsPrimitive}, DAC: {d.m_fFldIsPrimitive}");
            Debug.Assert(c.m_fFldIsCollectibleStatic == d.m_fFldIsCollectibleStatic, $"{label} field[{i}] m_fFldIsCollectibleStatic mismatch - cDAC: {c.m_fFldIsCollectibleStatic}, DAC: {d.m_fFldIsCollectibleStatic}");
            Debug.Assert(c.m_fldInstanceOffset == d.m_fldInstanceOffset, $"{label} field[{i}] m_fldInstanceOffset mismatch - cDAC: 0x{c.m_fldInstanceOffset:x}, DAC: 0x{d.m_fldInstanceOffset:x}");
            Debug.Assert(c.m_pFldStaticAddress == d.m_pFldStaticAddress, $"{label} field[{i}] m_pFldStaticAddress mismatch - cDAC: 0x{c.m_pFldStaticAddress:x}, DAC: 0x{d.m_pFldStaticAddress:x}");
            Debug.Assert(c.m_vmFieldDesc == d.m_vmFieldDesc, $"{label} field[{i}] m_vmFieldDesc mismatch - cDAC: 0x{c.m_vmFieldDesc:x}, DAC: 0x{d.m_vmFieldDesc:x}");
        }
    }
#endif

    public int TypeHandleToExpandedTypeInfo(AreValueTypesBoxed boxed, ulong vmTypeHandle, DebuggerIPCE_ExpandedTypeData* pTypeInfo)
    {
        int hr = HResults.S_OK;
        try
        {
            IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
            TypeHandle th = rts.GetTypeHandle(new TargetPointer(vmTypeHandle));
            TypeHandleToExpandedTypeInfoImpl(rts, boxed, th, pTypeInfo);
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            DebuggerIPCE_ExpandedTypeData dataLocal;
            int hrLocal = _legacy.TypeHandleToExpandedTypeInfo(boxed, vmTypeHandle, &dataLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                ValidateExpandedTypeData(pTypeInfo, &dataLocal);
            }
        }
#endif
        return hr;
    }

    public int GetObjectExpandedTypeInfo(AreValueTypesBoxed boxed, ulong addr, DebuggerIPCE_ExpandedTypeData* pTypeInfo)
    {
        int hr = HResults.S_OK;
        try
        {
            IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
            TargetPointer mtAddr = _target.Contracts.Object.GetMethodTableAddress(new TargetPointer(addr));
            TypeHandle th = rts.GetTypeHandle(mtAddr);
            TypeHandleToExpandedTypeInfoImpl(rts, boxed, th, pTypeInfo);
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            DebuggerIPCE_ExpandedTypeData dataLocal;
            int hrLocal = _legacy.GetObjectExpandedTypeInfo(boxed, addr, &dataLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                ValidateExpandedTypeData(pTypeInfo, &dataLocal);
            }
        }
#endif
        return hr;
    }

#if DEBUG
    private static void ValidateExpandedTypeData(DebuggerIPCE_ExpandedTypeData* cdac, DebuggerIPCE_ExpandedTypeData* dac)
    {
        Debug.Assert(cdac->elementType == dac->elementType,
            $"cDAC elementType: {cdac->elementType}, DAC: {dac->elementType}");
        switch ((CorElementType)ReadLittleEndian(cdac->elementType))
        {
            case CorElementType.Class:
            case CorElementType.ValueType:
                Debug.Assert(cdac->ClassTypeData_metadataToken == dac->ClassTypeData_metadataToken,
                    $"cDAC ClassTypeData.metadataToken: {cdac->ClassTypeData_metadataToken:x}, DAC: {dac->ClassTypeData_metadataToken:x}");
                Debug.Assert(cdac->ClassTypeData_vmAssembly == dac->ClassTypeData_vmAssembly,
                    $"cDAC ClassTypeData.vmAssembly: {cdac->ClassTypeData_vmAssembly:x}, DAC: {dac->ClassTypeData_vmAssembly:x}");
                Debug.Assert(cdac->ClassTypeData_typeHandle == dac->ClassTypeData_typeHandle,
                    $"cDAC ClassTypeData.typeHandle: {cdac->ClassTypeData_typeHandle:x}, DAC: {dac->ClassTypeData_typeHandle:x}");
                break;
            case CorElementType.Array:
            case CorElementType.SzArray:
                Debug.Assert(cdac->ArrayTypeData_arrayRank == dac->ArrayTypeData_arrayRank,
                    $"cDAC ArrayTypeData.arrayRank: {cdac->ArrayTypeData_arrayRank}, DAC: {dac->ArrayTypeData_arrayRank}");
                Debug.Assert(cdac->ArrayTypeData_arrayTypeArg.elementType == dac->ArrayTypeData_arrayTypeArg.elementType,
                    $"cDAC ArrayTypeData.arrayTypeArg.elementType: {cdac->ArrayTypeData_arrayTypeArg.elementType}, DAC: {dac->ArrayTypeData_arrayTypeArg.elementType}");
                Debug.Assert(cdac->ArrayTypeData_arrayTypeArg.metadataToken == dac->ArrayTypeData_arrayTypeArg.metadataToken,
                    $"cDAC ArrayTypeData.arrayTypeArg.metadataToken: {cdac->ArrayTypeData_arrayTypeArg.metadataToken:x}, DAC: {dac->ArrayTypeData_arrayTypeArg.metadataToken:x}");
                Debug.Assert(cdac->ArrayTypeData_arrayTypeArg.vmAssembly == dac->ArrayTypeData_arrayTypeArg.vmAssembly,
                    $"cDAC ArrayTypeData.arrayTypeArg.vmAssembly: {cdac->ArrayTypeData_arrayTypeArg.vmAssembly:x}, DAC: {dac->ArrayTypeData_arrayTypeArg.vmAssembly:x}");
                Debug.Assert(cdac->ArrayTypeData_arrayTypeArg.vmTypeHandle == dac->ArrayTypeData_arrayTypeArg.vmTypeHandle,
                    $"cDAC ArrayTypeData.arrayTypeArg.vmTypeHandle: {cdac->ArrayTypeData_arrayTypeArg.vmTypeHandle:x}, DAC: {dac->ArrayTypeData_arrayTypeArg.vmTypeHandle:x}");
                break;
            case CorElementType.Ptr:
            case CorElementType.Byref:
                Debug.Assert(cdac->UnaryTypeData_unaryTypeArg.elementType == dac->UnaryTypeData_unaryTypeArg.elementType,
                    $"cDAC UnaryTypeData.unaryTypeArg.elementType: {cdac->UnaryTypeData_unaryTypeArg.elementType}, DAC: {dac->UnaryTypeData_unaryTypeArg.elementType}");
                Debug.Assert(cdac->UnaryTypeData_unaryTypeArg.metadataToken == dac->UnaryTypeData_unaryTypeArg.metadataToken,
                    $"cDAC UnaryTypeData.unaryTypeArg.metadataToken: {cdac->UnaryTypeData_unaryTypeArg.metadataToken:x}, DAC: {dac->UnaryTypeData_unaryTypeArg.metadataToken:x}");
                Debug.Assert(cdac->UnaryTypeData_unaryTypeArg.vmAssembly == dac->UnaryTypeData_unaryTypeArg.vmAssembly,
                    $"cDAC UnaryTypeData.unaryTypeArg.vmAssembly: {cdac->UnaryTypeData_unaryTypeArg.vmAssembly:x}, DAC: {dac->UnaryTypeData_unaryTypeArg.vmAssembly:x}");
                Debug.Assert(cdac->UnaryTypeData_unaryTypeArg.vmTypeHandle == dac->UnaryTypeData_unaryTypeArg.vmTypeHandle,
                    $"cDAC UnaryTypeData.unaryTypeArg.vmTypeHandle: {cdac->UnaryTypeData_unaryTypeArg.vmTypeHandle:x}, DAC: {dac->UnaryTypeData_unaryTypeArg.vmTypeHandle:x}");
                break;
            case CorElementType.FnPtr:
                Debug.Assert(cdac->NaryTypeData_typeHandle == dac->NaryTypeData_typeHandle,
                    $"cDAC NaryTypeData.typeHandle: {cdac->NaryTypeData_typeHandle:x}, DAC: {dac->NaryTypeData_typeHandle:x}");
                break;
        }
    }
#endif

    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(DebuggerIPCE_ExpandedTypeData* pTypeData, ArgInfoList* pArgInfo, ulong* pVmTypeHandle)
    {
        if (pVmTypeHandle == null || pTypeData == null || pArgInfo == null)
            return HResults.E_POINTER;
        *pVmTypeHandle = 0;
        int hr = HResults.S_OK;
        try
        {
            IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
            TypeHandle th = default;
            CorElementType et = (CorElementType)ReadLittleEndian(pTypeData->elementType);
            switch (et)
            {
                case CorElementType.Array:
                case CorElementType.SzArray:
                    th = GetExactArrayTypeHandle(rts, pTypeData, pArgInfo);
                    break;
                case CorElementType.Ptr:
                case CorElementType.Byref:
                    th = GetExactPtrOrByRefTypeHandle(rts, pTypeData, pArgInfo);
                    break;
                case CorElementType.Class:
                case CorElementType.ValueType:
                    th = GetExactClassTypeHandle(rts, pTypeData, pArgInfo);
                    break;
                case CorElementType.FnPtr:
                    th = GetExactFnPtrTypeHandle(rts, pArgInfo);
                    break;
                default:
                    th = rts.GetPrimitiveType(et);
                    break;
            }
            if (th.Address == TargetPointer.Null)
                throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!;
            *pVmTypeHandle = th.Address.Value;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            ulong vmLocal;
            int hrLocal = _legacy.GetExactTypeHandle(pTypeData, pArgInfo, &vmLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pVmTypeHandle == vmLocal, $"cDAC: {*pVmTypeHandle:x}, DAC: {vmLocal:x}");
        }
#endif
        return hr;
    }

    private TypeHandle BasicTypeInfoToTypeHandle(IRuntimeTypeSystem rts, DebuggerIPCE_BasicTypeData* pData)
    {
        CorElementType et = (CorElementType)ReadLittleEndian(pData->elementType);
        TypeHandle th;
        switch (et)
        {
            case CorElementType.Array:
            case CorElementType.SzArray:
            case CorElementType.Ptr:
            case CorElementType.Byref:
            case CorElementType.FnPtr:
                ulong vmTh = ReadLittleEndian(pData->vmTypeHandle);
                if (vmTh == 0)
                    throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!;
                th = rts.GetTypeHandle(new TargetPointer(vmTh));
                break;
            case CorElementType.Class:
            case CorElementType.ValueType:
                th = GetClassOrValueTypeHandle(rts, pData);
                break;
            default:
                th = rts.GetPrimitiveType(et);
                break;
        }
        if (th.Address == TargetPointer.Null)
            throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!;
        return th;
    }

    private TypeHandle GetClassOrValueTypeHandle(IRuntimeTypeSystem rts, DebuggerIPCE_BasicTypeData* pData)
    {
        ulong vmTh = ReadLittleEndian(pData->vmTypeHandle);
        if (vmTh != 0)
            return rts.GetTypeHandle(new TargetPointer(vmTh));

        ulong vmAssembly = ReadLittleEndian(pData->vmAssembly);
        uint metadataToken = ReadLittleEndian(pData->metadataToken);
        return LookupTypeDefOrRefInAssembly(rts, vmAssembly, metadataToken);
    }

    private TypeHandle LookupTypeDefOrRefInAssembly(IRuntimeTypeSystem rts, ulong vmAssembly, uint metadataToken)
    {
        Contracts.ILoader loader = _target.Contracts.Loader;
        Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmAssembly));
        Contracts.ModuleLookupTables lookupTables = loader.GetLookupTables(moduleHandle);
        TargetPointer mt;
        switch ((EcmaMetadataUtils.TokenType)(metadataToken & EcmaMetadataUtils.TokenTypeMask))
        {
            case EcmaMetadataUtils.TokenType.mdtTypeDef:
                mt = loader.GetModuleLookupMapElement(lookupTables.TypeDefToMethodTable, metadataToken, out _);
                break;
            case EcmaMetadataUtils.TokenType.mdtTypeRef:
                mt = loader.GetModuleLookupMapElement(lookupTables.TypeRefToMethodTable, metadataToken, out _);
                break;
            default:
                throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!;
        }
        if (mt == TargetPointer.Null)
            throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!;
        return rts.GetTypeHandle(mt);
    }

    private TypeHandle GetExactArrayTypeHandle(IRuntimeTypeSystem rts, DebuggerIPCE_ExpandedTypeData* pTopLevel, ArgInfoList* pArgInfo)
    {
        if (pArgInfo->m_nEntries != 1)
            throw new ArgumentException($"Array type with arg count: {pArgInfo->m_nEntries}");
        TypeHandle elementType = BasicTypeInfoToTypeHandle(rts, &pArgInfo->m_pList[0]);
        CorElementType et = (CorElementType)ReadLittleEndian(pTopLevel->elementType);
        int rank = (int)ReadLittleEndian(pTopLevel->ArrayTypeData_arrayRank);
        return rts.GetConstructedType(elementType, et, rank, ImmutableArray<TypeHandle>.Empty);
    }

    private TypeHandle GetExactPtrOrByRefTypeHandle(IRuntimeTypeSystem rts, DebuggerIPCE_ExpandedTypeData* pTopLevel, ArgInfoList* pArgInfo)
    {
        if (pArgInfo->m_nEntries != 1)
            throw new ArgumentException($"Pointer or byref type with arg count: {pArgInfo->m_nEntries}");
        TypeHandle referent = BasicTypeInfoToTypeHandle(rts, &pArgInfo->m_pList[0]);
        CorElementType et = (CorElementType)ReadLittleEndian(pTopLevel->elementType);
        return rts.GetConstructedType(referent, et, 0, ImmutableArray<TypeHandle>.Empty);
    }

    private TypeHandle GetExactClassTypeHandle(IRuntimeTypeSystem rts, DebuggerIPCE_ExpandedTypeData* pTopLevel, ArgInfoList* pArgInfo)
    {
        ulong vmAssembly = ReadLittleEndian(pTopLevel->ClassTypeData_vmAssembly);
        uint metadataToken = ReadLittleEndian(pTopLevel->ClassTypeData_metadataToken);
        TypeHandle typeConstructor = LookupTypeDefOrRefInAssembly(rts, vmAssembly, metadataToken);

        int argCount = pArgInfo->m_nEntries;
        if (argCount == 0)
            return typeConstructor;

        ImmutableArray<TypeHandle>.Builder builder = ImmutableArray.CreateBuilder<TypeHandle>(argCount);
        for (int i = 0; i < argCount; i++)
            builder.Add(BasicTypeInfoToTypeHandle(rts, &pArgInfo->m_pList[i]));

        return rts.GetConstructedType(typeConstructor, CorElementType.GenericInst, 0, builder.MoveToImmutable());
    }

    private TypeHandle GetExactFnPtrTypeHandle(IRuntimeTypeSystem rts, ArgInfoList* pArgInfo)
    {
        int argCount = pArgInfo->m_nEntries;
        ImmutableArray<TypeHandle>.Builder builder = ImmutableArray.CreateBuilder<TypeHandle>(argCount);
        for (int i = 0; i < argCount; i++)
            builder.Add(BasicTypeInfoToTypeHandle(rts, &pArgInfo->m_pList[i]));

        // Non-default calling conventions are not supported.
        // Currently passes callConv=0 to match native DAC.
        return rts.GetConstructedType(default, CorElementType.FnPtr, 0, builder.MoveToImmutable());
    }

    public int EnumerateMethodDescParams(ulong vmMethodDesc, ulong genericsToken, uint* pcGenericClassTypeParams,
        delegate* unmanaged<DebuggerIPCE_ExpandedTypeData*, nint, void> fpCallback, nint pUserData)
    {
        int hr = HResults.S_OK;
#if DEBUG
        List<DebuggerIPCE_ExpandedTypeData> entries = new();
#endif
        uint cClassParams = 0;
        try
        {
            if (vmMethodDesc == 0)
                throw new ArgumentException("MethodDesc cannot be null", nameof(vmMethodDesc));
            if (pcGenericClassTypeParams == null)
                throw new ArgumentNullException(nameof(pcGenericClassTypeParams));
            if (fpCallback == null)
                throw new ArgumentNullException(nameof(fpCallback));

            *pcGenericClassTypeParams = 0;
            IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
            Contracts.MethodDescHandle pRepMethod = rts.GetMethodDescHandle(vmMethodDesc);
            TypeHandle thRepMt = rts.GetTypeHandle(rts.GetMethodTable(pRepMethod));

            // Try to resolve exact instantiations using the generics token. Fall back
            // to canonical when the token is unavailable, the method isn't shared, or any
            // resolution step fails (analogous to native's SanityCheck path).
            Contracts.MethodDescHandle pSpecificMethod = pRepMethod;
            TypeHandle thSpecificClass = thRepMt;
            bool isExact = false;

            GenericContextLoc ctxLoc = rts.GetGenericContextLoc(pRepMethod);
            if (ctxLoc == GenericContextLoc.None)
            {
                isExact = true;
            }
            else if (genericsToken != 0)
            {
                try
                {
                    if (ctxLoc == GenericContextLoc.InstArgMethodDesc)
                    {
                        // RequiresInstMethodDescArg: token is a MethodDesc*.
                        pSpecificMethod = rts.GetMethodDescHandle(new TargetPointer(genericsToken));
                        thSpecificClass = rts.GetTypeHandle(rts.GetMethodTable(pSpecificMethod));
                        isExact = true;
                    }
                    else if (ctxLoc == GenericContextLoc.InstArgMethodTable)
                    {
                        // RequiresInstMethodTableArg: token is a MethodTable*.
                        thSpecificClass = rts.GetTypeHandle(new TargetPointer(genericsToken));
                        isExact = true;
                    }
                    else
                    {
                        // AcquiresInstMethodTableFromThis: token is some MethodTable*; it may be a
                        // subclass, so walk the parent chain to find the exact declaring class.
                        TypeHandle thFromThis = rts.GetTypeHandle(new TargetPointer(genericsToken));
                        TypeHandle thMatch = GetMethodTableMatchingParentClass(rts, thFromThis, thRepMt);
                        if (!thMatch.IsNull)
                        {
                            thSpecificClass = thMatch;
                            isExact = true;
                        }
                    }
                }
                catch (VirtualReadException)
                {
                    // Any failure resolving the exact token: fall back to canonical.
                    isExact = false;
                }
            }

            if (!isExact)
            {
                pSpecificMethod = pRepMethod;
                thSpecificClass = thRepMt;
            }

            // Project the specific class onto the method's declaring class to get the class instantiation.
            TargetPointer specMethodMtPtr = rts.GetMethodTable(pSpecificMethod);
            TypeHandle thSpecMethodMt = rts.GetTypeHandle(specMethodMtPtr);
            TypeHandle thMatchingParent = GetMethodTableMatchingParentClass(rts, thSpecificClass, thSpecMethodMt);
            ReadOnlySpan<TypeHandle> classInst = thMatchingParent.IsNull
                ? ReadOnlySpan<TypeHandle>.Empty
                : rts.GetInstantiation(thMatchingParent);
            ReadOnlySpan<TypeHandle> methodInst = rts.GetGenericMethodInstantiation(pSpecificMethod);

            cClassParams = (uint)classInst.Length;
            *pcGenericClassTypeParams = cClassParams;

            // Resolve the System.__Canon TypeHandle for per-parameter fallback.
            TargetPointer canonMtPtr = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.CanonMethodTable));
            TypeHandle thCanon = rts.GetTypeHandle(canonMtPtr);

            DebuggerIPCE_ExpandedTypeData entry;
            for (int i = 0; i < classInst.Length; i++)
            {
                FillExpandedTypeDataWithCanonFallback(rts, classInst[i], thCanon, &entry);
#if DEBUG
                entries.Add(entry);
#endif
                fpCallback(&entry, pUserData);
            }
            for (int i = 0; i < methodInst.Length; i++)
            {
                FillExpandedTypeDataWithCanonFallback(rts, methodInst[i], thCanon, &entry);
#if DEBUG
                entries.Add(entry);
#endif
                fpCallback(&entry, pUserData);
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacy is not null)
        {
            DebugExpandedTypeInfo.Clear();
            uint cClassParamsLocal = 0;
            delegate* unmanaged<DebuggerIPCE_ExpandedTypeData*, nint, void> debugCallbackPtr = &EnumExpandedTypeInfoCallback;
            int hrLocal = _legacy.EnumerateMethodDescParams(vmMethodDesc, genericsToken, &cClassParamsLocal, debugCallbackPtr, 0);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                Debug.Assert(cClassParams == cClassParamsLocal,
                    $"cDAC class params: {cClassParams}, DAC: {cClassParamsLocal}");

                List<DebuggerIPCE_ExpandedTypeData> legacyEntries = DebugExpandedTypeInfo;
                if (!entries.SequenceEqual(legacyEntries))
                {
                    Debug.Assert(entries.Count == legacyEntries.Count,
                        $"cDAC param count: {entries.Count}, DAC: {legacyEntries.Count}");

                    int compareCount = Math.Min(entries.Count, legacyEntries.Count);
                    for (int i = 0; i < compareCount; i++)
                    {
                        Debug.Assert(entries[i].Equals(legacyEntries[i]),
                            $"Type param {i} mismatch{Environment.NewLine}" +
                            $"  cDAC: ({FormatExpandedTypeData(entries[i])}){Environment.NewLine}" +
                            $"  DAC:  ({FormatExpandedTypeData(legacyEntries[i])})");
                    }
                }
            }
            DebugExpandedTypeInfo.Clear();
        }
#endif
        return hr;
    }

    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)
    {
        *pRetVal = 0;
        int hr = HResults.S_OK;
        try
        {
            Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
            TargetPointer fd = new TargetPointer(vmField);
            *pRetVal = rts.GetFieldDescStaticAddress(fd, unboxValueTypes: false).Value;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            ulong retValLocal;
            int hrLocal = _legacy.GetCollectibleTypeStaticAddress(vmField, &retValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}");
        }
#endif
        return hr;
    }

    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 EnumerateTypeHandleParams(ulong vmTypeHandle,
        delegate* unmanaged<DebuggerIPCE_ExpandedTypeData*, nint, void> fpCallback, nint pUserData)
    {
        int hr = HResults.S_OK;
#if DEBUG
        List<DebuggerIPCE_ExpandedTypeData> entries = new();
#endif
        try
        {
            if (fpCallback == null)
                throw new ArgumentNullException(nameof(fpCallback));

            IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
            TypeHandle typeHandle = rts.GetTypeHandle(new TargetPointer(vmTypeHandle));
            ReadOnlySpan<TypeHandle> instantiation = rts.GetInstantiation(typeHandle);

            DebuggerIPCE_ExpandedTypeData entry;
            for (int i = 0; i < instantiation.Length; i++)
            {
                TypeHandleToExpandedTypeInfoImpl(rts, AreValueTypesBoxed.NoValueTypeBoxing, instantiation[i], &entry);
                fpCallback(&entry, pUserData);
#if DEBUG
                entries.Add(entry);
#endif
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacy is not null)
        {
            DebugExpandedTypeInfo.Clear();
            delegate* unmanaged<DebuggerIPCE_ExpandedTypeData*, nint, void> debugCallbackPtr = &EnumExpandedTypeInfoCallback;
            int hrLocal = _legacy.EnumerateTypeHandleParams(vmTypeHandle, debugCallbackPtr, 0);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                List<DebuggerIPCE_ExpandedTypeData> legacyEntries = DebugExpandedTypeInfo;
                if (!entries.SequenceEqual(legacyEntries))
                {
                    Debug.Assert(entries.Count == legacyEntries.Count,
                        $"cDAC param count: {entries.Count}, DAC: {legacyEntries.Count}");

                    int compareCount = Math.Min(entries.Count, legacyEntries.Count);
                    for (int i = 0; i < compareCount; i++)
                    {
                        Debug.Assert(entries[i].Equals(legacyEntries[i]),
                            $"Type param {i} mismatch{Environment.NewLine}" +
                            $"  cDAC: ({FormatExpandedTypeData(entries[i])}){Environment.NewLine}" +
                            $"  DAC:  ({FormatExpandedTypeData(legacyEntries[i])})");
                    }
                }
            }
            DebugExpandedTypeInfo.Clear();
        }
#endif
        return hr;
    }

#if DEBUG
    [ThreadStatic]
    private static List<DebuggerIPCE_ExpandedTypeData>? _debugExpandedTypeInfo;

    private static List<DebuggerIPCE_ExpandedTypeData> DebugExpandedTypeInfo
        => _debugExpandedTypeInfo ??= new();

    [UnmanagedCallersOnly]
    private static void EnumExpandedTypeInfoCallback(DebuggerIPCE_ExpandedTypeData* pTypeData, nint _)
    {
        DebugExpandedTypeInfo.Add(*pTypeData);
    }

    private static string FormatExpandedTypeData(DebuggerIPCE_ExpandedTypeData e) =>
        $"elementType={e.elementType}, " +
        $"token=0x{e.ClassTypeData_metadataToken:x}, " +
        $"vmAssembly=0x{e.ClassTypeData_vmAssembly:x}, " +
        $"vmTypeHandle=0x{e.ClassTypeData_typeHandle:x}";
#endif

    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)
    {
        *pResult = Interop.BOOL.FALSE;
        int hr = HResults.S_OK;
        try
        {
            IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
            TargetPointer objectAddress = new TargetPointer(vmObject);
            TargetPointer parentMT = _target.Contracts.Object.GetMethodTableAddress(objectAddress);
            TargetPointer exceptionMT = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.ExceptionMethodTable));

            while (parentMT != TargetPointer.Null)
            {
                if (parentMT == exceptionMT)
                {
                    *pResult = Interop.BOOL.TRUE;
                    break;
                }

                TypeHandle typeHandle = rts.GetTypeHandle(parentMT);
                parentMT = rts.GetParentMethodTable(typeHandle);
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            Interop.BOOL resultLocal;
            int hrLocal = _legacy.IsExceptionObject(vmObject, &resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}");
        }
#endif
        return hr;
    }

#if DEBUG
    [ThreadStatic]
    private static List<(ulong VmAppDomain, ulong VmAssembly, ulong Ip, uint MethodDef, Interop.BOOL IsLastForeignExceptionFrame)>? _debugEnumerateStackFramesFromException;

    private static List<(ulong VmAppDomain, ulong VmAssembly, ulong Ip, uint MethodDef, Interop.BOOL IsLastForeignExceptionFrame)> DebugEnumerateStackFramesFromException
        => _debugEnumerateStackFramesFromException ??= new();

    [UnmanagedCallersOnly]
    private static void EnumerateStackFramesFromExceptionDebugCallback(ulong vmAppDomain, ulong vmAssembly, ulong ip, uint methodDef, Interop.BOOL isLastForeignExceptionFrame, nint _)
    {
        DebugEnumerateStackFramesFromException.Add((vmAppDomain, vmAssembly, ip, methodDef, isLastForeignExceptionFrame));
    }
#endif

    public int EnumerateStackFramesFromException(ulong vmObject, delegate* unmanaged<ulong, ulong, ulong, uint, Interop.BOOL, nint, void> fpCallback, nint pUserData)
    {
        int hr = HResults.S_OK;
#if DEBUG
        List<(ulong VmAppDomain, ulong VmAssembly, ulong Ip, uint MethodDef, Interop.BOOL IsLastForeignExceptionFrame)> frames = new();
#endif
        try
        {
            if (fpCallback is null)
                throw new ArgumentNullException(nameof(fpCallback));

            TargetPointer appDomainPointer = _target.ReadGlobalPointer(Constants.Globals.AppDomain);
            ulong vmAppDomain = _target.ReadPointer(appDomainPointer).Value;

            IException exceptionContract = _target.Contracts.Exception;
            foreach (ExceptionStackFrameInfo frame in exceptionContract.GetExceptionStackFrames(new TargetPointer(vmObject)))
            {
                ResolveStubFrameAssemblyAndToken(frame.MethodDesc, out TargetPointer vmAssembly, out uint methodDef);
                Interop.BOOL isLastForeign = frame.IsLastForeignExceptionFrame ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
#if DEBUG
                frames.Add((vmAppDomain, vmAssembly.Value, frame.Ip.Value, methodDef, isLastForeign));
#endif
                fpCallback(vmAppDomain, vmAssembly.Value, frame.Ip.Value, methodDef, isLastForeign, pUserData);
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacy is not null)
        {
            DebugEnumerateStackFramesFromException.Clear();
            delegate* unmanaged<ulong, ulong, ulong, uint, Interop.BOOL, nint, void> debugCallbackPtr = &EnumerateStackFramesFromExceptionDebugCallback;
            int hrLocal = _legacy.EnumerateStackFramesFromException(vmObject, debugCallbackPtr, 0);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                List<(ulong VmAppDomain, ulong VmAssembly, ulong Ip, uint MethodDef, Interop.BOOL IsLastForeignExceptionFrame)> legacyFrames = DebugEnumerateStackFramesFromException;
                static string FormatFrame((ulong VmAppDomain, ulong VmAssembly, ulong Ip, uint MethodDef, Interop.BOOL IsLastForeignExceptionFrame) f)
                    => $"(AppDomain=0x{f.VmAppDomain:x}, Assembly=0x{f.VmAssembly:x}, Ip=0x{f.Ip:x}, MethodDef=0x{f.MethodDef:x}, IsLastForeignExceptionFrame={f.IsLastForeignExceptionFrame})";
                Debug.Assert(frames.SequenceEqual(legacyFrames),
                    $"Exception stack frame enumeration mismatch - "
                    + $"cDAC: [{string.Join(",", frames.Select(FormatFrame))}], "
                    + $"DAC: [{string.Join(",", legacyFrames.Select(FormatFrame))}]");
            }
            DebugEnumerateStackFramesFromException.Clear();
        }
#endif
        return hr;
    }

    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;
    }

#if DEBUG
    [ThreadStatic]
    private static List<ulong>? _debugEnumerateRcwCachedInterfacePointers;

    private static List<ulong> DebugEnumerateRcwCachedInterfacePointers
        => _debugEnumerateRcwCachedInterfacePointers ??= new();

    [UnmanagedCallersOnly]
    private static void EnumerateRcwCachedInterfacePointersDebugCallback(ulong itfPtr, nint _)
    {
        DebugEnumerateRcwCachedInterfacePointers.Add(itfPtr);
    }
#endif

    public int EnumerateRcwCachedInterfacePointers(ulong vmObject, delegate* unmanaged<ulong, nint, void> fpCallback, nint pUserData)
    {
        int hr = HResults.S_OK;
        List<ulong> itfPtrs = new();
        try
        {
            if (fpCallback is null)
                throw new ArgumentNullException(nameof(fpCallback));

            IObject obj = _target.Contracts.Object;
            _ = obj.GetBuiltInComData(new TargetPointer(vmObject), out TargetPointer rcw, out _, out _);
            if (rcw != TargetPointer.Null)
            {
                IBuiltInCOM builtInCom = _target.Contracts.BuiltInCOM;
                foreach ((TargetPointer methodTable, TargetPointer unknown) in builtInCom.GetRCWInterfaces(rcw))
                {
                    if (methodTable != TargetPointer.Null && unknown != TargetPointer.Null)
                        itfPtrs.Add(unknown.Value);
                }
            }

            foreach (ulong itfPtr in itfPtrs)
                fpCallback(itfPtr, pUserData);
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacy is not null)
        {
            DebugEnumerateRcwCachedInterfacePointers.Clear();
            delegate* unmanaged<ulong, nint, void> debugCallbackPtr = &EnumerateRcwCachedInterfacePointersDebugCallback;
            int hrLocal = _legacy.EnumerateRcwCachedInterfacePointers(vmObject, debugCallbackPtr, 0);
            Debug.ValidateHResult(hr, hrLocal);

            if (hr == HResults.S_OK)
            {
                List<ulong> legacyItfPtrs = DebugEnumerateRcwCachedInterfacePointers;
                Debug.Assert(itfPtrs.SequenceEqual(legacyItfPtrs),
                    $"cDAC: [{string.Join(",", itfPtrs.Select(p => $"0x{p:x}"))}], DAC: [{string.Join(",", legacyItfPtrs.Select(p => $"0x{p:x}"))}]");
            }
            DebugEnumerateRcwCachedInterfacePointers.Clear();
        }
#endif
        return hr;
    }

    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 GetThreadOwningMonitorLock(ulong vmObject, DacDbiMonitorLockInfo* pRetVal)
    {
        int hr = HResults.S_OK;
        *pRetVal = default;
        try
        {
            DacDbiMonitorLockInfo info = default;
            uint threadId = 0;
            uint recursionCount = 0;
            TargetPointer syncBlock = _target.Contracts.Object.GetSyncBlockAddress(vmObject);

            if (syncBlock == TargetPointer.Null || !_target.Contracts.SyncBlock.TryGetLockInfo(syncBlock, out threadId, out recursionCount))
            {
                *pRetVal = info;
            }
            else
            {
                TargetPointer threadPtr = _target.Contracts.Thread.IdToThread(threadId);
                Debug.Assert(threadPtr != TargetPointer.Null, "A thread should have been found");
                if (threadPtr != TargetPointer.Null)
                {
                    info.lockOwner = threadPtr;
                    info.acquisitionCount = recursionCount + 1; // The runtime tracks recursion count starting at 0, but diagnostics users expect it to start at 1.
                }
                *pRetVal = info;
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            DacDbiMonitorLockInfo pRetValLocal;
            int hrLocal = _legacy.GetThreadOwningMonitorLock(vmObject, &pRetValLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                Debug.Assert(pRetVal->lockOwner == pRetValLocal.lockOwner,
                    $"lockOwner mismatch: cDAC={pRetVal->lockOwner}, DAC={pRetValLocal.lockOwner}");
                Debug.Assert(pRetVal->acquisitionCount == pRetValLocal.acquisitionCount,
                    $"acquisitionCount mismatch: cDAC={pRetVal->acquisitionCount}, DAC={pRetValLocal.acquisitionCount}");
            }
        }
#endif
        return hr;
    }

    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)
    {
        int hr = HResults.S_OK;
        if (pHandle is null)
            return HResults.E_POINTER;
        *pHandle = 0;
        HeapWalk? walk = null;
        try
        {
            walk = new HeapWalk(_target);
            *pHandle = (nuint)((IEnum<COR_HEAPOBJECT>)walk).GetHandle();
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            nuint legacyHandle = 0;
            int hrLocal = _legacy.CreateHeapWalk(&legacyHandle);
            // The cDAC walker uses a lazy C# iterator and doesn't pre-validate objects at construction time;
            // the legacy walker eagerly validates the heap-start object and can refuse if it's corrupt.
            Debug.ValidateHResult(hr, hrLocal, HResultValidationMode.AllowCdacSuccess);
            if (hrLocal == HResults.S_OK && walk is not null)
                walk.LegacyHandle = legacyHandle;
            else if (hrLocal == HResults.S_OK)
                _legacy.DeleteHeapWalk(legacyHandle);
        }
#endif
        return hr;
    }

    public int DeleteHeapWalk(nuint handle)
    {
        if (handle == 0)
            return HResults.S_OK;

        int hr = HResults.S_OK;
        nuint legacyHandle = 0;
        try
        {
            GCHandle gcHandle = GCHandle.FromIntPtr((nint)handle);
            if (gcHandle.Target is not HeapWalk walk)
                throw new ArgumentException("Invalid heap walk handle", nameof(handle));
            legacyHandle = walk.LegacyHandle;
            ((IEnum<COR_HEAPOBJECT>)walk).Dispose();
            gcHandle.Free();
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null && legacyHandle != 0)
        {
            int hrLocal = _legacy.DeleteHeapWalk(legacyHandle);
            Debug.ValidateHResult(hr, hrLocal);
        }
#endif
        return hr;
    }

    // Should be called repeatedly until it returns S_FALSE. E_FAIL is not fatal, just indicates partial heap corruption.
    public int WalkHeap(nuint handle, uint count, COR_HEAPOBJECT* objects, uint* fetched)
    {
        if (fetched is null)
            return HResults.E_INVALIDARG;
        *fetched = 0;
        if (objects is null && count > 0)
            return HResults.E_INVALIDARG;
        if (handle == 0)
            return HResults.E_INVALIDARG;

        HeapWalk walk;
        try
        {
            GCHandle gcHandle = GCHandle.FromIntPtr((nint)handle);
            if (gcHandle.Target is not HeapWalk hw)
                throw new ArgumentException("Invalid heap walk handle", nameof(handle));
            walk = hw;
        }
        catch (System.Exception ex)
        {
            return ex.HResult;
        }

        int hr = HResults.S_OK;
        uint i = 0;
        try
        {
            while (i < count && walk.Enumerator.MoveNext())
            {
                COR_HEAPOBJECT current = walk.Enumerator.Current;
                // Sentinel value indicates invalid object.
                if (current.address == 0)
                {
                    hr = HResults.E_FAIL;
                    break;
                }
                objects[i++] = current;
            }

            // A clean batch reports S_FALSE iff we couldn't fill the caller's request.
            if (hr == HResults.S_OK && i < count)
                hr = HResults.S_FALSE;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

        *fetched = i;
#if DEBUG
        if (_legacy is not null && walk.LegacyHandle != 0)
        {
            COR_HEAPOBJECT[] objectsLocal = new COR_HEAPOBJECT[count];
            uint fetchedLocal = 0;
            int hrLocal;
            fixed (COR_HEAPOBJECT* objectsLocalPtr = objectsLocal)
            {
                hrLocal = _legacy.WalkHeap(walk.LegacyHandle, count, objectsLocalPtr, &fetchedLocal);
            }
            Debug.ValidateHResult(hr, hrLocal);
            if (hr >= HResults.S_OK)
            {
                Debug.Assert(*fetched == fetchedLocal,
                    $"cDAC WalkHeap fetched {*fetched}, legacy fetched {fetchedLocal}");
                for (uint k = 0; k < fetchedLocal; k++)
                {
                    Debug.Assert(objects[k].address == objectsLocal[k].address,
                        $"cDAC[{k}].address=0x{objects[k].address:x}, legacy=0x{objectsLocal[k].address:x}");
                    Debug.Assert(objects[k].size == objectsLocal[k].size,
                        $"cDAC[{k}].size=0x{objects[k].size:x}, legacy=0x{objectsLocal[k].size:x} (addr 0x{objects[k].address:x})");
                    Debug.Assert(objects[k].type.token1 == objectsLocal[k].type.token1,
                        $"cDAC[{k}].type.token1=0x{objects[k].type.token1:x}, legacy=0x{objectsLocal[k].type.token1:x} (addr 0x{objects[k].address:x})");
                }
            }
        }
#endif
        return hr;
    }

#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)
    {
        int hr = HResults.S_OK;
        Interop.BOOL isValid = Interop.BOOL.FALSE;

        if (obj != 0 && obj != ulong.MaxValue)
        {
            try
            {
                IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
                TargetPointer mt = _target.Contracts.Object.GetMethodTableAddress(new TargetPointer(obj));
                TypeHandle th = rts.GetTypeHandle(mt);
                TargetPointer canonMT = rts.GetCanonicalMethodTable(th);

                if (mt == canonMT)
                {
                    isValid = Interop.BOOL.TRUE;
                }
                else if (!rts.IsCanonicalMethodTable(th) || rts.IsContinuationWithoutMetadata(th))
                {
                    TargetPointer cls = rts.GetClassPointer(th);
                    TypeHandle canonTh = rts.GetTypeHandle(canonMT);
                    TargetPointer canonCls = rts.GetClassPointer(canonTh);
                    if (canonCls == cls)
                        isValid = Interop.BOOL.TRUE;
                }
            }
            catch (System.Exception)
            {
                isValid = Interop.BOOL.FALSE;
            }
        }
        *pResult = isValid;

#if DEBUG
        if (_legacy is not null)
        {
            Interop.BOOL resultLocal;
            int hrLocal = _legacy.IsValidObject(obj, &resultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}");
            }
        }
#endif

        return hr;
    }

    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(ulong id, uint celt, COR_FIELD* layout, uint* pceltFetched)
    {
        int hr = HResults.S_OK;
        uint cFields = 0;
        try
        {
            if (pceltFetched == null)
                throw new NullReferenceException(nameof(pceltFetched));

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

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

            if (rts.IsTypeDesc(typeHandle))
                throw new ArgumentException("TypeDescs are not supported", nameof(id));

            typeHandle = UpCastTypeIfNeeded(rts, typeHandle);

            // Number of introduced instance fields = NumInstanceFields - parent's NumInstanceFields.
            cFields = rts.GetNumInstanceFields(typeHandle);
            TargetPointer parentMT = rts.GetParentMethodTable(typeHandle);
            if (parentMT != TargetPointer.Null)
            {
                TypeHandle parentHandle = rts.GetTypeHandle(parentMT);
                cFields -= rts.GetNumInstanceFields(parentHandle);
            }

            // Caller may pass a null layout buffer to query the number of fields.
            if (layout == null)
            {
                *pceltFetched = cFields;
                hr = HResults.S_FALSE;
            }
            else
            {
                if (celt < cFields)
                {
                    cFields = celt;
                    hr = HResults.S_FALSE;
                }

                // Match native DAC: pceltFetched is set to celt (the input capacity), not the
                // count actually written. Preserve this behavior for compatibility w/ICorDebug.
                *pceltFetched = celt;

                bool isReferenceType = rts.IsObjRef(typeHandle);
                uint firstFieldOffset = isReferenceType ? _target.GetTypeInfo(DataType.Object).Size!.Value : 0;

                TargetPointer[] fieldDescList = rts.GetFieldDescList(typeHandle).Take((int)cFields).ToArray();

                IEcmaMetadata ecmaMetadataContract = _target.Contracts.EcmaMetadata;
                ISignature signature = _target.Contracts.Signature;

                for (uint i = 0; i < cFields; ++i)
                {
                    TargetPointer fieldDescPtr = fieldDescList[i];
                    COR_FIELD* corField = layout + i;

                    uint memberDef = rts.GetFieldDescMemberDef(fieldDescPtr);
                    corField->token = memberDef;

                    // Resolve metadata for this field's enclosing class (for offset lookup and
                    // signature decoding context).
                    TargetPointer enclosingMT = rts.GetMTOfEnclosingClass(fieldDescPtr);
                    TypeHandle enclosingTypeHandle = rts.GetTypeHandle(enclosingMT);
                    TargetPointer enclosingModulePtr = rts.GetModule(enclosingTypeHandle);
                    Contracts.ModuleHandle enclosingModuleHandle = _target.Contracts.Loader.GetModuleHandleFromModulePtr(enclosingModulePtr);
                    MetadataReader enclosingMdReader = ecmaMetadataContract.GetMetadata(enclosingModuleHandle)!;
                    FieldDefinitionHandle fieldDefHandle = (FieldDefinitionHandle)MetadataTokens.Handle((int)memberDef);
                    FieldDefinition fieldDef = enclosingMdReader.GetFieldDefinition(fieldDefHandle);

                    corField->offset = rts.GetFieldDescOffset(fieldDescPtr, fieldDef) + firstFieldOffset;

                    // Resolve the field's type. If we cannot decode the signature (e.g. corrupt
                    // metadata or a type that cannot be loaded), zero out the type id and
                    // fieldType, matching native DAC behavior when LookupFieldTypeHandle returns
                    // a null TypeHandle.
                    try
                    {
                        TypeHandle fieldTypeHandle = signature.DecodeFieldSignature(fieldDef.Signature, enclosingModuleHandle, enclosingTypeHandle);
                        if (fieldTypeHandle.IsNull)
                        {
                            corField->id = default;
                            corField->fieldType = 0;
                            continue;
                        }
                        CorElementType signatureType = rts.GetSignatureCorElementType(fieldTypeHandle);
                        if (signatureType == CorElementType.Byref)
                        {
                            corField->fieldType = (int)CorElementType.Byref;
                            // All ByRefs intentionally return IntPtr's MethodTable.
                            corField->id.token1 = rts.GetPrimitiveType(CorElementType.I).Address.Value;
                            corField->id.token2 = 0;
                        }
                        else
                        {
                            //   - Pointer/FnPtr typedescs report ELEMENT_TYPE_U's MethodTable.
                            TypeHandle mtHandle = (signatureType == CorElementType.Ptr || signatureType == CorElementType.FnPtr)
                                ? rts.GetPrimitiveType(CorElementType.U)
                                : fieldTypeHandle;

                            corField->fieldType = (int)rts.GetInternalCorElementType(mtHandle);
                            corField->id.token1 = mtHandle.Address.Value;
                            corField->id.token2 = 0;
                        }
                    }
                    catch (System.Exception)
                    {
                        // Field type could not be resolved - mirror native's null-TypeHandle path.
                        corField->id = default;
                        corField->fieldType = 0;
                    }
                }
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacy is not null)
        {
            uint fetchedLocal = 0;
            // Allocate at least one element so the `fixed` pointer is valid even when celt is 0.
            COR_FIELD[] localFields = new COR_FIELD[celt == 0 ? 1 : celt];
            fixed (COR_FIELD* localFieldsPtr = localFields)
            {
                int hrLocal = _legacy.GetObjectFields(id, celt, layout == null ? null : localFieldsPtr, &fetchedLocal);
                Debug.ValidateHResult(hr, hrLocal);
                if (hr >= HResults.S_OK && hrLocal >= HResults.S_OK)
                {
                    Debug.Assert(*pceltFetched == fetchedLocal, $"cDAC: {*pceltFetched}, DAC: {fetchedLocal}");
                    uint written = layout == null ? 0 : Math.Min(celt, cFields);
                    for (uint i = 0; i < written; ++i)
                    {
                        Debug.Assert(layout[i].token == localFieldsPtr[i].token, $"field[{i}].token cDAC: {layout[i].token:x}, DAC: {localFieldsPtr[i].token:x}");
                        Debug.Assert(layout[i].offset == localFieldsPtr[i].offset, $"field[{i}].offset cDAC: {layout[i].offset}, DAC: {localFieldsPtr[i].offset}");
                        Debug.Assert(layout[i].fieldType == localFieldsPtr[i].fieldType, $"field[{i}].fieldType cDAC: {layout[i].fieldType}, DAC: {localFieldsPtr[i].fieldType}");
                        Debug.Assert(layout[i].id.token1 == localFieldsPtr[i].id.token1, $"field[{i}].id.token1 cDAC: {layout[i].id.token1:x}, DAC: {localFieldsPtr[i].id.token1:x}");
                        Debug.Assert(layout[i].id.token2 == localFieldsPtr[i].id.token2, $"field[{i}].id.token2 cDAC: {layout[i].id.token2:x}, DAC: {localFieldsPtr[i].id.token2:x}");
                    }
                }
            }
        }
#endif

        return hr;
    }

    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)
    {
        int hr = HResults.S_OK;
        try
        {
            if (vmModule == 0)
                throw new ArgumentException("Module pointer cannot be null.", nameof(vmModule));

            if (pOptimizationsDisabled is null)
                throw new ArgumentException("Output pointer cannot be null.", nameof(pOptimizationsDisabled));

            if ((EcmaMetadataUtils.TokenType)(methodTk & EcmaMetadataUtils.TokenTypeMask) != EcmaMetadataUtils.TokenType.mdtMethodDef)
                throw new ArgumentException("methodTk must be a MethodDef token.", nameof(methodTk));

            *pOptimizationsDisabled = Interop.BOOL.FALSE;
            if (_target.Contracts.TryGetContract<IReJIT>(out IReJIT rejit))
            {
                ILoader loader = _target.Contracts.Loader;
                Contracts.ModuleHandle module = loader.GetModuleHandleFromModulePtr(new TargetPointer(vmModule));
                ModuleLookupTables lookupTables = loader.GetLookupTables(module);
                TargetPointer methodDesc = loader.GetModuleLookupMapElement(lookupTables.MethodDefToDesc, methodTk, out _);

                if (methodDesc != TargetPointer.Null)
                {
                    ICodeVersions codeVersions = _target.Contracts.CodeVersions;
                    ILCodeVersionHandle ilCodeVersion = codeVersions.GetActiveILCodeVersion(methodDesc);
                    if (rejit.IsDeoptimized(ilCodeVersion))
                    {
                        *pOptimizationsDisabled = Interop.BOOL.TRUE;
                    }
                }
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacy is not null)
        {
            Interop.BOOL localPOptimizationsDisabled;
            int hrLocal = _legacy.AreOptimizationsDisabled(vmModule, methodTk, &localPOptimizationsDisabled);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pOptimizationsDisabled == localPOptimizationsDisabled);
        }
#endif

        return hr;
    }

    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)
    {
        int hr = HResults.S_OK;
        try
        {
            if (vmObject == 0)
            {
                *pResult = Interop.BOOL.FALSE;
            }
            else
            {
                *pResult = IsDelegateHelper(vmObject) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            Interop.BOOL pResultLocal;
            int hrLocal = _legacy.IsDelegate(vmObject, &pResultLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pResult == pResultLocal, $"cDAC: {*pResult}, DAC: {pResultLocal}");
        }
#endif
        return hr;
    }

    public int GetDelegateFunctionData(ulong delegateObject, ulong* ppFunctionAssembly, uint* pMethodDef)
    {
        int hr = HResults.S_OK;
        try
        {
            if (ppFunctionAssembly == null)
                throw new ArgumentNullException(nameof(ppFunctionAssembly));
            if (pMethodDef == null)
                throw new ArgumentNullException(nameof(pMethodDef));
            if (!IsDelegateHelper(delegateObject))
                throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_UNSUPPORTED_DELEGATE)!;

            DelegateInfo delegateInfo = _target.Contracts.Object.GetDelegateInfo(new TargetPointer(delegateObject));

            // Only closed/open delegates expose a single managed target method via this API.
            // Multicast, unmanaged-fptr, wrapper, and special-sig delegates are not supported.
            if (delegateInfo.DelegateType is not (DelegateType.Closed or DelegateType.Open))
            {
                throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_UNSUPPORTED_DELEGATE)!;
            }

            IExecutionManager eman = _target.Contracts.ExecutionManager;
            TargetPointer methodDescPtr = eman.NonVirtualEntry2MethodDesc(delegateInfo.TargetMethodPtr);

            if (methodDescPtr == TargetPointer.Null)
            {
                throw new ArgumentException("Unable to find MethodDesc for the delegate's target method.", nameof(delegateObject));
            }

            IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
            MethodDescHandle mdHandle = rts.GetMethodDescHandle(methodDescPtr);
            *pMethodDef = rts.GetMethodToken(mdHandle);

            TargetPointer mtPtr = rts.GetMethodTable(mdHandle);
            TypeHandle typeHandle = rts.GetTypeHandle(mtPtr);
            TargetPointer modulePtr = rts.GetModule(typeHandle);
            Contracts.ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandleFromModulePtr(modulePtr);
            *ppFunctionAssembly = _target.Contracts.Loader.GetAssembly(moduleHandle).Value;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            ulong asmLocal;
            uint methodDefLocal;
            int hrLocal = _legacy.GetDelegateFunctionData(delegateObject, &asmLocal, &methodDefLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                Debug.Assert(*ppFunctionAssembly == asmLocal, $"cDAC: {*ppFunctionAssembly:x}, DAC: {asmLocal:x}");
                Debug.Assert(*pMethodDef == methodDefLocal, $"cDAC: {*pMethodDef:x}, DAC: {methodDefLocal:x}");
            }
        }
#endif
        return hr;
    }

    public int GetDelegateTargetObject(ulong delegateObject, ulong* ppTargetObj)
    {
        int hr = HResults.S_OK;
        try
        {
            if (ppTargetObj == null)
                throw new ArgumentNullException(nameof(ppTargetObj));
            if (!IsDelegateHelper(delegateObject))
                throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_UNSUPPORTED_DELEGATE)!;

            DelegateInfo delegateInfo = _target.Contracts.Object.GetDelegateInfo(new TargetPointer(delegateObject));
            if (delegateInfo.DelegateType is not (DelegateType.Closed or DelegateType.Open))
            {
                throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_UNSUPPORTED_DELEGATE)!;
            }

            *ppTargetObj = delegateInfo.TargetObject.Value;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            ulong targetObjLocal;
            int hrLocal = _legacy.GetDelegateTargetObject(delegateObject, &targetObjLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*ppTargetObj == targetObjLocal, $"cDAC: {*ppTargetObj:x}, DAC: {targetObjLocal:x}");
        }
#endif
        return hr;
    }

    private bool IsDelegateHelper(ulong vmObject)
    {
        TargetPointer mt = _target.Contracts.Object.GetMethodTableAddress(vmObject);
        IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
        TypeHandle typeHandle = rts.GetTypeHandle(mt);
        return rts.IsDelegate(typeHandle);
    }

    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)
    {
        int hr = HResults.S_OK;
        try
        {
            if (pVmAssembly == null)
                throw new ArgumentNullException(nameof(pVmAssembly));

            *pVmAssembly = 0;

            if (vmModule == 0)
                throw new ArgumentException("Module pointer must not be zero.", nameof(vmModule));

            Contracts.ILoader loader = _target.Contracts.Loader;
            Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(new TargetPointer(vmModule));
            TargetPointer assemblyPtr = loader.GetAssembly(handle);
            *pVmAssembly = assemblyPtr.Value;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacy is not null)
        {
            ulong assemblyLocal;
            int hrLocal = _legacy.GetAssemblyFromModule(vmModule, &assemblyLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*pVmAssembly == assemblyLocal, $"cDAC: {*pVmAssembly:x}, DAC: {assemblyLocal:x}");
        }
#endif
        return hr;
    }

    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.InstArgMethodDesc:
                case GenericContextLoc.InstArgMethodTable:
                    *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;
    }

    // Fills a DebuggerIPCE_ExpandedTypeData entry for a single type parameter, falling back to System.__Canon on failure.
    private void FillExpandedTypeDataWithCanonFallback(IRuntimeTypeSystem rts, TypeHandle typeHandle, TypeHandle thCanon, DebuggerIPCE_ExpandedTypeData* pTypeInfo)
    {
        try
        {
            TypeHandleToExpandedTypeInfoImpl(rts, AreValueTypesBoxed.NoValueTypeBoxing, typeHandle, pTypeInfo);
        }
        catch (VirtualReadException)
        {
            TypeHandleToExpandedTypeInfoImpl(rts, AreValueTypesBoxed.NoValueTypeBoxing, thCanon, pTypeInfo);
        }
    }

    // True if `a` and `b` share the same non-zero TypeDef RID and Module.
    // Mirrors native MethodTable::HasSameTypeDefAs.
    private static bool HasSameTypeDefAs(IRuntimeTypeSystem rts, TypeHandle a, TypeHandle b)
    {
        if (a.Address == b.Address)
            return true;
        uint ridA = EcmaMetadataUtils.GetRowId(rts.GetTypeDefToken(a));
        uint ridB = EcmaMetadataUtils.GetRowId(rts.GetTypeDefToken(b));
        if (ridA == 0 || ridA != ridB)
            return false;
        return rts.GetModule(a) == rts.GetModule(b);
    }

    // Walks the parent chain of `start` and returns the first MethodTable whose TypeDef matches `parent`,
    // or default if no match is found. The walk is bounded by a hard iteration cap to defend against
    // cycles observed in corrupt dumps. Mirrors native MethodTable::GetMethodTableMatchingParentClass.
    private static TypeHandle GetMethodTableMatchingParentClass(IRuntimeTypeSystem rts, TypeHandle start, TypeHandle parent)
    {
        TypeHandle current = start;
        TargetPointer prev = TargetPointer.Null;
        for (int i = 0; i < 1000 && !current.IsNull; i++)
        {
            if (HasSameTypeDefAs(rts, current, parent))
                return current;
            TargetPointer next = rts.GetParentMethodTable(current);
            if (next == TargetPointer.Null || next == prev || next == current.Address)
                break;
            prev = current.Address;
            current = rts.GetTypeHandle(next);
        }
        return default;
    }

    // Shared core implementation for TypeHandleToExpandedTypeInfo and GetObjectExpandedTypeInfo.
    private void TypeHandleToExpandedTypeInfoImpl(IRuntimeTypeSystem rts, AreValueTypesBoxed boxed, TypeHandle typeHandle, DebuggerIPCE_ExpandedTypeData* pTypeInfo)
    {
        *pTypeInfo = default;
        CorElementType elementType = GetElementType(rts, typeHandle);
        WriteLittleEndian(ref pTypeInfo->elementType, (int)elementType);

        switch (elementType)
        {
            case CorElementType.Array:
            case CorElementType.SzArray:
                FillArrayTypeInfo(rts, typeHandle, pTypeInfo);
                break;

            case CorElementType.Ptr:
            case CorElementType.Byref:
                FillPtrTypeInfo(rts, boxed, typeHandle, pTypeInfo);
                break;

            case CorElementType.ValueType:
                if (boxed == AreValueTypesBoxed.OnlyPrimitivesUnboxed || boxed == AreValueTypesBoxed.AllBoxed)
                {
                    WriteLittleEndian(ref pTypeInfo->elementType, (int)CorElementType.Class);
                }
                FillClassTypeInfo(rts, typeHandle, pTypeInfo);
                break;

            case CorElementType.Class:
                FillClassTypeInfo(rts, typeHandle, pTypeInfo);
                break;

            case CorElementType.FnPtr:
                FillFnPtrTypeInfo(rts, boxed, typeHandle, pTypeInfo);
                break;

            default:
                if (boxed == AreValueTypesBoxed.AllBoxed)
                {
                    WriteLittleEndian(ref pTypeInfo->elementType, (int)CorElementType.Class);
                    FillClassTypeInfo(rts, typeHandle, pTypeInfo);
                }
                break;
        }
    }

    // Determines the CorElementType for a type handle, mapping System.Object and System.String
    // to their specific element types (the runtime's GetSignatureCorElementType returns E_T_CLASS
    // for both Object and String).
    private static CorElementType GetElementType(IRuntimeTypeSystem rts, TypeHandle typeHandle)
    {
        if (typeHandle.IsNull)
            return CorElementType.Void;

        if (rts.IsString(typeHandle))
            return CorElementType.String;

        if (rts.IsObject(typeHandle))
            return CorElementType.Object;

        return rts.GetSignatureCorElementType(typeHandle);
    }

    // Mirrors native TypeHandle::UpCastTypeIfNeeded — for continuation types, returns the
    // parent (continuation base) type handle instead.
    private static TypeHandle UpCastTypeIfNeeded(IRuntimeTypeSystem rts, TypeHandle typeHandle)
    {
        if (rts.IsContinuationWithoutMetadata(typeHandle))
        {
            TargetPointer parentMT = rts.GetParentMethodTable(typeHandle);
            if (parentMT != TargetPointer.Null)
                return rts.GetTypeHandle(parentMT);
        }
        return typeHandle;
    }

    // Fills ArrayTypeData for E_T_ARRAY and E_T_SZARRAY.
    // Mirrors native DacDbiInterfaceImpl::GetArrayTypeInfo.
    private void FillArrayTypeInfo(IRuntimeTypeSystem rts, TypeHandle typeHandle, DebuggerIPCE_ExpandedTypeData* pTypeInfo)
    {
        Debug.Assert(rts.IsArray(typeHandle, out _));
        rts.IsArray(typeHandle, out uint rank);
        WriteLittleEndian(ref pTypeInfo->ArrayTypeData_arrayRank, rank);
        TypeHandle elemTypeHandle = rts.GetTypeParam(typeHandle);
        FillBasicTypeInfo(rts, elemTypeHandle, out pTypeInfo->ArrayTypeData_arrayTypeArg);
    }

    // Fills UnaryTypeData for E_T_PTR and E_T_BYREF (or ClassTypeData if AllBoxed).
    private void FillPtrTypeInfo(IRuntimeTypeSystem rts, AreValueTypesBoxed boxed, TypeHandle typeHandle, DebuggerIPCE_ExpandedTypeData* pTypeInfo)
    {
        if (boxed == AreValueTypesBoxed.AllBoxed)
        {
            FillClassTypeInfo(rts, typeHandle, pTypeInfo);
        }
        else
        {
            TypeHandle paramTypeHandle = rts.GetTypeParam(typeHandle);
            FillBasicTypeInfo(rts, paramTypeHandle, out pTypeInfo->UnaryTypeData_unaryTypeArg);
        }
    }

    // Fills ClassTypeData for E_T_CLASS and E_T_VALUETYPE.
    private void FillClassTypeInfo(IRuntimeTypeSystem rts, TypeHandle typeHandle, DebuggerIPCE_ExpandedTypeData* pTypeInfo)
    {
        typeHandle = UpCastTypeIfNeeded(rts, typeHandle);

        TargetPointer modulePtr = rts.GetModule(typeHandle);
        Contracts.ILoader loader = _target.Contracts.Loader;
        Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePtr);

        ReadOnlySpan<TypeHandle> instantiation = rts.GetInstantiation(typeHandle);
        if (instantiation.Length > 0)
        {
            // Generic instantiation — set the type handle so the debugger can fetch type arguments
            WriteLittleEndian(ref pTypeInfo->ClassTypeData_typeHandle, typeHandle.Address.Value);
        }
        // else: non-generic — typeHandle stays null

        WriteLittleEndian(ref pTypeInfo->ClassTypeData_metadataToken, rts.GetTypeDefToken(typeHandle));

        Debug.Assert(modulePtr != TargetPointer.Null);
        WriteLittleEndian(ref pTypeInfo->ClassTypeData_vmAssembly, loader.GetAssembly(moduleHandle).Value);
    }

    // Fills NaryTypeData for E_T_FNPTR (or ClassTypeData if AllBoxed).
    private void FillFnPtrTypeInfo(IRuntimeTypeSystem rts, AreValueTypesBoxed boxed, TypeHandle typeHandle, DebuggerIPCE_ExpandedTypeData* pTypeInfo)
    {
        if (boxed == AreValueTypesBoxed.AllBoxed)
        {
            FillClassTypeInfo(rts, typeHandle, pTypeInfo);
        }
        else
        {
            WriteLittleEndian(ref pTypeInfo->NaryTypeData_typeHandle, typeHandle.Address.Value);
        }
    }

    // Fills a DebuggerIPCE_BasicTypeData for a type handle — used for array element types
    // and ptr/byref referent types. Exposed as internal so tests can build the ArgInfoList
    // needed to round-trip a TypeHandle through GetExactTypeHandle.
    internal void FillBasicTypeInfo(IRuntimeTypeSystem rts, TypeHandle typeHandle, out DebuggerIPCE_BasicTypeData typeInfo)
    {
        typeInfo = default;
        CorElementType elementType = GetElementType(rts, typeHandle);
        WriteLittleEndian(ref typeInfo.elementType, (int)elementType);

        switch (elementType)
        {
            case CorElementType.Array:
            case CorElementType.SzArray:
            case CorElementType.FnPtr:
            case CorElementType.Ptr:
            case CorElementType.Byref:
                WriteLittleEndian(ref typeInfo.vmTypeHandle, typeHandle.Address.Value);
                // metadataToken and vmAssembly stay zero
                break;

            case CorElementType.Class:
            case CorElementType.ValueType:
            {
                typeHandle = UpCastTypeIfNeeded(rts, typeHandle);

                TargetPointer modulePtr = rts.GetModule(typeHandle);
                Contracts.ILoader loader = _target.Contracts.Loader;
                Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePtr);

                ReadOnlySpan<TypeHandle> instantiation = rts.GetInstantiation(typeHandle);
                if (instantiation.Length > 0)
                {
                    WriteLittleEndian(ref typeInfo.vmTypeHandle, typeHandle.Address.Value);
                }
                // else: vmTypeHandle stays null

                WriteLittleEndian(ref typeInfo.metadataToken, rts.GetTypeDefToken(typeHandle));
                Debug.Assert(modulePtr != TargetPointer.Null);
                WriteLittleEndian(ref typeInfo.vmAssembly, loader.GetAssembly(moduleHandle).Value);
                break;
            }

            default:
                // All fields zero
                break;
        }
    }

    // Little-endian read/write helpers for IPCE structs.
    // Native IPCE structs use Portable<T>, which stores data in little-endian format.
    // These helpers ensure managed reads/writes match that convention.
    private static void WriteLittleEndian<T>(ref T dest, T value) where T : unmanaged, IBinaryInteger<T>
    {
        if (BitConverter.IsLittleEndian)
        {
            dest = value;
        }
        else
        {
            Span<byte> destBytes = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref dest, 1));
            value.WriteLittleEndian(destBytes);
        }
    }

    private static T ReadLittleEndian<T>(T value) where T : unmanaged, IBinaryInteger<T>
    {
        if (BitConverter.IsLittleEndian)
            return value;
        MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref value, 1)).Reverse();
        return value;
    }

}