File: Dbi\DacDbiImpl.NativeCodeInfo.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.Collections.Generic;
using System.Diagnostics;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;
using Microsoft.Diagnostics.DataContractReader.Contracts;

namespace Microsoft.Diagnostics.DataContractReader.Legacy;

public sealed unsafe partial class DacDbiImpl
{
    // Gets the number of fixed arguments (i.e., the explicit args and the "this" pointer) from the method signature.
    // This does not include other implicit arguments or varargs.
    private uint GetArgCount(ulong vmMethodDesc)
    {
        IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
        MethodDescHandle mdh = rts.GetMethodDescHandle(new TargetPointer(vmMethodDesc));

        if (!rts.TryGetMethodSignature(mdh, out ReadOnlySpan<byte> signature))
            throw Marshal.GetExceptionForHR(HResults.E_FAIL)!;

        MethodSignatureHelpers.GetSignatureInfo(signature, out _, out uint numArgs);
        return numArgs;
    }

    internal static NativeVarInfo ConvertToNativeVarInfo(DebugVarInfo varInfo)
    {
        NativeVarInfo nvi = default;
        nvi.startOffset = varInfo.StartOffset;
        nvi.endOffset = varInfo.EndOffset;
        nvi.callReturnValueILOffset = varInfo.CallReturnValueILOffset;
        nvi.varNumber = varInfo.VarNumber;
        nvi.loc = ConvertToVarLoc(varInfo);
        return nvi;
    }

    internal static DbiOffsetMapping ConvertToDbiOffsetMapping(Contracts.OffsetMapping mapping)
    {
        DbiOffsetMapping nativeMapping = default;
        nativeMapping.nativeOffset = mapping.NativeOffset;
        nativeMapping.ilOffset = mapping.ILOffset;
        nativeMapping.source = ConvertSourceTypesToNative(mapping.SourceType);
        return nativeMapping;
    }

    internal static VarLoc ConvertToVarLoc(DebugVarInfo varInfo)
    {
        VarLoc loc = default;
        loc.vlType = (varInfo.Kind, varInfo.IsByRef, varInfo.IsFloatingPoint) switch
        {
            (DebugVarLocKind.Register, false, false) => VarLocType.VLT_REG,
            (DebugVarLocKind.Register, false, true) => VarLocType.VLT_REG_FP,
            (DebugVarLocKind.Register, true, _) => VarLocType.VLT_REG_BYREF,
            (DebugVarLocKind.Stack, false, _) => VarLocType.VLT_STK,
            (DebugVarLocKind.Stack, true, _) => VarLocType.VLT_STK_BYREF,
            (DebugVarLocKind.RegisterRegister, _, _) => VarLocType.VLT_REG_REG,
            (DebugVarLocKind.RegisterStack, _, _) => VarLocType.VLT_REG_STK,
            (DebugVarLocKind.StackRegister, _, _) => VarLocType.VLT_STK_REG,
            (DebugVarLocKind.DoubleStack, _, _) => VarLocType.VLT_STK2,
            (DebugVarLocKind.FloatingPointStack, _, _) => VarLocType.VLT_FPSTK,
            (DebugVarLocKind.FixedVarArg, _, _) => VarLocType.VLT_FIXED_VA,
            _ => VarLocType.VLT_INVALID,
        };

        switch (varInfo.Kind)
        {
            case DebugVarLocKind.Register:
                loc.vlrReg = varInfo.Register;
                break;
            case DebugVarLocKind.Stack:
                loc.vlsBaseReg = varInfo.BaseRegister;
                loc.vlsOffset = varInfo.StackOffset;
                break;
            case DebugVarLocKind.RegisterRegister:
                loc.vlrrReg1 = varInfo.Register;
                loc.vlrrReg2 = varInfo.Register2;
                break;
            case DebugVarLocKind.RegisterStack:
                loc.vlrsReg = varInfo.Register;
                loc.vlrssBaseReg = varInfo.BaseRegister2;
                loc.vlrssOffset = varInfo.StackOffset2;
                break;
            case DebugVarLocKind.StackRegister:
                loc.vlsrsBaseReg = varInfo.BaseRegister;
                loc.vlsrsOffset = varInfo.StackOffset;
                loc.vlsrReg = varInfo.Register;
                break;
            case DebugVarLocKind.DoubleStack:
                loc.vlsBaseReg = varInfo.BaseRegister;
                loc.vlsOffset = varInfo.StackOffset;
                break;
            case DebugVarLocKind.FloatingPointStack:
                loc.vlfReg = varInfo.FloatingPointStackRegister;
                break;
            case DebugVarLocKind.FixedVarArg:
                loc.vlfvOffset = varInfo.FixedVarArgOffset;
                break;
        }

        return loc;
    }

