File: src\coreclr\nativeaot\Runtime.Base\src\System\Runtime\ExceptionHandling.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
 
#if NATIVEAOT
using Internal.Runtime;
#else
using System.Diagnostics.CodeAnalysis;
using System.Runtime.ExceptionServices;
#endif
 
// Disable: Filter expression is a constant. We know. We just can't do an unfiltered catch.
#pragma warning disable 7095
 
namespace System.Runtime
{
    internal static unsafe partial class EH
    {
        internal static UIntPtr MaxSP
        {
            get
            {
                return (UIntPtr)(void*)(-1);
            }
        }
 
        private enum RhEHClauseKind
        {
            RH_EH_CLAUSE_TYPED = 0,
            RH_EH_CLAUSE_FAULT = 1,
            RH_EH_CLAUSE_FILTER = 2,
            RH_EH_CLAUSE_UNUSED = 3,
        }
 
        private struct RhEHClause
        {
            internal RhEHClauseKind _clauseKind;
            internal uint _tryStartOffset;
            internal uint _tryEndOffset;
            internal byte* _filterAddress;
            internal byte* _handlerAddress;
            internal void* _pTargetType;
#if !NATIVEAOT
            internal bool _isSameTry;
#endif
 
            ///<summary>
            /// We expect the stackwalker to adjust return addresses to point at 'return address - 1' so that we
            /// can use an interval here that is closed at the start and open at the end.  When a hardware fault
            /// occurs, the IP is pointing at the start of the instruction and will not be adjusted by the
            /// stackwalker.  Therefore, it will naturally work with an interval that has a closed start and open
            /// end.
            ///</summary>
            public bool ContainsCodeOffset(uint codeOffset)
            {
                return ((codeOffset >= _tryStartOffset) &&
                        (codeOffset < _tryEndOffset));
            }
        }
 
        [StructLayout(LayoutKind.Explicit, Size = AsmOffsets.SIZEOF__EHEnum)]
        private struct EHEnum
        {
            [FieldOffset(0)]
            private IntPtr _dummy; // For alignment
        }
 
        internal struct MethodRegionInfo
        {
            internal byte* _hotStartAddress;
            internal nuint _hotSize;
            internal byte* _coldStartAddress;
            internal nuint _coldSize;
        }
 
#pragma warning disable IDE0060
        // This is a fail-fast function used by the runtime as a last resort that will terminate the process with
        // as little effort as possible. No guarantee is made about the semantics of this fail-fast.
#if !NATIVEAOT
        [DoesNotReturn]
#endif
        internal static void FallbackFailFast(RhFailFastReason reason, object? unhandledException)
        {
#if NATIVEAOT
            InternalCalls.RhpFallbackFailFast();
#else
            // TODO-NewEH: Debugging the FailFasts is difficult, debugger doesn't stop here, but
            // only after the process exits. The Debugger.Break() is a temporary solution to
            // help during development. Figure out how to do it properly.
            System.Diagnostics.Debugger.Break();
            Environment.FailFast(reason.ToString());
#endif
        }
#pragma warning restore IDE0060
 
        // Given an address pointing somewhere into a managed module, get the classlib-defined fail-fast
        // function and invoke it.  Any failure to find and invoke the function, or if it returns, results in
        // MRT-defined fail-fast behavior.
#if !NATIVEAOT
        [DoesNotReturn]
#endif
        internal static void FailFastViaClasslib(RhFailFastReason reason, object? unhandledException,
            IntPtr classlibAddress)
        {
#if NATIVEAOT
            // Find the classlib function that will fail fast. This is a RuntimeExport function from the
            // classlib module, and is therefore managed-callable.
            IntPtr pFailFastFunction = (IntPtr)InternalCalls.RhpGetClasslibFunctionFromCodeAddress(classlibAddress,
                                                                           ClassLibFunctionId.FailFast);
 
            if (pFailFastFunction == IntPtr.Zero)
            {
                // The classlib didn't provide a function, so we fail our way...
                FallbackFailFast(reason, unhandledException);
            }
 
            try
            {
                // Invoke the classlib fail fast function.
                ((delegate*<RhFailFastReason, object, IntPtr, IntPtr, void>)pFailFastFunction)
                    (reason, unhandledException, IntPtr.Zero, IntPtr.Zero);
            }
            catch when (true)
            {
                // disallow all exceptions leaking out of callbacks
            }
 
            // The classlib's function should never return and should not throw. If it does, then we fail our way...
#endif
            FallbackFailFast(reason, unhandledException);
        }
 
#if TARGET_AMD64
        [StructLayout(LayoutKind.Explicit, Size = 0x4d0)]
#elif TARGET_ARM
        [StructLayout(LayoutKind.Explicit, Size = 0x1a0)]
#elif TARGET_X86
        [StructLayout(LayoutKind.Explicit, Size = 0x2cc)]
#elif TARGET_ARM64
        [StructLayout(LayoutKind.Explicit, Size = 0x390)]
#else
        [StructLayout(LayoutKind.Explicit, Size = 0x10)] // this is small enough that it should trip an assert in RhpCopyContextFromExInfo
#endif
        private struct OSCONTEXT
        {
        }
 
        internal static unsafe void* PointerAlign(void* ptr, int alignmentInBytes)
        {
            int alignMask = alignmentInBytes - 1;
#if TARGET_64BIT
            return (void*)((((long)ptr) + alignMask) & ~alignMask);
#else
            return (void*)((((int)ptr) + alignMask) & ~alignMask);
#endif
        }
 
#if NATIVEAOT
        private static void OnFirstChanceExceptionViaClassLib(object exception)
        {
            IntPtr pOnFirstChanceFunction =
                (IntPtr)InternalCalls.RhpGetClasslibFunctionFromEEType(exception.GetMethodTable(), ClassLibFunctionId.OnFirstChance);
 
            if (pOnFirstChanceFunction == IntPtr.Zero)
            {
                return;
            }
 
            try
            {
                ((delegate*<object, void>)pOnFirstChanceFunction)(exception);
            }
            catch when (true)
            {
                // disallow all exceptions leaking out of callbacks
            }
        }
#endif // NATIVEAOT
 
