File: ClrDataStackWalk.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.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using Microsoft.Diagnostics.DataContractReader.Contracts;
using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers;

namespace Microsoft.Diagnostics.DataContractReader.Legacy;

[GeneratedComClass]
public sealed unsafe partial class ClrDataStackWalk : IXCLRDataStackWalk
{
    private readonly TargetPointer _threadAddr;
    private readonly CLRDataStackWalkFlag _flags;
    private readonly Target _target;
    private readonly IXCLRDataStackWalk? _legacyImpl;
    private readonly ThreadData _threadData;

    private bool _currentFrameIsValid;
    private IEnumerator<IStackDataFrameHandle> _dataFrames;

    public ClrDataStackWalk(TargetPointer threadAddr, CLRDataStackWalkFlag flags, Target target, IXCLRDataStackWalk? legacyImpl)
    {
        _threadAddr = threadAddr;
        _flags = flags;
        _target = target;
        _legacyImpl = legacyImpl;

        _threadData = _target.Contracts.Thread.GetThreadData(_threadAddr);
        _dataFrames = _target.Contracts.StackWalk.CreateStackWalk(_threadData).GetEnumerator();

        // IEnumerator<T> begins before the first element.
        // Call MoveNext() to set _dataFrames.Current to the first element.
        _currentFrameIsValid = MoveNextLegacyVisible();
    }

    /// <summary>
    /// Advance the enumerator to the next frame that the legacy SOSDAC stack walker
    /// would have surfaced.
    /// </summary>
    private bool MoveNextLegacyVisible()
    {
        while (_dataFrames.MoveNext())
        {
            if (IsLegacyVisible(_dataFrames.Current))
            {
                return true;
            }
        }
        return false;
    }

    internal bool IsLegacyVisible(IStackDataFrameHandle frame)
        => frame.State is StackWalkState.Frameless
            || ((_flags & CLRDataStackWalkFlag.CLRDATA_SIMPFRAME_RUNTIME_UNMANAGED_CODE) != 0
                && frame.State is StackWalkState.Frame or StackWalkState.SkippedFrame);

    private void Reseed(byte[] context, bool isFirst)
    {
        _dataFrames.Dispose();
        _dataFrames = _target.Contracts.StackWalk.CreateStackWalk(_threadData, context, isFirst).GetEnumerator();
        _currentFrameIsValid = MoveNextLegacyVisible();
    }