    // Converts cDAC Contracts.SourceTypes to native ICorDebugInfo::SourceTypes values.
    // The cDAC uses compact bit positions while the native enum uses different bit values.
    internal static DbiSourceTypes ConvertSourceTypesToNative(Contracts.SourceTypes source)
    {
        DbiSourceTypes result = DbiSourceTypes.SourceTypeInvalid;
        if ((source & Contracts.SourceTypes.StackEmpty) != 0)
            result |= DbiSourceTypes.StackEmpty;
        if ((source & Contracts.SourceTypes.CallInstruction) != 0)
            result |= DbiSourceTypes.CallInstruction;
        if ((source & Contracts.SourceTypes.Async) != 0)
            result |= DbiSourceTypes.Async;

        return result;
    }

#if DEBUG
    private void ValidateNativeCodeInfoAgainstLegacy(
        ulong vmMethodDesc,
        ulong startAddress,
        Interop.BOOL fCodeAvailable,
        uint* pFixedArgCount,
        List<NativeVarInfo> cdacVarInfos,
        List<DbiOffsetMapping> cdacSeqPoints,
        int hr,
        bool varInfoRequested,
        bool seqPointsRequested)
    {
        uint dacFixedArgCount = 0;
        var dacData = new DebugNativeCodeData();
        GCHandle dacHandle = GCHandle.Alloc(dacData);
        int hrLocal;
        try
        {
            hrLocal = _legacy!.GetNativeCodeSequencePointsAndVarInfo(
                vmMethodDesc, startAddress, fCodeAvailable, &dacFixedArgCount,
                (delegate* unmanaged<NativeVarInfo*, void*, void>)&CollectNativeVarInfoCallback,
                (delegate* unmanaged<DbiOffsetMapping*, void*, void>)&CollectOffsetMappingCallback,
                GCHandle.ToIntPtr(dacHandle));
        }
        finally
        {
            dacHandle.Free();
        }

        Debug.ValidateHResult(hr, hrLocal);
        if (hr == HResults.S_OK)
        {
            if (pFixedArgCount != null)
            {
                Debug.Assert(*pFixedArgCount == dacFixedArgCount,
                    $"fixedArgCount mismatch - cDAC: {*pFixedArgCount}, DAC: {dacFixedArgCount}");
            }

            // Only compare lists whose callback was supplied.
            if (seqPointsRequested)
                AssertSeqPointsEqual(cdacSeqPoints, dacData.SeqPoints);
            if (varInfoRequested)
                AssertVarInfosEqual(cdacVarInfos, dacData.VarInfos);
        }
    }

    private static void AssertSeqPointsEqual(List<DbiOffsetMapping> cdac, List<DbiOffsetMapping> dac)
    {
        Debug.Assert(cdac.Count == dac.Count,
            $"SeqPoint count mismatch - cDAC: {cdac.Count}, DAC: {dac.Count}");
        int n = Math.Min(cdac.Count, dac.Count);
        for (int i = 0; i < n; i++)
        {
            DbiOffsetMapping c = cdac[i];
            DbiOffsetMapping d = dac[i];
            Debug.Assert(c.nativeOffset == d.nativeOffset,
                $"SeqPoint[{i}] nativeOffset mismatch - cDAC: {c.nativeOffset}, DAC: {d.nativeOffset}");
            Debug.Assert(c.ilOffset == d.ilOffset,
                $"SeqPoint[{i}] ilOffset mismatch - cDAC: {c.ilOffset}, DAC: {d.ilOffset}");
            Debug.Assert(c.source == d.source,
                $"SeqPoint[{i}] source mismatch - cDAC: 0x{c.source:X}, DAC: 0x{d.source:X}");
        }
    }