        private static void OnUnhandledExceptionViaClassLib(object exception)
        {
#if NATIVEAOT
            IntPtr pOnUnhandledExceptionFunction =
                (IntPtr)InternalCalls.RhpGetClasslibFunctionFromEEType(exception.GetMethodTable(), ClassLibFunctionId.OnUnhandledException);
 
            if (pOnUnhandledExceptionFunction == IntPtr.Zero)
            {
                return;
            }
 
            try
            {
                ((delegate*<object, void>)pOnUnhandledExceptionFunction)(exception);
            }
            catch when (true)
            {
                // disallow all exceptions leaking out of callbacks
            }
#else
            Debug.Assert(false, "Unhandled exceptions should be processed by the native runtime only");
#endif
        }
 
        [MethodImpl(MethodImplOptions.NoInlining)]
        internal static unsafe void UnhandledExceptionFailFastViaClasslib(
            RhFailFastReason reason, object unhandledException, IntPtr classlibAddress, ref ExInfo exInfo)
        {
#if NATIVEAOT
            IntPtr pFailFastFunction =
                (IntPtr)InternalCalls.RhpGetClasslibFunctionFromCodeAddress(classlibAddress, ClassLibFunctionId.FailFast);
 
            if (pFailFastFunction == IntPtr.Zero)
            {
                FailFastViaClasslib(
                    reason,
                    unhandledException,
                    classlibAddress);
            }
 
            // 16-byte align the context.  This is overkill on x86 and ARM, but simplifies things slightly.
            const int contextAlignment = 16;
            byte* pbBuffer = stackalloc byte[sizeof(OSCONTEXT) + contextAlignment];
            void* pContext = PointerAlign(pbBuffer, contextAlignment);
 
            InternalCalls.RhpCopyContextFromExInfo(pContext, sizeof(OSCONTEXT), exInfo._pExContext);
 
            try
            {
                ((delegate*<RhFailFastReason, object, IntPtr, void*, void>)pFailFastFunction)
                    (reason, unhandledException, exInfo._pExContext->IP, pContext);
            }
            catch when (true)
            {
                // disallow all exceptions leaking out of callbacks
            }
 
            // The classlib's function should never return and should not throw. If it does, then we fail our way...
            FallbackFailFast(reason, unhandledException);
#else
            FailFastViaClasslib(
                reason,
                unhandledException,
                classlibAddress);
#endif
        }
 
        private enum RhEHFrameType
        {
            RH_EH_FIRST_FRAME = 1,
            RH_EH_FIRST_RETHROW_FRAME = 2,
        }
 
        private static void AppendExceptionStackFrameViaClasslib(object exception, IntPtr ip,
            UIntPtr sp, ref ExInfo exInfo,
            ref bool isFirstRethrowFrame, ref bool isFirstFrame)
        {
            int flags = (isFirstFrame ? (int)RhEHFrameType.RH_EH_FIRST_FRAME : 0) |
                        (isFirstRethrowFrame ? (int)RhEHFrameType.RH_EH_FIRST_RETHROW_FRAME : 0);
#if NATIVEAOT
            IntPtr pAppendStackFrame = (IntPtr)InternalCalls.RhpGetClasslibFunctionFromCodeAddress(ip,
                ClassLibFunctionId.AppendExceptionStackFrame);
 
            if (pAppendStackFrame != IntPtr.Zero)
            {
                try
                {
                    ((delegate*<object, IntPtr, int, void>)pAppendStackFrame)(exception, ip, flags);
                }
                catch when (true)
                {
                    // disallow all exceptions leaking out of callbacks
                }
 
                // Clear flags only if we called the function
                isFirstRethrowFrame = false;
                isFirstFrame = false;
            }
#else
#pragma warning disable CS8500
            fixed (EH.ExInfo* pExInfo = &exInfo)
            {
                InternalCalls.RhpAppendExceptionStackFrame(ObjectHandleOnStack.Create(ref exception), ip, sp, flags, pExInfo);
            }
#pragma warning restore CS8500
            // Clear flags only if we called the function
            isFirstRethrowFrame = false;
            isFirstFrame = false;
#endif
        }
 
        // Given an ExceptionID and an address pointing somewhere into a managed module, get
        // an exception object of a type that the module containing the given address will understand.
        // This finds the classlib-defined GetRuntimeException function and asks it for the exception object.
        internal static Exception GetClasslibException(ExceptionIDs id, IntPtr address)
        {
#if NATIVEAOT
            // Find the classlib function that will give us the exception object we want to throw. This
            // is a RuntimeExport function from the classlib module, and is therefore managed-callable.
            IntPtr pGetRuntimeExceptionFunction =
                (IntPtr)InternalCalls.RhpGetClasslibFunctionFromCodeAddress(address, ClassLibFunctionId.GetRuntimeException);
 
            // Return the exception object we get from the classlib.
            Exception? e = null;
            try
            {
                e = ((delegate*<ExceptionIDs, Exception>)pGetRuntimeExceptionFunction)(id);
            }
            catch when (true)
            {
                // disallow all exceptions leaking out of callbacks
            }
#else
            Exception? e = id switch
            {
                ExceptionIDs.AccessViolation => new AccessViolationException(),
                ExceptionIDs.Arithmetic => new ArithmeticException(),
                ExceptionIDs.AmbiguousImplementation => new AmbiguousImplementationException(),
                ExceptionIDs.ArrayTypeMismatch => new ArrayTypeMismatchException(),
                ExceptionIDs.DataMisaligned => new DataMisalignedException(),
                ExceptionIDs.DivideByZero => new DivideByZeroException(),
                ExceptionIDs.EntrypointNotFound => new EntryPointNotFoundException(),
                ExceptionIDs.IndexOutOfRange => new IndexOutOfRangeException(),
                ExceptionIDs.InvalidCast => new InvalidCastException(),
                ExceptionIDs.NullReference => new NullReferenceException(),
                ExceptionIDs.OutOfMemory => new OutOfMemoryException(),
                ExceptionIDs.Overflow => new OverflowException(),
                _ => null
            };
#endif
            // If the helper fails to yield an object, then we fail-fast.
            if (e == null)
            {
                FailFastViaClasslib(RhFailFastReason.InternalError, null, address);
            }
 
            return e;
        }
#if NATIVEAOT
        // Given an ExceptionID and an MethodTable address, get an exception object of a type that the module containing
        // the given address will understand. This finds the classlib-defined GetRuntimeException function and asks
        // it for the exception object.
        internal static Exception GetClasslibExceptionFromEEType(ExceptionIDs id, MethodTable* pEEType)
        {
            // Find the classlib function that will give us the exception object we want to throw. This
            // is a RuntimeExport function from the classlib module, and is therefore managed-callable.
            IntPtr pGetRuntimeExceptionFunction = IntPtr.Zero;
            if (pEEType != null)
            {
                pGetRuntimeExceptionFunction = (IntPtr)InternalCalls.RhpGetClasslibFunctionFromEEType(pEEType, ClassLibFunctionId.GetRuntimeException);
            }
 
            // Return the exception object we get from the classlib.
            Exception? e = null;
            try
            {
                e = ((delegate*<ExceptionIDs, Exception>)pGetRuntimeExceptionFunction)(id);
            }
            catch when (true)
            {
                // disallow all exceptions leaking out of callbacks
            }
 
            // If the helper fails to yield an object, then we fail-fast.
            if (e == null)
            {
                FailFastViaClasslib(RhFailFastReason.InternalError, null, (IntPtr)pEEType);
            }
 
            return e;
        }
 