    int IXCLRDataStackWalk.GetContext(uint contextFlags, uint contextBufSize, uint* contextSize, [MarshalUsing(CountElementName = "contextBufSize"), Out] byte[] contextBuf)
    {
        int hr = HResults.S_OK;

        if (_currentFrameIsValid)
        {
            IStackWalk sw = _target.Contracts.StackWalk;
            IStackDataFrameHandle dataFrame = _dataFrames.Current;
            byte[] context = sw.GetRawContext(dataFrame);
            if (context.Length > contextBufSize)
                hr = HResults.E_INVALIDARG;

            if (contextSize is not null)
            {
                *contextSize = (uint)context.Length;
            }

            context.CopyTo(contextBuf);
        }
        else
        {
            hr = HResults.S_FALSE;
        }


#if DEBUG
        if (_legacyImpl is not null)
        {
            byte[] localContextBuf = new byte[contextBufSize];
            int hrLocal = _legacyImpl.GetContext(contextFlags, contextBufSize, null, localContextBuf);
            Debug.ValidateHResult(hr, hrLocal);

            if (hr == HResults.S_OK)
            {
                IPlatformAgnosticContext contextStruct = IPlatformAgnosticContext.GetContextForPlatform(_target);
                IPlatformAgnosticContext localContextStruct = IPlatformAgnosticContext.GetContextForPlatform(_target);
                contextStruct.FillFromBuffer(contextBuf);
                localContextStruct.FillFromBuffer(localContextBuf);

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

        return hr;
    }

    int IXCLRDataStackWalk.GetFrame(DacComNullableByRef<IXCLRDataFrame> frame)
    {
        int hr = HResults.S_OK;

        IXCLRDataFrame? legacyFrame = null;
        if (_legacyImpl is not null)
        {
            DacComNullableByRef<IXCLRDataFrame> legacyFrameOut = new(isNullRef: false);
            int hrLocal = _legacyImpl.GetFrame(legacyFrameOut);
            if (hrLocal < 0)
                return hrLocal;
            legacyFrame = legacyFrameOut.Interface;
        }

        try
        {
            if (!_currentFrameIsValid)
                throw new ArgumentException();

            frame.Interface = new ClrDataFrame(_target, _threadAddr, _dataFrames.Current, legacyFrame);
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

        return hr;
    }
    int IXCLRDataStackWalk.GetFrameType(CLRDataSimpleFrameType* simpleType, CLRDataDetailedFrameType* detailedType)
    {
        int hr = HResults.S_OK;
        try
        {
            if (_currentFrameIsValid)
            {
                IStackDataFrameHandle dataFrame = _dataFrames.Current;
                if (simpleType is not null)
                {
                    *simpleType = dataFrame.State switch
                    {
                        StackWalkState.Frameless => CLRDataSimpleFrameType.CLRDATA_SIMPFRAME_MANAGED_METHOD,
                        StackWalkState.Frame or StackWalkState.SkippedFrame => CLRDataSimpleFrameType.CLRDATA_SIMPFRAME_RUNTIME_UNMANAGED_CODE,
                        _ => CLRDataSimpleFrameType.CLRDATA_SIMPFRAME_UNRECOGNIZED,
                    };
                }

                if (detailedType is not null)
                {
                    *detailedType = dataFrame.IsExceptionFrame
                        ? CLRDataDetailedFrameType.CLRDATA_DETFRAME_EXCEPTION_FILTER
                        : CLRDataDetailedFrameType.CLRDATA_DETFRAME_UNRECOGNIZED;
                }
            }
            else
            {
                hr = HResults.S_FALSE;
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacyImpl is not null)
        {
            CLRDataSimpleFrameType simpleTypeLocal = 0;
            CLRDataDetailedFrameType detailedTypeLocal = 0;
            int hrLocal = _legacyImpl.GetFrameType(&simpleTypeLocal, &detailedTypeLocal);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
            {
                if (simpleType is not null)
                    Debug.Assert(*simpleType == simpleTypeLocal, $"cDAC: {*simpleType:x}, DAC: {simpleTypeLocal:x}");
                if (detailedType is not null)
                    Debug.Assert(*detailedType == detailedTypeLocal, $"cDAC: {*detailedType:x}, DAC: {detailedTypeLocal:x}");
            }
        }
#endif

        return hr;
    }
    int IXCLRDataStackWalk.GetStackSizeSkipped(ulong* stackSizeSkipped)
        => LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.GetStackSizeSkipped(stackSizeSkipped) : HResults.E_NOTIMPL;
    int IXCLRDataStackWalk.Next()
    {
        int hr;
        try
        {
            _currentFrameIsValid = MoveNextLegacyVisible();
            hr = _currentFrameIsValid ? HResults.S_OK : HResults.S_FALSE;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

        // Advance the legacy stack walk to keep it in sync with the cDAC walk.
        // GetFrame() passes the legacy frame to ClrDataFrame, which delegates
        // GetArgumentByIndex/GetLocalVariableByIndex to it. If we don't advance
        // the legacy walk here, those calls operate on the wrong frame.
        if (_legacyImpl is not null)
        {
            int hrLocal = _legacyImpl.Next();
#if DEBUG
            Debug.ValidateHResult(hr, hrLocal);
#endif
        }

        return hr;
    }
    int IXCLRDataStackWalk.Request(uint reqCode, uint inBufferSize, byte* inBuffer, uint outBufferSize, byte* outBuffer)
    {
        const uint DACSTACKPRIV_REQUEST_FRAME_DATA = 0xf0000000;

        int hr = HResults.S_OK;
        try
        {
            switch (reqCode)
            {
                case (uint)CLRDataGeneralRequest.CLRDATA_REQUEST_REVISION:
                    if (inBufferSize != 0 || inBuffer != null || outBufferSize != sizeof(uint) || outBuffer == null)
                        throw new ArgumentException("Invalid buffer parameters for CLRDATA_REQUEST_REVISION");
                    *(uint*)outBuffer = 1;
                    hr = HResults.S_OK;
                    break;
                case (uint)CLRDataStackWalkRequest.CLRDATA_STACK_WALK_REQUEST_SET_FIRST_FRAME:
                    if (inBufferSize != sizeof(uint) || inBuffer == null || outBufferSize != 0)
                        throw new ArgumentException("Invalid buffer parameters for CLRDATA_STACK_WALK_REQUEST_SET_FIRST_FRAME");
                    hr = HResults.S_OK; // no-op for the case where we use this
                    break;
                case DACSTACKPRIV_REQUEST_FRAME_DATA:
                    if (inBufferSize != 0 || inBuffer != null || outBufferSize != sizeof(ulong) || outBuffer == null)
                        throw new ArgumentException("Invalid buffer parameters for DACSTACKPRIV_REQUEST_FRAME_DATA");
                    if (!_currentFrameIsValid)
                        throw new ArgumentException("Invalid frame");

                    IStackWalk sw = _target.Contracts.StackWalk;
                    IStackDataFrameHandle frameData = _dataFrames.Current;
                    TargetPointer frameAddr = sw.GetFrameAddress(frameData);
                    *(ulong*)outBuffer = frameAddr.ToClrDataAddress(_target);
                    hr = HResults.S_OK;
                    break;
                default:
                    throw new ArgumentException("Invalid request code");
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacyImpl is not null)
        {
            byte[] localOutBuffer = new byte[(int)outBufferSize];
            fixed (byte* localOutBufferPtr = localOutBuffer)
            {
                int hrLocal = _legacyImpl.Request(reqCode, inBufferSize, inBuffer, outBufferSize, localOutBufferPtr);
                Debug.ValidateHResult(hr, hrLocal);
                if (hr == HResults.S_OK)
                    Debug.Assert(new ReadOnlySpan<byte>(outBuffer, (int)outBufferSize).SequenceEqual(localOutBuffer));
            }
        }
#endif
        return hr;
    }
    int IXCLRDataStackWalk.SetContext(uint contextSize, [In, MarshalUsing(CountElementName = "contextSize")] byte[] context)
    {
        int hr = HResults.S_OK;
        try
        {
            uint platformContextSize = IPlatformAgnosticContext.GetContextForPlatform(_target).Size;
            if (context is null || contextSize < platformContextSize)
                throw new ArgumentException("Invalid context buffer");

            bool isFirst = _currentFrameIsValid && _dataFrames.Current.IsActiveFrame;
            Reseed(context, isFirst);
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

        if (_legacyImpl is not null)
        {
            int hrLocal = _legacyImpl.SetContext(contextSize, context);
#if DEBUG
            Debug.ValidateHResult(hr, hrLocal);
#endif
        }

        return hr;
    }

    int IXCLRDataStackWalk.SetContext2(uint flags, uint contextSize, [In, MarshalUsing(CountElementName = "contextSize")] byte[] context)
        => HResults.E_NOTIMPL;
}