    private static void AssertVarInfosEqual(List<NativeVarInfo> cdac, List<NativeVarInfo> dac)
    {
        Debug.Assert(cdac.Count == dac.Count,
            $"VarInfo count mismatch - cDAC: {cdac.Count}, DAC: {dac.Count}");
        int n = Math.Min(cdac.Count, dac.Count);
        for (int i = 0; i < n; i++)
        {
            NativeVarInfo c = cdac[i];
            NativeVarInfo d = dac[i];
            Debug.Assert(c.startOffset == d.startOffset,
                $"VarInfo[{i}] startOffset mismatch - cDAC: {c.startOffset}, DAC: {d.startOffset}");
            Debug.Assert(c.endOffset == d.endOffset,
                $"VarInfo[{i}] endOffset mismatch - cDAC: {c.endOffset}, DAC: {d.endOffset}");
            Debug.Assert(c.callReturnValueILOffset == d.callReturnValueILOffset,
                $"VarInfo[{i}] callReturnValueILOffset mismatch - cDAC: {c.callReturnValueILOffset}, DAC: {d.callReturnValueILOffset}");
            Debug.Assert(c.varNumber == d.varNumber,
                $"VarInfo[{i}] varNumber mismatch - cDAC: {c.varNumber}, DAC: {d.varNumber}");
            Debug.Assert(c.loc.vlType == d.loc.vlType,
                $"VarInfo[{i}] vlType mismatch - cDAC: {c.loc.vlType}, DAC: {d.loc.vlType}");

            switch (c.loc.vlType)
            {
                case VarLocType.VLT_REG:
                case VarLocType.VLT_REG_FP:
                case VarLocType.VLT_REG_BYREF:
                    Debug.Assert(c.loc.vlrReg == d.loc.vlrReg,
                        $"VarInfo[{i}] vlrReg mismatch - cDAC: {c.loc.vlrReg}, DAC: {d.loc.vlrReg}");
                    break;
                case VarLocType.VLT_STK:
                case VarLocType.VLT_STK_BYREF:
                case VarLocType.VLT_STK2:
                    Debug.Assert(c.loc.vlsBaseReg == d.loc.vlsBaseReg,
                        $"VarInfo[{i}] vlsBaseReg mismatch - cDAC: {c.loc.vlsBaseReg}, DAC: {d.loc.vlsBaseReg}");
                    Debug.Assert(c.loc.vlsOffset == d.loc.vlsOffset,
                        $"VarInfo[{i}] vlsOffset mismatch - cDAC: {c.loc.vlsOffset}, DAC: {d.loc.vlsOffset}");
                    break;
                case VarLocType.VLT_REG_REG:
                    Debug.Assert(c.loc.vlrrReg1 == d.loc.vlrrReg1,
                        $"VarInfo[{i}] vlrrReg1 mismatch - cDAC: {c.loc.vlrrReg1}, DAC: {d.loc.vlrrReg1}");
                    Debug.Assert(c.loc.vlrrReg2 == d.loc.vlrrReg2,
                        $"VarInfo[{i}] vlrrReg2 mismatch - cDAC: {c.loc.vlrrReg2}, DAC: {d.loc.vlrrReg2}");
                    break;
                case VarLocType.VLT_REG_STK:
                    Debug.Assert(c.loc.vlrsReg == d.loc.vlrsReg,
                        $"VarInfo[{i}] vlrsReg mismatch - cDAC: {c.loc.vlrsReg}, DAC: {d.loc.vlrsReg}");
                    Debug.Assert(c.loc.vlrssBaseReg == d.loc.vlrssBaseReg,
                        $"VarInfo[{i}] vlrssBaseReg mismatch - cDAC: {c.loc.vlrssBaseReg}, DAC: {d.loc.vlrssBaseReg}");
                    Debug.Assert(c.loc.vlrssOffset == d.loc.vlrssOffset,
                        $"VarInfo[{i}] vlrssOffset mismatch - cDAC: {c.loc.vlrssOffset}, DAC: {d.loc.vlrssOffset}");
                    break;
                case VarLocType.VLT_STK_REG:
                    Debug.Assert(c.loc.vlsrsBaseReg == d.loc.vlsrsBaseReg,
                        $"VarInfo[{i}] vlsrsBaseReg mismatch - cDAC: {c.loc.vlsrsBaseReg}, DAC: {d.loc.vlsrsBaseReg}");
                    Debug.Assert(c.loc.vlsrsOffset == d.loc.vlsrsOffset,
                        $"VarInfo[{i}] vlsrsOffset mismatch - cDAC: {c.loc.vlsrsOffset}, DAC: {d.loc.vlsrsOffset}");
                    Debug.Assert(c.loc.vlsrReg == d.loc.vlsrReg,
                        $"VarInfo[{i}] vlsrReg mismatch - cDAC: {c.loc.vlsrReg}, DAC: {d.loc.vlsrReg}");
                    break;
                case VarLocType.VLT_FPSTK:
                    Debug.Assert(c.loc.vlfReg == d.loc.vlfReg,
                        $"VarInfo[{i}] vlfReg mismatch - cDAC: {c.loc.vlfReg}, DAC: {d.loc.vlfReg}");
                    break;
                case VarLocType.VLT_FIXED_VA:
                    Debug.Assert(c.loc.vlfvOffset == d.loc.vlfvOffset,
                        $"VarInfo[{i}] vlfvOffset mismatch - cDAC: {c.loc.vlfvOffset}, DAC: {d.loc.vlfvOffset}");
                    break;
            }
        }
    }

    private sealed class DebugNativeCodeData
    {
        public List<NativeVarInfo> VarInfos { get; } = new();
        public List<DbiOffsetMapping> SeqPoints { get; } = new();
    }

    [UnmanagedCallersOnly]
    private static void CollectNativeVarInfoCallback(NativeVarInfo* data, void* pUserData)
    {
        GCHandle handle = GCHandle.FromIntPtr((nint)pUserData);
        ((DebugNativeCodeData)handle.Target!).VarInfos.Add(*data);
    }

    [UnmanagedCallersOnly]
    private static void CollectOffsetMappingCallback(DbiOffsetMapping* data, void* pUserData)
    {
        GCHandle handle = GCHandle.FromIntPtr((nint)pUserData);
        ((DebugNativeCodeData)handle.Target!).SeqPoints.Add(*data);
    }
#endif
}