        // RhExceptionHandling_ functions are used to throw exceptions out of our asm helpers. We tail-call from
        // the asm helpers to these functions, which performs the throw. The tail-call is important: it ensures that
        // the stack is crawlable from within these functions.
        [StackTraceHidden]
        [RuntimeExport("RhExceptionHandling_ThrowClasslibOverflowException")]
        public static void ThrowClasslibOverflowException(IntPtr address)
        {
            // Throw the overflow exception defined by the classlib, using the return address of the asm helper
            // to find the correct classlib.
 
            throw GetClasslibException(ExceptionIDs.Overflow, address);
        }
 
        [StackTraceHidden]
        [RuntimeExport("RhExceptionHandling_ThrowClasslibDivideByZeroException")]
        public static void ThrowClasslibDivideByZeroException(IntPtr address)
        {
            // Throw the divide by zero exception defined by the classlib, using the return address of the asm helper
            // to find the correct classlib.
 
            throw GetClasslibException(ExceptionIDs.DivideByZero, address);
        }
 
        [StackTraceHidden]
        [RuntimeExport("RhExceptionHandling_FailedAllocation")]
        public static void FailedAllocation(MethodTable* pEEType, bool fIsOverflow)
        {
            ExceptionIDs exID = fIsOverflow ? ExceptionIDs.Overflow : ExceptionIDs.OutOfMemory;
 
            // Throw the out of memory exception defined by the classlib, using the input MethodTable*
            // to find the correct classlib.
 
            throw pEEType->GetClasslibException(exID);
        }
 
#if !INPLACE_RUNTIME
        private static readonly OutOfMemoryException s_theOOMException = new OutOfMemoryException();
 
        // MRT exports GetRuntimeException for the few cases where we have a helper that throws an exception
        // and may be called by either MRT or other classlibs and that helper needs to throw an exception.
        // There are only a few cases where this happens now (the fast allocation helpers), so we limit the
        // exception types that MRT will return.
        [RuntimeExport("GetRuntimeException")]
        public static Exception GetRuntimeException(ExceptionIDs id)
        {
            switch (id)
            {
                case ExceptionIDs.OutOfMemory:
                    // Throw a preallocated exception to avoid infinite recursion.
                    return s_theOOMException;
 
                case ExceptionIDs.Overflow:
                    return new OverflowException();
 
                case ExceptionIDs.InvalidCast:
                    return new InvalidCastException();
 
                default:
                    Debug.Assert(false, "unexpected ExceptionID");
                    FallbackFailFast(RhFailFastReason.InternalError, null);
                    return null;
            }
        }
#endif
#endif // NATIVEAOT
 
        private enum HwExceptionCode : uint
        {
            STATUS_REDHAWK_NULL_REFERENCE = 0x00000000u,
            STATUS_REDHAWK_UNMANAGED_HELPER_NULL_REFERENCE = 0x00000042u,
            STATUS_REDHAWK_THREAD_ABORT = 0x00000043u,
 
            STATUS_DATATYPE_MISALIGNMENT = 0x80000002u,
            STATUS_ACCESS_VIOLATION = 0xC0000005u,
            STATUS_INTEGER_DIVIDE_BY_ZERO = 0xC0000094u,
            STATUS_INTEGER_OVERFLOW = 0xC0000095u,
        }
        [StructLayout(LayoutKind.Explicit, Size = AsmOffsets.SIZEOF__PAL_LIMITED_CONTEXT)]
        public struct PAL_LIMITED_CONTEXT
        {
            [FieldOffset(AsmOffsets.OFFSETOF__PAL_LIMITED_CONTEXT__IP)]
            internal IntPtr IP;
#if !NATIVEAOT
            [FieldOffset(AsmOffsets.OFFSETOF__PAL_LIMITED_CONTEXT__FP)]
            internal UIntPtr FP;
#endif
            // the rest of the struct is left unspecified.
        }
 
        // N.B. -- These values are burned into the throw helper assembly code and are also known the the
        //         StackFrameIterator code.
        [Flags]
        internal enum ExKind : byte
        {
            None = 0,
            Throw = 1,
            HardwareFault = 2,
            KindMask = 3,
 
            RethrowFlag = 4,
 
            SupersededFlag = 8,
 
            InstructionFaultFlag = 0x10
        }
 
