Welcome to mirror list, hosted at ThFree Co, Russian Federation.

ExceptionHandling.cs « Runtime « System « src « Runtime.Base « src - github.com/mono/corert.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 4d3cdf674fb7a65b94c7172c7fa7bb40565e3361 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Diagnostics;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

using Internal.Runtime;

// Disable: Filter expression is a constant. We know. We just can't do an unfiltered catch.
#pragma warning disable 7095

namespace System.Runtime
{
    public enum RhFailFastReason
    {
        Unknown = 0,
        InternalError = 1,                                   // "Runtime internal error"
        UnhandledException_ExceptionDispatchNotAllowed = 2,  // "Unhandled exception: no handler found before escaping a finally clause or other fail-fast scope."
        UnhandledException_CallerDidNotHandle = 3,           // "Unhandled exception: no handler found in calling method."
        ClassLibDidNotTranslateExceptionID = 4,              // "Unable to translate failure into a classlib-specific exception object."
        IllegalNativeCallableEntry = 5,                      // "Invalid Program: attempted to call a NativeCallable method from runtime-typesafe code."

        PN_UnhandledException = 6,                           // ProjectN: "unhandled exception"
        PN_UnhandledExceptionFromPInvoke = 7,                // ProjectN: "Unhandled exception: an unmanaged exception was thrown out of a managed-to-native transition."
        Max
    }

    // Keep this synchronized with the duplicate definition in DebugEventSource.cpp
    [Flags]
    internal enum ExceptionEventKind
    {
        Thrown = 1,
        CatchHandlerFound = 2,
        Unhandled = 4,
        FirstPassFrameEntered = 8
    }

    internal static unsafe class DebuggerNotify
    {
        // We cache the events a debugger is interested on the C# side to avoid p/invokes when the
        // debugger isn't attached.
        //
        // Ideally we would like the managed debugger to start toggling this directly so that
        // it stays perfectly up-to-date. However as a reasonable approximation we fetch
        // the value from native code at the beginning of each exception dispatch. If the debugger
        // attempts to enroll in more events mid-exception handling we aren't going to see it.
        private static ExceptionEventKind s_cachedEventMask;

        internal static void BeginFirstPass(object exceptionObj, byte* faultingIP, UIntPtr faultingFrameSP)
        {
            s_cachedEventMask = InternalCalls.RhpGetRequestedExceptionEvents();

            if ((s_cachedEventMask & ExceptionEventKind.Thrown) == 0)
                return;

            InternalCalls.RhpSendExceptionEventToDebugger(ExceptionEventKind.Thrown, faultingIP, faultingFrameSP);
        }

        internal static void FirstPassFrameEntered(object exceptionObj, byte* enteredFrameIP, UIntPtr enteredFrameSP)
        {
            s_cachedEventMask = InternalCalls.RhpGetRequestedExceptionEvents();

            if ((s_cachedEventMask & ExceptionEventKind.FirstPassFrameEntered) == 0)
                return;

            InternalCalls.RhpSendExceptionEventToDebugger(ExceptionEventKind.FirstPassFrameEntered, enteredFrameIP, enteredFrameSP);
        }

        internal static void EndFirstPass(object exceptionObj, byte* handlerIP, UIntPtr handlingFrameSP)
        {
            if (handlerIP == null)
            {
                if ((s_cachedEventMask & ExceptionEventKind.Unhandled) == 0)
                    return;
                InternalCalls.RhpSendExceptionEventToDebugger(ExceptionEventKind.Unhandled, null, UIntPtr.Zero);
            }
            else
            {
                if ((s_cachedEventMask & ExceptionEventKind.CatchHandlerFound) == 0)
                    return;
                InternalCalls.RhpSendExceptionEventToDebugger(ExceptionEventKind.CatchHandlerFound, handlerIP, handlingFrameSP);
            }
        }

        internal static void BeginSecondPass()
        {
            //desktop debugging has an unwind begin event, however it appears that is unneeded for now, and possibly
            // will never be needed?
        }
    }

    internal static unsafe 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;

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

        // 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.
        internal static void FallbackFailFast(RhFailFastReason reason, object unhandledException)
        {
            InternalCalls.RhpFallbackFailFast();
        }

