File: Contracts\Exception_1.cs
Web Access
Project: src\runtime\src\native\managed\cdac\Microsoft.Diagnostics.DataContractReader.Contracts\Microsoft.Diagnostics.DataContractReader.Contracts.csproj (Microsoft.Diagnostics.DataContractReader.Contracts)
// 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;

namespace Microsoft.Diagnostics.DataContractReader.Contracts;

internal readonly struct Exception_1 : IException
{
    // STEF_LAST_FRAME_FROM_FOREIGN_STACK_TRACE flag value from src/coreclr/vm/clrex.h.
    private const int STEF_LAST_FRAME_FROM_FOREIGN_STACK_TRACE = 0x0001;

    private readonly Target _target;

    internal Exception_1(Target target)
    {
        _target = target;
    }

    TargetPointer IException.GetNestedExceptionInfo(TargetPointer exceptionInfoAddr, out TargetPointer nextNestedExceptionInfo, out TargetPointer thrownObjectHandle)
    {
        Data.ExceptionInfo exceptionInfo = _target.ProcessedData.GetOrAdd<Data.ExceptionInfo>(exceptionInfoAddr);
        nextNestedExceptionInfo = exceptionInfo.PreviousNestedInfo;
        // ThrownObject is a direct object pointer stored in ExInfo::m_exception.
        // Return the address of the field as a "handle" - reading through it yields the
        // exception Object*. This has the same lifetime as the ExInfo (both are invalidated
        // when PopExInfos calls ReleaseResources). See dacimpl.h for the equivalent native
        // DAC documentation.
        Target.TypeInfo type = _target.GetTypeInfo(DataType.ExceptionInfo);
        thrownObjectHandle = exceptionInfoAddr + (ulong)type.Fields[nameof(Data.ExceptionInfo.ThrownObject)].Offset;
        return exceptionInfo.ThrownObject;
    }

    ExceptionData IException.GetExceptionData(TargetPointer exceptionAddr)
    {
        Data.Exception exception = _target.ProcessedData.GetOrAdd<Data.Exception>(exceptionAddr);
        return new ExceptionData(
            exception.Message,
            exception.InnerException,
            exception.StackTrace,
            exception.WatsonBuckets,
            exception.StackTraceString,
            exception.RemoteStackTraceString,
            exception.HResult,
            exception.XCode);
    }

    IEnumerable<ExceptionStackFrameInfo> IException.GetExceptionStackFrames(TargetPointer exceptionAddr)
    {
        Data.Exception exception = _target.ProcessedData.GetOrAdd<Data.Exception>(exceptionAddr);
        TargetPointer stackTraceObj = exception.StackTrace;
        if (stackTraceObj == TargetPointer.Null)
            yield break;

        // Path 1: the stack trace object's MethodTable ContainsGCPointers. The object is a
        //         combined object[] whose first slot is the actual stack-trace I1Array and
        //         whose subsequent slots are the keep-alive references. We unwrap to slot 0.
        // Path 2: the stack trace object is itself the I1Array payload.
        IObject objectContract = _target.Contracts.Object;
        IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem;

        TargetPointer mt = objectContract.GetMethodTableAddress(stackTraceObj);
        if (mt == TargetPointer.Null)
            throw new InvalidOperationException($"Stack trace object 0x{stackTraceObj.Value:x} has no MethodTable.");
        TypeHandle stackTraceHandle = rtsContract.GetTypeHandle(mt);

        TargetPointer i1ArrayAddr;
        if (rtsContract.ContainsGCPointers(stackTraceHandle))
        {
            // Combined PTRArray; slot 0 holds the I1Array pointer.
            Data.Array combinedArray = _target.ProcessedData.GetOrAdd<Data.Array>(stackTraceObj);
            i1ArrayAddr = _target.ReadPointer(combinedArray.DataPointer);
        }
        else
        {
            i1ArrayAddr = stackTraceObj;
        }

        if (i1ArrayAddr == TargetPointer.Null)
            yield break;

        Data.Array i1Array = _target.ProcessedData.GetOrAdd<Data.Array>(i1ArrayAddr);
        TargetPointer payload = i1Array.DataPointer;

        Data.StackTraceArrayHeader header = _target.ProcessedData.GetOrAdd<Data.StackTraceArrayHeader>(payload);
        uint frameCount = header.Size;
        if (frameCount == 0)
            yield break;

        Target.TypeInfo elementTypeInfo = _target.GetTypeInfo(DataType.StackTraceElement);
        ulong elementSize = elementTypeInfo.Size!.Value;

        uint headerSize = _target.GetTypeInfo(DataType.StackTraceArrayHeader).Size!.Value;
        TargetPointer cursor = payload + headerSize;
        for (uint i = 0; i < frameCount; i++)
        {
            Data.StackTraceElement element = _target.ProcessedData.GetOrAdd<Data.StackTraceElement>(cursor);
            yield return new ExceptionStackFrameInfo(
                element.Ip,
                element.MethodDesc,
                (element.Flags & STEF_LAST_FRAME_FROM_FOREIGN_STACK_TRACE) != 0);
            cursor += elementSize;
        }
    }
}