        [StructLayout(LayoutKind.Explicit)]
        public ref struct ExInfo
        {
            internal void Init(object exceptionObj, bool instructionFault = false)
            {
                // _pPrevExInfo    -- set by asm helper
                // _pExContext     -- set by asm helper
                // _passNumber     -- set by asm helper
                // _kind           -- set by asm helper
                // _idxCurClause   -- set by asm helper
                // _frameIter      -- initialized explicitly during dispatch
 
                _exception = exceptionObj;
                if (instructionFault)
                    _kind |= ExKind.InstructionFaultFlag;
                _notifyDebuggerSP = UIntPtr.Zero;
            }
 
            internal void Init(object exceptionObj, ref ExInfo rethrownExInfo)
            {
                // _pPrevExInfo    -- set by asm helper
                // _pExContext     -- set by asm helper
                // _passNumber     -- set by asm helper
                // _idxCurClause   -- set by asm helper
                // _frameIter      -- initialized explicitly during dispatch
 
                _exception = exceptionObj;
                _kind = rethrownExInfo._kind | ExKind.RethrowFlag;
                _notifyDebuggerSP = UIntPtr.Zero;
            }
 
            internal object ThrownException
            {
                get
                {
                    return _exception;
                }
            }
 
            [FieldOffset(AsmOffsets.OFFSETOF__ExInfo__m_pPrevExInfo)]
            internal void* _pPrevExInfo;
 
            [FieldOffset(AsmOffsets.OFFSETOF__ExInfo__m_pExContext)]
            internal PAL_LIMITED_CONTEXT* _pExContext;
 
            [FieldOffset(AsmOffsets.OFFSETOF__ExInfo__m_exception)]
            private object _exception;  // actual object reference, specially reported by GcScanRootsWorker
 
            [FieldOffset(AsmOffsets.OFFSETOF__ExInfo__m_kind)]
            internal ExKind _kind;
 
            [FieldOffset(AsmOffsets.OFFSETOF__ExInfo__m_passNumber)]
            internal byte _passNumber;
 
            // BEWARE: This field is used by the stackwalker to know if the dispatch code has reached the
            //         point at which a handler is called.  In other words, it serves as an "is a handler
            //         active" state where '_idxCurClause == MaxTryRegionIdx' means 'no'.
            [FieldOffset(AsmOffsets.OFFSETOF__ExInfo__m_idxCurClause)]
            internal uint _idxCurClause;
 
            [FieldOffset(AsmOffsets.OFFSETOF__ExInfo__m_frameIter)]
            internal StackFrameIterator _frameIter;
 
            [FieldOffset(AsmOffsets.OFFSETOF__ExInfo__m_notifyDebuggerSP)]
            internal volatile UIntPtr _notifyDebuggerSP;
        }
 
        //
        // Called by RhpThrowHwEx
        //
#if NATIVEAOT
        [RuntimeExport("RhThrowHwEx")]
#endif
        public static void RhThrowHwEx(uint exceptionCode, ref ExInfo exInfo)
        {
#if NATIVEAOT
            // trigger a GC (only if gcstress) to ensure we can stackwalk at this point
            GCStress.TriggerGC();
 
            InternalCalls.RhpValidateExInfoStack();
#endif
            IntPtr faultingCodeAddress = exInfo._pExContext->IP;
            bool instructionFault = true;
            ExceptionIDs exceptionId = default(ExceptionIDs);
            Exception? exceptionToThrow = null;
 
            switch (exceptionCode)
            {
                case (uint)HwExceptionCode.STATUS_REDHAWK_NULL_REFERENCE:
                    exceptionId = ExceptionIDs.NullReference;
                    break;
 
                case (uint)HwExceptionCode.STATUS_REDHAWK_UNMANAGED_HELPER_NULL_REFERENCE:
                    // The write barrier where the actual fault happened has been unwound already.
                    // The IP of this fault needs to be treated as return address, not as IP of
                    // faulting instruction.
                    instructionFault = false;
                    exceptionId = ExceptionIDs.NullReference;
                    break;
 
#if NATIVEAOT
                case (uint)HwExceptionCode.STATUS_REDHAWK_THREAD_ABORT:
                    exceptionToThrow = InternalCalls.RhpGetThreadAbortException();
                    break;
#endif
 
                case (uint)HwExceptionCode.STATUS_DATATYPE_MISALIGNMENT:
                    exceptionId = ExceptionIDs.DataMisaligned;
                    break;
 
                // N.B. -- AVs that have a read/write address lower than 64k are already transformed to
                //         HwExceptionCode.REDHAWK_NULL_REFERENCE prior to calling this routine.
                case (uint)HwExceptionCode.STATUS_ACCESS_VIOLATION:
                    exceptionId = ExceptionIDs.AccessViolation;
                    break;
 
                case (uint)HwExceptionCode.STATUS_INTEGER_DIVIDE_BY_ZERO:
                    exceptionId = ExceptionIDs.DivideByZero;
                    break;
 
                case (uint)HwExceptionCode.STATUS_INTEGER_OVERFLOW:
                    exceptionId = ExceptionIDs.Overflow;
                    break;
 
                default:
                    // We don't wrap SEH exceptions from foreign code like CLR does, so we believe that we
                    // know the complete set of HW faults generated by managed code and do not need to handle
                    // this case.
                    FailFastViaClasslib(RhFailFastReason.InternalError, null, faultingCodeAddress);
                    break;
            }
 
            if (exceptionId != default(ExceptionIDs))
            {
                exceptionToThrow = GetClasslibException(exceptionId, faultingCodeAddress);
            }
 
            exInfo.Init(exceptionToThrow!, instructionFault);
            DispatchEx(ref exInfo._frameIter, ref exInfo);
            FallbackFailFast(RhFailFastReason.InternalError, null);
        }
 