        // 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.
        internal static void FailFastViaClasslib(RhFailFastReason reason, object unhandledException,
            IntPtr classlibAddress)
        {
            // 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.
                CalliIntrinsics.CallVoid(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...
            FallbackFailFast(reason, unhandledException);
        }

#if AMD64
        [StructLayout(LayoutKind.Explicit, Size = 0x4d0)]
#elif ARM
        [StructLayout(LayoutKind.Explicit, Size = 0x1a0)]
#elif X86
        [StructLayout(LayoutKind.Explicit, Size = 0x2cc)]
#elif 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 BIT64
            return (void*)((((long)ptr) + alignMask) & ~alignMask);
#else
            return (void*)((((int)ptr) + alignMask) & ~alignMask);
#endif
        }

        private static void OnFirstChanceExceptionViaClassLib(object exception)
        {
            IntPtr pOnFirstChanceFunction =
                (IntPtr)InternalCalls.RhpGetClasslibFunctionFromEEType((IntPtr)exception.m_pEEType, ClassLibFunctionId.OnFirstChance);

            if (pOnFirstChanceFunction == IntPtr.Zero)
            {
                return;
            }

            try
            {
                CalliIntrinsics.CallVoid(pOnFirstChanceFunction, exception);
            }
            catch when (true)
            {
                // disallow all exceptions leaking out of callbacks
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        internal static unsafe void UnhandledExceptionFailFastViaClasslib(
            RhFailFastReason reason, object unhandledException, IntPtr classlibAddress, ref ExInfo exInfo)
        {
            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
            {
                CalliIntrinsics.CallVoid(pFailFastFunction, reason, unhandledException, exInfo._pExContext->IP, (IntPtr)pContext);
            }
            catch when (true)
            {
                // disallow all exceptions leaking out of callbacks
            }

            // The classlib's funciton should never return and should not throw. If it does, then we fail our way...
            FallbackFailFast(reason, unhandledException);
        }

        private enum RhEHFrameType
        {
            RH_EH_FIRST_FRAME = 1,
            RH_EH_FIRST_RETHROW_FRAME = 2,
        }

        private static void AppendExceptionStackFrameViaClasslib(object exception, IntPtr IP,
            ref bool isFirstRethrowFrame, ref bool isFirstFrame)
        {
            IntPtr pAppendStackFrame = (IntPtr)InternalCalls.RhpGetClasslibFunctionFromCodeAddress(IP,
                ClassLibFunctionId.AppendExceptionStackFrame);

            if (pAppendStackFrame != IntPtr.Zero)
            {
                int flags = (isFirstFrame ? (int)RhEHFrameType.RH_EH_FIRST_FRAME : 0) |
                            (isFirstRethrowFrame ? (int)RhEHFrameType.RH_EH_FIRST_RETHROW_FRAME : 0);

                try
                {
                    CalliIntrinsics.CallVoid(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;
            }
        }

        // 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)
        {
            // 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 = CalliIntrinsics.Call<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.ClassLibDidNotTranslateExceptionID,
                    null,
                    address);
            }

            return e;
        }

        // Given an ExceptionID and an EEType 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, IntPtr 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 != IntPtr.Zero)
            {
                pGetRuntimeExceptionFunction = (IntPtr)InternalCalls.RhpGetClasslibFunctionFromEEType(pEEType, ClassLibFunctionId.GetRuntimeException);
            }

            // Return the exception object we get from the classlib.
            Exception e = null;
            try
            {
                e = CalliIntrinsics.Call<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.ClassLibDidNotTranslateExceptionID,
                    null,
                    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.
        [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);
        }

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

        [RuntimeExport("RhExceptionHandling_FailedAllocation")]
        public static void FailedAllocation(EETypePtr pEEType, bool fIsOverflow)
        {
            ExceptionIDs exID = fIsOverflow ? ExceptionIDs.Overflow : ExceptionIDs.OutOfMemory;

            // Throw the out of memory exception defined by the classlib, using the input EEType* 
            // to find the correct classlib.

            throw pEEType.ToPointer()->GetClasslibException(exID);
        }

#if !INPLACE_RUNTIME
        private static 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

        private enum HwExceptionCode : uint
        {
            STATUS_REDHAWK_NULL_REFERENCE = 0x00000000u,
            STATUS_REDHAWK_WRITE_BARRIER_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;
            // 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)]
            volatile internal UIntPtr _notifyDebuggerSP;
        }

        //
        // Called by RhpThrowHwEx
        //
        [RuntimeExport("RhThrowHwEx")]
        public static void RhThrowHwEx(uint exceptionCode, ref ExInfo exInfo)
        {
            // trigger a GC (only if gcstress) to ensure we can stackwalk at this point
            GCStress.TriggerGC();

            InternalCalls.RhpValidateExInfoStack();

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

                case (uint)HwExceptionCode.STATUS_REDHAWK_THREAD_ABORT:
                    exceptionToThrow = InternalCalls.RhpGetThreadAbortException();
                    break;

                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, MaxTryRegionIdx);
            FallbackFailFast(RhFailFastReason.InternalError, null);
        }

        private const uint MaxTryRegionIdx = 0xFFFFFFFFu;

        [RuntimeExport("RhThrowEx")]
        public static void RhThrowEx(object exceptionObj, ref ExInfo exInfo)
        {
            // trigger a GC (only if gcstress) to ensure we can stackwalk at this point
            GCStress.TriggerGC();

            InternalCalls.RhpValidateExInfoStack();

            // 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, MaxTryRegionIdx);
            FallbackFailFast(RhFailFastReason.InternalError, null);
        }

        [RuntimeExport("RhRethrow")]
        public static void RhRethrow(ref ExInfo activeExInfo, ref ExInfo exInfo)
        {
            // trigger a GC (only if gcstress) to ensure we can stackwalk at this point
            GCStress.TriggerGC();

            InternalCalls.RhpValidateExInfoStack();

            // 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, activeExInfo._idxCurClause);
            FallbackFailFast(RhFailFastReason.InternalError, null);
        }

        private static void DispatchEx(ref StackFrameIterator frameIter, ref ExInfo exInfo, uint startIdx)
        {
            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 = (startIdx != MaxTryRegionIdx);
            bool isFirstFrame = true;

            byte* prevControlPC = null;
            UIntPtr prevFramePtr = UIntPtr.Zero;
            bool unwoundReversePInvoke = false;

            bool isValid = frameIter.Init(exInfo._pExContext, (exInfo._kind & ExKind.InstructionFaultFlag) != 0);
            Debug.Assert(isValid, "RhThrowEx called with an unexpected context");

            OnFirstChanceExceptionViaClassLib(exceptionObj);
            DebuggerNotify.BeginFirstPass(exceptionObj, frameIter.ControlPC, frameIter.SP);

            for (; isValid; isValid = frameIter.Next(out startIdx, out unwoundReversePInvoke))
            {
                // 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;

                prevControlPC = frameIter.ControlPC;

                DebugScanCallFrame(exInfo._passNumber, frameIter.ControlPC, frameIter.SP);

                // A debugger can subscribe to get callbacks at a specific frame of exception dispatch
                // exInfo._notifyDebuggerSP can be populated by the debugger from out of process
                // at any time.
                if (exInfo._notifyDebuggerSP == frameIter.SP)
                    DebuggerNotify.FirstPassFrameEntered(exceptionObj, frameIter.ControlPC, frameIter.SP);

                UpdateStackTrace(exceptionObj, ref exInfo, ref isFirstRethrowFrame, ref prevFramePtr, ref isFirstFrame);

                byte* pHandler;
                if (FindFirstPassHandler(exceptionObj, startIdx, ref frameIter,
                                         out catchingTryRegionIdx, out pHandler))
                {
                    handlingFrameSP = frameIter.SP;
                    pCatchHandler = pHandler;

                    DebugVerifyHandlingFrame(handlingFrameSP);
                    break;
                }
            }
            DebuggerNotify.EndFirstPass(exceptionObj, pCatchHandler, handlingFrameSP);

            if (pCatchHandler == null)
            {
                UnhandledExceptionFailFastViaClasslib(
                    RhFailFastReason.PN_UnhandledException,
                    exceptionObj,
                    (IntPtr)prevControlPC, // 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.
            Debug.Assert(pCatchHandler != null, "We should have a handler if we're starting the second pass");

            DebuggerNotify.BeginSecondPass();
            // ------------------------------------------------
            //
            // 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.
            InternalCalls.RhpSetThreadDoNotTriggerGC();

            exInfo._passNumber = 2;
            startIdx = MaxTryRegionIdx;
            isValid = frameIter.Init(exInfo._pExContext, (exInfo._kind & ExKind.InstructionFaultFlag) != 0);
            for (; isValid && ((byte*)frameIter.SP <= (byte*)handlingFrameSP); isValid = frameIter.Next(out startIdx))
            {
                Debug.Assert(isValid, "second-pass EH unwind failed unexpectedly");
                DebugScanCallFrame(exInfo._passNumber, frameIter.ControlPC, frameIter.SP);

                if (frameIter.SP == handlingFrameSP)
                {
                    // invoke only a partial second-pass here...
                    InvokeSecondPass(ref exInfo, startIdx, catchingTryRegionIdx);
                    break;
                }

                InvokeSecondPass(ref exInfo, startIdx);
            }

            // ------------------------------------------------
            //
            // Call the handler and resume execution
            //
            // ------------------------------------------------
            exInfo._idxCurClause = catchingTryRegionIdx;
            InternalCalls.RhpCallCatchFunclet(
                exceptionObj, pCatchHandler, frameIter.RegisterSet, ref exInfo);
            // 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");
        }

        private static void UpdateStackTrace(object exceptionObj, ref ExInfo exInfo,
            ref bool isFirstRethrowFrame, ref UIntPtr prevFramePtr, ref bool isFirstFrame)
        {
            // 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.
            UIntPtr curFramePtr = exInfo._frameIter.FramePointer;
            if ((prevFramePtr == UIntPtr.Zero) || (curFramePtr != prevFramePtr))
            {
                AppendExceptionStackFrameViaClasslib(exceptionObj, (IntPtr)exInfo._frameIter.ControlPC,
                    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;
            byte* pbMethodStartAddress;
            if (!InternalCalls.RhpEHEnumInitFromStackFrameIterator(ref frameIter, &pbMethodStartAddress, &ehEnum))
                return false;

            byte* pbControlPC = frameIter.ControlPC;

            uint codeOffset = (uint)(pbControlPC - pbMethodStartAddress);

            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))
                        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, (EEType*)ehClause._pTargetType))
                    {
                        pHandler = ehClause._handlerAddress;
                        tryRegionIdx = curIdx;
                        return true;
                    }
                }
                else
                {
                    byte* pFilterFunclet = ehClause._filterAddress;
                    bool shouldInvokeHandler =
                        InternalCalls.RhpCallFilterFunclet(exception, pFilterFunclet, frameIter.RegisterSet);

                    if (shouldInvokeHandler)
                    {
                        pHandler = ehClause._handlerAddress;
                        tryRegionIdx = curIdx;
                        return true;
                    }
                }
            }