        private const uint MaxTryRegionIdx = 0xFFFFFFFFu;
 
#if NATIVEAOT
        [RuntimeExport("RhThrowEx")]
#endif
        public static void RhThrowEx(object exceptionObj, ref ExInfo exInfo)
        {
#if NATIVEAOT
            // trigger a GC (only if gcstress) to ensure we can stackwalk at this point
            GCStress.TriggerGC();
 
            InternalCalls.RhpValidateExInfoStack();
#endif
            // Transform attempted throws of null to a throw of NullReferenceException.
            if (exceptionObj == null)
            {
                IntPtr faultingCodeAddress = exInfo._pExContext->IP;
                exceptionObj = GetClasslibException(ExceptionIDs.NullReference, faultingCodeAddress);
            }
 
            exInfo.Init(exceptionObj);
            DispatchEx(ref exInfo._frameIter, ref exInfo);
            FallbackFailFast(RhFailFastReason.InternalError, null);
        }
#if !NATIVEAOT
        public static void RhUnwindAndIntercept(ref ExInfo exInfo, UIntPtr interceptStackFrameSP)
        {
            exInfo._passNumber = 2;
            exInfo._idxCurClause = MaxTryRegionIdx;
            uint startIdx = MaxTryRegionIdx;
            bool unwoundReversePInvoke = false;
            bool isExceptionIntercepted = false;
            bool isValid = exInfo._frameIter.Init(exInfo._pExContext, (exInfo._kind & ExKind.InstructionFaultFlag) != 0, &isExceptionIntercepted);
            for (; isValid && !isExceptionIntercepted && ((byte*)exInfo._frameIter.SP <= (byte*)interceptStackFrameSP); isValid = exInfo._frameIter.Next(&startIdx, &unwoundReversePInvoke, &isExceptionIntercepted))
            {
                Debug.Assert(isValid, "Unwind and intercept failed unexpectedly");
                DebugScanCallFrame(exInfo._passNumber, exInfo._frameIter.ControlPC, exInfo._frameIter.SP);
 
                if (unwoundReversePInvoke)
                {
                    // Found the native frame that called the reverse P/invoke.
                    // It is not possible to run managed second pass handlers on a native frame.
                    break;
                }
 
                if (exInfo._frameIter.SP == interceptStackFrameSP)
                {
                    break;
                }
 
                InvokeSecondPass(ref exInfo, startIdx);
                if (isExceptionIntercepted)
                {
                    Debug.Assert(false);
                    break;
                }
            }
 
            // ------------------------------------------------
            //
            // Call the interception code
            //
            // ------------------------------------------------
            if (unwoundReversePInvoke)
            {
                object exceptionObj = exInfo.ThrownException;
#pragma warning disable CS8500
                fixed (EH.ExInfo* pExInfo = &exInfo)
                {
                    InternalCalls.RhpCallCatchFunclet(
                        ObjectHandleOnStack.Create(ref exceptionObj), null, exInfo._frameIter.RegisterSet, pExInfo);
                }
#pragma warning restore CS8500
            }
            else
            {
                InternalCalls.ResumeAtInterceptionLocation(exInfo._frameIter.RegisterSet);
            }
 
            Debug.Assert(false, "unreachable");
            FallbackFailFast(RhFailFastReason.InternalError, null);
        }
#endif // !NATIVEAOT
 
 
#if NATIVEAOT
        [RuntimeExport("RhRethrow")]
#endif
        public static void RhRethrow(ref ExInfo activeExInfo, ref ExInfo exInfo)
        {
#if NATIVEAOT
            // trigger a GC (only if gcstress) to ensure we can stackwalk at this point
            GCStress.TriggerGC();
 
            InternalCalls.RhpValidateExInfoStack();
#endif
            // We need to copy the exception object to this stack location because collided unwinds
            // will cause the original stack location to go dead.
            object rethrownException = activeExInfo.ThrownException;
 
            exInfo.Init(rethrownException, ref activeExInfo);
            DispatchEx(ref exInfo._frameIter, ref exInfo);
            FallbackFailFast(RhFailFastReason.InternalError, null);
        }
 
        private static void DispatchEx(scoped ref StackFrameIterator frameIter, ref ExInfo exInfo)
        {
            Debug.Assert(exInfo._passNumber == 1, "expected asm throw routine to set the pass");
            object exceptionObj = exInfo.ThrownException;
 
            // ------------------------------------------------
            //
            // First pass
            //
            // ------------------------------------------------
            UIntPtr handlingFrameSP = MaxSP;
            byte* pCatchHandler = null;
            uint catchingTryRegionIdx = MaxTryRegionIdx;
 
            bool isFirstRethrowFrame = (exInfo._kind & ExKind.RethrowFlag) != 0;
            bool isFirstFrame = true;
            bool isExceptionIntercepted = false;
 
            byte* prevControlPC = null;
            byte* prevOriginalPC = null;
            UIntPtr prevFramePtr = UIntPtr.Zero;
            bool unwoundReversePInvoke = false;
            IntPtr pReversePInvokePropagationCallback = IntPtr.Zero;
            IntPtr pReversePInvokePropagationContext = IntPtr.Zero;
 
            bool isValid = frameIter.Init(exInfo._pExContext, (exInfo._kind & ExKind.InstructionFaultFlag) != 0, &isExceptionIntercepted);
            Debug.Assert(isValid, "RhThrowEx called with an unexpected context");
 
#if NATIVEAOT
            OnFirstChanceExceptionViaClassLib(exceptionObj);
#endif
            uint startIdx = MaxTryRegionIdx;
            for (; isValid; isValid = frameIter.Next(&startIdx, &unwoundReversePInvoke, &isExceptionIntercepted))
            {
                // For GC stackwalking, we'll happily walk across native code blocks, but for EH dispatch, we
                // disallow dispatching exceptions across native code.
                if (unwoundReversePInvoke)
                    break;
 
                if (isExceptionIntercepted)
                {
                    break;
                }
 
                prevControlPC = frameIter.ControlPC;
                prevOriginalPC = frameIter.OriginalControlPC;
 
                DebugScanCallFrame(exInfo._passNumber, frameIter.ControlPC, frameIter.SP);
 
#if !NATIVEAOT
                // Don't add frames at collided unwind
                if (startIdx == MaxTryRegionIdx)
#endif
                {
                    UpdateStackTrace(exceptionObj, exInfo._frameIter.FramePointer, (IntPtr)frameIter.OriginalControlPC, frameIter.SP, ref isFirstRethrowFrame, ref prevFramePtr, ref isFirstFrame, ref exInfo);
                }
 
                byte* pHandler;
                if (FindFirstPassHandler(exceptionObj, startIdx, ref frameIter,
                                         out catchingTryRegionIdx, out pHandler))
                {
                    handlingFrameSP = frameIter.SP;
                    pCatchHandler = pHandler;
 
                    DebugVerifyHandlingFrame(handlingFrameSP);
                    break;
                }
            }
 
            if (unwoundReversePInvoke)
            {
#if NATIVEAOT
#if FEATURE_OBJCMARSHAL
                // We did not find any managed handlers before hitting a reverse P/Invoke boundary.
                // See if the classlib has a handler to propagate the exception to native code.
                IntPtr pGetHandlerClasslibFunction = (IntPtr)InternalCalls.RhpGetClasslibFunctionFromCodeAddress((IntPtr)prevControlPC,
                    ClassLibFunctionId.ObjectiveCMarshalGetUnhandledExceptionPropagationHandler);
                if (pGetHandlerClasslibFunction != IntPtr.Zero)
                {
                    var pGetHandler = (delegate*<object, IntPtr, out IntPtr, IntPtr>)pGetHandlerClasslibFunction;
                    pReversePInvokePropagationCallback = pGetHandler(
                        exceptionObj, (IntPtr)prevControlPC, out pReversePInvokePropagationContext);
                    if (pReversePInvokePropagationCallback != IntPtr.Zero)
                    {
                        // Tell the second pass to unwind to this frame.
                        handlingFrameSP = frameIter.SP;
                        catchingTryRegionIdx = MaxTryRegionIdx;
                    }
                }
#endif // FEATURE_OBJCMARSHAL
#else // !NATIVEAOT
                handlingFrameSP = frameIter.SP;
                catchingTryRegionIdx = MaxTryRegionIdx;
#endif // !NATIVEAOT
            }
 
            if (pCatchHandler == null && pReversePInvokePropagationCallback == IntPtr.Zero && !isExceptionIntercepted
#if !NATIVEAOT
                && !unwoundReversePInvoke
#endif
            )
            {
                OnUnhandledExceptionViaClassLib(exceptionObj);
 
                UnhandledExceptionFailFastViaClasslib(
                    RhFailFastReason.UnhandledException,
                    exceptionObj,
                    (IntPtr)prevOriginalPC, // IP of the last frame that did not handle the exception
                    ref exInfo);
            }
 
            // We FailFast above if the exception goes unhandled.  Therefore, we cannot run the second pass
            // without a catch handler or propagation callback.
            Debug.Assert(pCatchHandler != null || pReversePInvokePropagationCallback != IntPtr.Zero || unwoundReversePInvoke || isExceptionIntercepted, "We should have a handler if we're starting the second pass");
            Debug.Assert(!isExceptionIntercepted || (pCatchHandler == null), "No catch handler should be returned for intercepted exceptions in the first pass");
 
            // ------------------------------------------------
            //
            // Second pass
            //
            // ------------------------------------------------
 
            // Due to the stackwalker logic, we cannot tolerate triggering a GC from the dispatch code once we
            // are in the 2nd pass.  This is because the stackwalker applies a particular unwind semantic to
            // 'collapse' funclets which gets confused when we walk out of the dispatch code and encounter the
            // 'main body' without first encountering the funclet.  The thunks used to invoke 2nd-pass
            // funclets will always toggle this mode off before invoking them.
#if NATIVEAOT
            InternalCalls.RhpSetThreadDoNotTriggerGC();
#endif
            exInfo._passNumber = 2;
            exInfo._idxCurClause = catchingTryRegionIdx;
            startIdx = MaxTryRegionIdx;
            unwoundReversePInvoke = false;
            isExceptionIntercepted = false;
            isValid = frameIter.Init(exInfo._pExContext, (exInfo._kind & ExKind.InstructionFaultFlag) != 0, &isExceptionIntercepted);
            for (; isValid && ((byte*)frameIter.SP <= (byte*)handlingFrameSP); isValid = frameIter.Next(&startIdx, &unwoundReversePInvoke, &isExceptionIntercepted))
            {
                Debug.Assert(isValid, "second-pass EH unwind failed unexpectedly");
                DebugScanCallFrame(exInfo._passNumber, frameIter.ControlPC, frameIter.SP);
 
                if (isExceptionIntercepted)
                {
                    pCatchHandler = null;
                    break;
                }
 
                if (unwoundReversePInvoke)
                {
#if NATIVEAOT
                    Debug.Assert(pReversePInvokePropagationCallback != IntPtr.Zero, "Unwound to a reverse P/Invoke in the second pass. We should have a propagation handler.");
                    Debug.Assert(frameIter.PreviousTransitionFrame != IntPtr.Zero, "Should have a transition frame for reverse P/Invoke.");
#endif
                    Debug.Assert(frameIter.SP == handlingFrameSP, "Encountered a different reverse P/Invoke frame in the second pass.");
                    // Found the native frame that called the reverse P/invoke.
                    // It is not possible to run managed second pass handlers on a native frame.
                    break;
                }
 
                if ((frameIter.SP == handlingFrameSP)
#if TARGET_ARM64
                    && (frameIter.ControlPC == prevControlPC)
#endif
                    )
                {
                    // invoke only a partial second-pass here...
                    InvokeSecondPass(ref exInfo, startIdx, catchingTryRegionIdx);
                    break;
                }
 
                InvokeSecondPass(ref exInfo, startIdx);
            }
 
#if FEATURE_OBJCMARSHAL
            if (pReversePInvokePropagationCallback != IntPtr.Zero)
            {
#if NATIVEAOT
                InternalCalls.RhpCallPropagateExceptionCallback(
                    pReversePInvokePropagationContext, pReversePInvokePropagationCallback, frameIter.RegisterSet, ref exInfo, frameIter.PreviousTransitionFrame);
                // the helper should jump to propagation handler and not return
#endif
                Debug.Assert(false, "unreachable");
                FallbackFailFast(RhFailFastReason.InternalError, null);
            }
#endif // FEATURE_OBJCMARSHAL
 
 
            // ------------------------------------------------
            //
            // Call the handler and resume execution
            //
            // ------------------------------------------------
            exInfo._idxCurClause = catchingTryRegionIdx;
#if NATIVEAOT
            InternalCalls.RhpCallCatchFunclet(
                exceptionObj, pCatchHandler, frameIter.RegisterSet, ref exInfo);
#else // NATIVEAOT
#pragma warning disable CS8500
            fixed (EH.ExInfo* pExInfo = &exInfo)
            {
                InternalCalls.RhpCallCatchFunclet(
                    ObjectHandleOnStack.Create(ref exceptionObj), pCatchHandler, frameIter.RegisterSet, pExInfo);
            }
#pragma warning restore CS8500
#endif // NATIVEAOT
            // currently, RhpCallCatchFunclet will resume after the catch
            Debug.Assert(false, "unreachable");
            FallbackFailFast(RhFailFastReason.InternalError, null);
        }
 