            return false;
        }

#if DEBUG && !INPLACE_RUNTIME
        private static EEType* s_pLowLevelObjectType;
        private static void AssertNotRuntimeObject(EEType* 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().EEType;
            }

            Debug.Assert(!pClauseType->IsEquivalentTo(s_pLowLevelObjectType));
        }
#endif // DEBUG && !INPLACE_RUNTIME


        private static bool ShouldTypedClauseCatchThisException(object exception, EEType* pClauseType)
        {
#if DEBUG && !INPLACE_RUNTIME
            AssertNotRuntimeObject(pClauseType);
#endif

            return TypeCast.IsInstanceOfClass(exception, pClauseType) != null;
        }

        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;
            byte* pbMethodStartAddress;
            if (!InternalCalls.RhpEHEnumInitFromStackFrameIterator(ref exInfo._frameIter, &pbMethodStartAddress, &ehEnum))
                return;

            byte* pbControlPC = exInfo._frameIter.ControlPC;

            uint codeOffset = (uint)(pbControlPC - pbMethodStartAddress);

            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))
                        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;
                InternalCalls.RhpCallFinallyFunclet(pFinallyHandler, exInfo._frameIter.RegisterSet);
                exInfo._idxCurClause = MaxTryRegionIdx;
            }
        }

        [NativeCallable(EntryPoint = "RhpFailFastForPInvokeExceptionPreemp", CallingConvention = CallingConvention.Cdecl)]
        public static void RhpFailFastForPInvokeExceptionPreemp(IntPtr PInvokeCallsiteReturnAddr, void* pExceptionRecord, void* pContextRecord)
        {
            FailFastViaClasslib(RhFailFastReason.PN_UnhandledExceptionFromPInvoke, null, PInvokeCallsiteReturnAddr);
        }
        [RuntimeExport("RhpFailFastForPInvokeExceptionCoop")]
        public static void RhpFailFastForPInvokeExceptionCoop(IntPtr classlibBreadcrumb, void* pExceptionRecord, void* pContextRecord)
        {
            FailFastViaClasslib(RhFailFastReason.PN_UnhandledExceptionFromPInvoke, null, classlibBreadcrumb);
        }
    } // static class EH
}