        [System.Diagnostics.Conditional("DEBUG")]
        private static void DebugScanCallFrame(int passNumber, byte* ip, UIntPtr sp)
        {
            Debug.Assert(ip != null, "IP address must not be null");
        }
 
        [System.Diagnostics.Conditional("DEBUG")]
        private static void DebugVerifyHandlingFrame(UIntPtr handlingFrameSP)
        {
            Debug.Assert(handlingFrameSP != MaxSP, "Handling frame must have an SP value");
            Debug.Assert(((UIntPtr*)handlingFrameSP) > &handlingFrameSP,
                "Handling frame must have a valid stack frame pointer");
        }
 
        // Caclulate the code offset from the start of the method as if the hot and cold regions were
        // stored sequentially in memory.
        private static uint CalculateCodeOffset(byte* pbControlPC, in MethodRegionInfo methodRegionInfo)
        {
            uint codeOffset = (uint)(pbControlPC - methodRegionInfo._hotStartAddress);
            // If the PC is in the cold region, adjust the offset to be relative to the start of the method.
            if ((methodRegionInfo._coldSize != 0) && (codeOffset >= methodRegionInfo._hotSize))
            {
                codeOffset = (uint)(methodRegionInfo._hotSize + (nuint)(pbControlPC - methodRegionInfo._coldStartAddress));
            }
 
            return codeOffset;
        }
 
        private static void UpdateStackTrace(object exceptionObj, UIntPtr curFramePtr, IntPtr ip, UIntPtr sp,
            ref bool isFirstRethrowFrame, ref UIntPtr prevFramePtr, ref bool isFirstFrame, ref ExInfo exInfo)
        {
            // We use the fact that all funclet stack frames belonging to the same logical method activation
            // will have the same FramePointer value.  Additionally, the stackwalker will return a sequence of
            // callbacks for all the funclet stack frames, one right after the other.  The classlib doesn't
            // want to know about funclets, so we strip them out by only reporting the first frame of a
            // sequence of funclets.  This is correct because the leafmost funclet is first in the sequence
            // and corresponds to the current 'IP state' of the method.
#if NATIVEAOT
            if ((prevFramePtr == UIntPtr.Zero) || (curFramePtr != prevFramePtr))
#endif
            {
                AppendExceptionStackFrameViaClasslib(exceptionObj, ip, sp, ref exInfo,
                    ref isFirstRethrowFrame, ref isFirstFrame);
            }
            prevFramePtr = curFramePtr;
        }
 
        private static bool FindFirstPassHandler(object exception, uint idxStart,
            ref StackFrameIterator frameIter, out uint tryRegionIdx, out byte* pHandler)
        {
            pHandler = null;
            tryRegionIdx = MaxTryRegionIdx;
 
            EHEnum ehEnum;
            MethodRegionInfo methodRegionInfo;
            if (!InternalCalls.RhpEHEnumInitFromStackFrameIterator(ref frameIter, out methodRegionInfo, &ehEnum))
                return false;
 
            byte* pbControlPC = frameIter.ControlPC;
 
            uint codeOffset = CalculateCodeOffset(pbControlPC, in methodRegionInfo);
 
            uint lastTryStart = 0, lastTryEnd = 0;
 
            // Search the clauses for one that contains the current offset.
            RhEHClause ehClause;
            for (uint curIdx = 0; InternalCalls.RhpEHEnumNext(&ehEnum, &ehClause); curIdx++)
            {
                //
                // Skip to the starting try region.  This is used by collided unwinds and rethrows to pickup where
                // the previous dispatch left off.
                //
                if (idxStart != MaxTryRegionIdx)
                {
                    if (curIdx <= idxStart)
                    {
                        lastTryStart = ehClause._tryStartOffset; lastTryEnd = ehClause._tryEndOffset;
                        continue;
                    }
 
                    // Now, we continue skipping while the try region is identical to the one that invoked the
                    // previous dispatch.
                    if ((ehClause._tryStartOffset == lastTryStart) && (ehClause._tryEndOffset == lastTryEnd)
#if !NATIVEAOT
                        && (ehClause._isSameTry)
#endif
                    )
                        continue;
 
                    // We are done skipping. This is required to handle empty finally block markers that are used
                    // to separate runs of different try blocks with same native code offsets.
                    idxStart = MaxTryRegionIdx;
                }
 
                RhEHClauseKind clauseKind = ehClause._clauseKind;
 
                if (((clauseKind != RhEHClauseKind.RH_EH_CLAUSE_TYPED) &&
                     (clauseKind != RhEHClauseKind.RH_EH_CLAUSE_FILTER))
                    || !ehClause.ContainsCodeOffset(codeOffset))
                {
                    continue;
                }
 
                // Found a containing clause. Because of the order of the clauses, we know this is the
                // most containing.
                if (clauseKind == RhEHClauseKind.RH_EH_CLAUSE_TYPED)
                {
                    if (ShouldTypedClauseCatchThisException(exception, (MethodTable*)ehClause._pTargetType, !frameIter.IsRuntimeWrappedExceptions))
                    {
                        pHandler = ehClause._handlerAddress;
                        tryRegionIdx = curIdx;
                        return true;
                    }
                }
                else
                {
                    byte* pFilterFunclet = ehClause._filterAddress;
 
                    bool shouldInvokeHandler = false;
#if NATIVEAOT
                    try
                    {
                        shouldInvokeHandler =
                            InternalCalls.RhpCallFilterFunclet(exception, pFilterFunclet, frameIter.RegisterSet);
                    }
                    catch when (true)
                    {
                        // Prevent leaking any exception from the filter funclet
                    }
#else // NATIVEAOT
                    shouldInvokeHandler =
                        InternalCalls.RhpCallFilterFunclet(ObjectHandleOnStack.Create(ref exception), pFilterFunclet, frameIter.RegisterSet);
#endif // NATIVEAOT
 
                    if (shouldInvokeHandler)
                    {
                        pHandler = ehClause._handlerAddress;
                        tryRegionIdx = curIdx;
                        return true;
                    }
                }
            }
 
            return false;
        }
 
#if DEBUG && !INPLACE_RUNTIME && NATIVEAOT
        private static MethodTable* s_pLowLevelObjectType;
        private static void AssertNotRuntimeObject(MethodTable* pClauseType)
        {
            //
            // The C# try { } catch { } clause expands into a typed catch of System.Object.
            // Since runtime has its own definition of System.Object, try { } catch { } might not do what
            // was intended (catch all exceptions).
            //
            // This assertion is making sure we don't use try { } catch { } within the runtime.
            // The runtime codebase should either use try { } catch (Exception) { } for exception types
            // from the runtime or a try { } catch when (true) { } to catch all exceptions.
            //
 
            if (s_pLowLevelObjectType == null)
            {
                // Allocating might fail, but since this is just a debug assert, it's probably fine.
                s_pLowLevelObjectType = new System.Object().MethodTable;
            }
 
            Debug.Assert(!pClauseType->IsEquivalentTo(s_pLowLevelObjectType));
        }
#endif // DEBUG && !INPLACE_RUNTIME && NATIVEAOT
 
 
        private static bool ShouldTypedClauseCatchThisException(object exception, MethodTable* pClauseType, bool tryUnwrapException)
        {
#if NATIVEAOT
#if DEBUG && !INPLACE_RUNTIME
            AssertNotRuntimeObject(pClauseType);
#endif

            return TypeCast.IsInstanceOfException(pClauseType, exception);
#else
            if (tryUnwrapException && exception is RuntimeWrappedException ex)
            {
                exception = ex.WrappedException;
            }
 
            MethodTable* mt = RuntimeHelpers.GetMethodTable(exception);
            while (mt != null)
            {
                if (pClauseType == mt)
                {
                    return true;
                }
 
                mt = mt->ParentMethodTable;
            }
 
            return false;
#endif
        }
 
        private static void InvokeSecondPass(ref ExInfo exInfo, uint idxStart)
        {
            InvokeSecondPass(ref exInfo, idxStart, MaxTryRegionIdx);
        }
        private static void InvokeSecondPass(ref ExInfo exInfo, uint idxStart, uint idxLimit)
        {
            EHEnum ehEnum;
            MethodRegionInfo methodRegionInfo;
 
            if (!InternalCalls.RhpEHEnumInitFromStackFrameIterator(ref exInfo._frameIter, out methodRegionInfo, &ehEnum))
                return;
 
            byte* pbControlPC = exInfo._frameIter.ControlPC;
 
            uint codeOffset = CalculateCodeOffset(pbControlPC, in methodRegionInfo);
 
            uint lastTryStart = 0, lastTryEnd = 0;
 
            // Search the clauses for one that contains the current offset.
            RhEHClause ehClause;
            for (uint curIdx = 0; InternalCalls.RhpEHEnumNext(&ehEnum, &ehClause) && curIdx < idxLimit; curIdx++)
            {
                //
                // Skip to the starting try region.  This is used by collided unwinds and rethrows to pickup where
                // the previous dispatch left off.
                //
                if (idxStart != MaxTryRegionIdx)
                {
                    if (curIdx <= idxStart)
                    {
                        lastTryStart = ehClause._tryStartOffset; lastTryEnd = ehClause._tryEndOffset;
                        continue;
                    }
 
                    // Now, we continue skipping while the try region is identical to the one that invoked the
                    // previous dispatch.
                    if ((ehClause._tryStartOffset == lastTryStart) && (ehClause._tryEndOffset == lastTryEnd)
#if !NATIVEAOT
                        && (ehClause._isSameTry)
#endif
                    )
                        continue;
 
                    // We are done skipping. This is required to handle empty finally block markers that are used
                    // to separate runs of different try blocks with same native code offsets.
                    idxStart = MaxTryRegionIdx;
                }
 
                RhEHClauseKind clauseKind = ehClause._clauseKind;
 
                if ((clauseKind != RhEHClauseKind.RH_EH_CLAUSE_FAULT)
                    || !ehClause.ContainsCodeOffset(codeOffset))
                {
                    continue;
                }
 
                // Found a containing clause. Because of the order of the clauses, we know this is the
                // most containing.
 
                // N.B. -- We need to suppress GC "in-between" calls to finallys in this loop because we do
                // not have the correct next-execution point live on the stack and, therefore, may cause a GC
                // hole if we allow a GC between invocation of finally funclets (i.e. after one has returned
                // here to the dispatcher, but before the next one is invoked).  Once they are running, it's
                // fine for them to trigger a GC, obviously.
                //
                // As a result, RhpCallFinallyFunclet will set this state in the runtime upon return from the
                // funclet, and we need to reset it if/when we fall out of the loop and we know that the
                // method will no longer get any more GC callbacks.
 
                byte* pFinallyHandler = ehClause._handlerAddress;
                exInfo._idxCurClause = curIdx;
#if NATIVEAOT
                InternalCalls.RhpCallFinallyFunclet(pFinallyHandler, exInfo._frameIter.RegisterSet);
#else // NATIVEAOT
#pragma warning disable CS8500
                fixed (EH.ExInfo* pExInfo = &exInfo)
                {
                    InternalCalls.RhpCallFinallyFunclet(pFinallyHandler, exInfo._frameIter.RegisterSet, pExInfo);
                }
#pragma warning restore CS8500
#endif // NATIVEAOT
                exInfo._idxCurClause = MaxTryRegionIdx;
            }
        }
 
#if NATIVEAOT
#pragma warning disable IDE0060
        [UnmanagedCallersOnly(EntryPoint = "RhpFailFastForPInvokeExceptionPreemp")]
        public static void RhpFailFastForPInvokeExceptionPreemp(IntPtr PInvokeCallsiteReturnAddr, void* pExceptionRecord, void* pContextRecord)
        {
            FailFastViaClasslib(RhFailFastReason.UnhandledExceptionFromPInvoke, null, PInvokeCallsiteReturnAddr);
        }
        [RuntimeExport("RhpFailFastForPInvokeExceptionCoop")]
        public static void RhpFailFastForPInvokeExceptionCoop(IntPtr classlibBreadcrumb, void* pExceptionRecord, void* pContextRecord)
        {
            FailFastViaClasslib(RhFailFastReason.UnhandledExceptionFromPInvoke, null, classlibBreadcrumb);
        }
#pragma warning restore IDE0060
#endif
    } // static class EH
}