1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Diagnostics;
6 using System.Runtime;
7 using System.Runtime.CompilerServices;
8 using System.Runtime.InteropServices;
9 
10 using Internal.Runtime;
11 
12 namespace System.Runtime
13 {
14     public enum RhFailFastReason
15     {
16         Unknown = 0,
17         InternalError = 1,                                   // "Runtime internal error"
18         UnhandledException_ExceptionDispatchNotAllowed = 2,  // "Unhandled exception: no handler found before escaping a finally clause or other fail-fast scope."
19         UnhandledException_CallerDidNotHandle = 3,           // "Unhandled exception: no handler found in calling method."
20         ClassLibDidNotTranslateExceptionID = 4,              // "Unable to translate failure into a classlib-specific exception object."
21         IllegalNativeCallableEntry = 5,                      // "Invalid Program: attempted to call a NativeCallable method from runtime-typesafe code."
22 
23         PN_UnhandledException = 6,                           // ProjectN: "unhandled exception"
24         PN_UnhandledExceptionFromPInvoke = 7,                // ProjectN: "Unhandled exception: an unmanaged exception was thrown out of a managed-to-native transition."
25         Max
26     }
27 
28     // Keep this synchronized with the duplicate definition in DebugEventSource.cpp
29     [Flags]
30     internal enum ExceptionEventKind
31     {
32         Thrown = 1,
33         CatchHandlerFound = 2,
34         Unhandled = 4,
35         FirstPassFrameEntered = 8
36     }
37 
38     internal static unsafe class DebuggerNotify
39     {
40         // We cache the events a debugger is interested on the C# side to avoid p/invokes when the
41         // debugger isn't attached.
42         //
43         // Ideally we would like the managed debugger to start toggling this directly so that
44         // it stays perfectly up-to-date. However as a reasonable approximation we fetch
45         // the value from native code at the beginning of each exception dispatch. If the debugger
46         // attempts to enroll in more events mid-exception handling we aren't going to see it.
47         private static ExceptionEventKind s_cachedEventMask;
48 
BeginFirstPass(object exceptionObj, byte* faultingIP, UIntPtr faultingFrameSP)49         internal static void BeginFirstPass(object exceptionObj, byte* faultingIP, UIntPtr faultingFrameSP)
50         {
51             s_cachedEventMask = InternalCalls.RhpGetRequestedExceptionEvents();
52 
53             if ((s_cachedEventMask & ExceptionEventKind.Thrown) == 0)
54                 return;
55 
56             InternalCalls.RhpSendExceptionEventToDebugger(ExceptionEventKind.Thrown, faultingIP, faultingFrameSP);
57         }
58 
FirstPassFrameEntered(object exceptionObj, byte* enteredFrameIP, UIntPtr enteredFrameSP)59         internal static void FirstPassFrameEntered(object exceptionObj, byte* enteredFrameIP, UIntPtr enteredFrameSP)
60         {
61             s_cachedEventMask = InternalCalls.RhpGetRequestedExceptionEvents();
62 
63             if ((s_cachedEventMask & ExceptionEventKind.FirstPassFrameEntered) == 0)
64                 return;
65 
66             InternalCalls.RhpSendExceptionEventToDebugger(ExceptionEventKind.FirstPassFrameEntered, enteredFrameIP, enteredFrameSP);
67         }
68 
EndFirstPass(object exceptionObj, byte* handlerIP, UIntPtr handlingFrameSP)69         internal static void EndFirstPass(object exceptionObj, byte* handlerIP, UIntPtr handlingFrameSP)
70         {
71             if (handlerIP == null)
72             {
73                 if ((s_cachedEventMask & ExceptionEventKind.Unhandled) == 0)
74                     return;
75                 InternalCalls.RhpSendExceptionEventToDebugger(ExceptionEventKind.Unhandled, null, UIntPtr.Zero);
76             }
77             else
78             {
79                 if ((s_cachedEventMask & ExceptionEventKind.CatchHandlerFound) == 0)
80                     return;
81                 InternalCalls.RhpSendExceptionEventToDebugger(ExceptionEventKind.CatchHandlerFound, handlerIP, handlingFrameSP);
82             }
83         }
84 
BeginSecondPass()85         internal static void BeginSecondPass()
86         {
87             //desktop debugging has an unwind begin event, however it appears that is unneeded for now, and possibly
88             // will never be needed?
89         }
90     }
91 
92     internal static unsafe class EH
93     {
94         internal static UIntPtr MaxSP
95         {
96             get
97             {
98                 return (UIntPtr)(void*)(-1);
99             }
100         }
101 
102         private enum RhEHClauseKind
103         {
104             RH_EH_CLAUSE_TYPED = 0,
105             RH_EH_CLAUSE_FAULT = 1,
106             RH_EH_CLAUSE_FILTER = 2,
107             RH_EH_CLAUSE_UNUSED = 3,
108         }
109 
110         private struct RhEHClause
111         {
112             internal RhEHClauseKind _clauseKind;
113             internal uint _tryStartOffset;
114             internal uint _tryEndOffset;
115             internal byte* _filterAddress;
116             internal byte* _handlerAddress;
117             internal void* _pTargetType;
118 
119             ///<summary>
120             /// We expect the stackwalker to adjust return addresses to point at 'return address - 1' so that we
121             /// can use an interval here that is closed at the start and open at the end.  When a hardware fault
122             /// occurs, the IP is pointing at the start of the instruction and will not be adjusted by the
123             /// stackwalker.  Therefore, it will naturally work with an interval that has a closed start and open
124             /// end.
125             ///</summary>
ContainsCodeOffsetSystem.Runtime.EH.RhEHClause126             public bool ContainsCodeOffset(uint codeOffset)
127             {
128                 return ((codeOffset >= _tryStartOffset) &&
129                         (codeOffset < _tryEndOffset));
130             }
131         }
132 
133         [StructLayout(LayoutKind.Explicit, Size = AsmOffsets.SIZEOF__EHEnum)]
134         private struct EHEnum
135         {
136             [FieldOffset(0)]
137             private IntPtr _dummy; // For alignment
138         }
139 
140         // This is a fail-fast function used by the runtime as a last resort that will terminate the process with
141         // as little effort as possible. No guarantee is made about the semantics of this fail-fast.
FallbackFailFast(RhFailFastReason reason, object unhandledException)142         internal static void FallbackFailFast(RhFailFastReason reason, object unhandledException)
143         {
144             InternalCalls.RhpFallbackFailFast();
145         }
146 
147         // Constants used with RhpGetClasslibFunction, to indicate which classlib function
148         // we are interested in.
149         // Note: make sure you change the def in EHHelpers.cpp if you change this!
150         internal enum ClassLibFunctionId
151         {
152             GetRuntimeException = 0,
153             FailFast = 1,
154             // UnhandledExceptionHandler = 2, // unused
155             AppendExceptionStackFrame = 3,
156             CheckStaticClassConstruction = 4,
157             GetSystemArrayEEType = 5,
158             OnFirstChance = 6,
159         }
160 
161         // Given an address pointing somewhere into a managed module, get the classlib-defined fail-fast
162         // function and invoke it.  Any failure to find and invoke the function, or if it returns, results in
163         // MRT-defined fail-fast behavior.
FailFastViaClasslib(RhFailFastReason reason, object unhandledException, IntPtr classlibAddress)164         internal static void FailFastViaClasslib(RhFailFastReason reason, object unhandledException,
165             IntPtr classlibAddress)
166         {
167             // Find the classlib function that will fail fast. This is a RuntimeExport function from the
168             // classlib module, and is therefore managed-callable.
169             IntPtr pFailFastFunction = (IntPtr)InternalCalls.RhpGetClasslibFunctionFromCodeAddress(classlibAddress,
170                                                                            ClassLibFunctionId.FailFast);
171 
172             if (pFailFastFunction == IntPtr.Zero)
173             {
174                 // The classlib didn't provide a function, so we fail our way...
175                 FallbackFailFast(reason, unhandledException);
176             }
177 
178             try
179             {
180                 // Invoke the classlib fail fast function.
181                 CalliIntrinsics.CallVoid(pFailFastFunction, reason, unhandledException, IntPtr.Zero, IntPtr.Zero);
182             }
183             catch
184             {
185                 // disallow all exceptions leaking out of callbacks
186             }
187 
188             // The classlib's function should never return and should not throw. If it does, then we fail our way...
189             FallbackFailFast(reason, unhandledException);
190         }
191 
192 #if AMD64
193         [StructLayout(LayoutKind.Explicit, Size = 0x4d0)]
194 #elif ARM
195         [StructLayout(LayoutKind.Explicit, Size = 0x1a0)]
196 #elif X86
197         [StructLayout(LayoutKind.Explicit, Size = 0x2cc)]
198 #elif ARM64
199         [StructLayout(LayoutKind.Explicit, Size = 0x390)]
200 #else
201         [StructLayout(LayoutKind.Explicit, Size = 0x10)] // this is small enough that it should trip an assert in RhpCopyContextFromExInfo
202 #endif
203         private struct OSCONTEXT
204         {
205         }
206 
PointerAlign(void* ptr, int alignmentInBytes)207         internal static unsafe void* PointerAlign(void* ptr, int alignmentInBytes)
208         {
209             int alignMask = alignmentInBytes - 1;
210 #if BIT64
211             return (void*)((((long)ptr) + alignMask) & ~alignMask);
212 #else
213             return (void*)((((int)ptr) + alignMask) & ~alignMask);
214 #endif
215         }
216 
OnFirstChanceExceptionViaClassLib(object exception)217         private static void OnFirstChanceExceptionViaClassLib(object exception)
218         {
219             IntPtr pOnFirstChanceFunction =
220                 (IntPtr)InternalCalls.RhpGetClasslibFunctionFromEEType((IntPtr)exception.m_pEEType, ClassLibFunctionId.OnFirstChance);
221 
222             if (pOnFirstChanceFunction == IntPtr.Zero)
223             {
224                 return;
225             }
226 
227             try
228             {
229                 CalliIntrinsics.CallVoid(pOnFirstChanceFunction, exception);
230             }
231             catch
232             {
233                 // disallow all exceptions leaking out of callbacks
234             }
235         }
236 
237         [MethodImpl(MethodImplOptions.NoInlining)]
UnhandledExceptionFailFastViaClasslib( RhFailFastReason reason, object unhandledException, IntPtr classlibAddress, ref ExInfo exInfo)238         internal static unsafe void UnhandledExceptionFailFastViaClasslib(
239             RhFailFastReason reason, object unhandledException, IntPtr classlibAddress, ref ExInfo exInfo)
240         {
241             IntPtr pFailFastFunction =
242                 (IntPtr)InternalCalls.RhpGetClasslibFunctionFromCodeAddress(classlibAddress, ClassLibFunctionId.FailFast);
243 
244             if (pFailFastFunction == IntPtr.Zero)
245             {
246                 FailFastViaClasslib(
247                     reason,
248                     unhandledException,
249                     classlibAddress);
250             }
251 
252             // 16-byte align the context.  This is overkill on x86 and ARM, but simplifies things slightly.
253             const int contextAlignment = 16;
254             byte* pbBuffer = stackalloc byte[sizeof(OSCONTEXT) + contextAlignment];
255             void* pContext = PointerAlign(pbBuffer, contextAlignment);
256 
257             InternalCalls.RhpCopyContextFromExInfo(pContext, sizeof(OSCONTEXT), exInfo._pExContext);
258 
259             try
260             {
261                 CalliIntrinsics.CallVoid(pFailFastFunction, reason, unhandledException, exInfo._pExContext->IP, (IntPtr)pContext);
262             }
263             catch
264             {
265                 // disallow all exceptions leaking out of callbacks
266             }
267 
268             // The classlib's funciton should never return and should not throw. If it does, then we fail our way...
269             FallbackFailFast(reason, unhandledException);
270         }
271 
272         private enum RhEHFrameType
273         {
274             RH_EH_FIRST_FRAME = 1,
275             RH_EH_FIRST_RETHROW_FRAME = 2,
276         }
277 
AppendExceptionStackFrameViaClasslib(object exception, IntPtr IP, ref bool isFirstRethrowFrame, ref bool isFirstFrame)278         private static void AppendExceptionStackFrameViaClasslib(object exception, IntPtr IP,
279             ref bool isFirstRethrowFrame, ref bool isFirstFrame)
280         {
281             IntPtr pAppendStackFrame = (IntPtr)InternalCalls.RhpGetClasslibFunctionFromCodeAddress(IP,
282                 ClassLibFunctionId.AppendExceptionStackFrame);
283 
284             if (pAppendStackFrame != IntPtr.Zero)
285             {
286                 int flags = (isFirstFrame ? (int)RhEHFrameType.RH_EH_FIRST_FRAME : 0) |
287                             (isFirstRethrowFrame ? (int)RhEHFrameType.RH_EH_FIRST_RETHROW_FRAME : 0);
288 
289                 try
290                 {
291                     CalliIntrinsics.CallVoid(pAppendStackFrame, exception, IP, flags);
292                 }
293                 catch
294                 {
295                     // disallow all exceptions leaking out of callbacks
296                 }
297 
298                 // Clear flags only if we called the function
299                 isFirstRethrowFrame = false;
300                 isFirstFrame = false;
301             }
302         }
303 
304         // Given an ExceptionID and an address pointing somewhere into a managed module, get
305         // an exception object of a type that the module containing the given address will understand.
306         // This finds the classlib-defined GetRuntimeException function and asks it for the exception object.
GetClasslibException(ExceptionIDs id, IntPtr address)307         internal static Exception GetClasslibException(ExceptionIDs id, IntPtr address)
308         {
309             // Find the classlib function that will give us the exception object we want to throw. This
310             // is a RuntimeExport function from the classlib module, and is therefore managed-callable.
311             IntPtr pGetRuntimeExceptionFunction =
312                 (IntPtr)InternalCalls.RhpGetClasslibFunctionFromCodeAddress(address, ClassLibFunctionId.GetRuntimeException);
313 
314             // Return the exception object we get from the classlib.
315             Exception e = null;
316             try
317             {
318                 e = CalliIntrinsics.Call<Exception>(pGetRuntimeExceptionFunction, id);
319             }
320             catch
321             {
322                 // disallow all exceptions leaking out of callbacks
323             }
324 
325             // If the helper fails to yield an object, then we fail-fast.
326             if (e == null)
327             {
328                 FailFastViaClasslib(
329                     RhFailFastReason.ClassLibDidNotTranslateExceptionID,
330                     null,
331                     address);
332             }
333 
334             return e;
335         }
336 
337         // Given an ExceptionID and an EEType address, get an exception object of a type that the module containing
338         // the given address will understand. This finds the classlib-defined GetRuntimeException function and asks
339         // it for the exception object.
GetClasslibExceptionFromEEType(ExceptionIDs id, IntPtr pEEType)340         internal static Exception GetClasslibExceptionFromEEType(ExceptionIDs id, IntPtr pEEType)
341         {
342             // Find the classlib function that will give us the exception object we want to throw. This
343             // is a RuntimeExport function from the classlib module, and is therefore managed-callable.
344             IntPtr pGetRuntimeExceptionFunction = IntPtr.Zero;
345             if (pEEType != IntPtr.Zero)
346             {
347                 pGetRuntimeExceptionFunction = (IntPtr)InternalCalls.RhpGetClasslibFunctionFromEEType(pEEType, ClassLibFunctionId.GetRuntimeException);
348             }
349 
350             // Return the exception object we get from the classlib.
351             Exception e = null;
352             try
353             {
354                 e = CalliIntrinsics.Call<Exception>(pGetRuntimeExceptionFunction, id);
355             }
356             catch
357             {
358                 // disallow all exceptions leaking out of callbacks
359             }
360 
361             // If the helper fails to yield an object, then we fail-fast.
362             if (e == null)
363             {
364                 FailFastViaClasslib(
365                     RhFailFastReason.ClassLibDidNotTranslateExceptionID,
366                     null,
367                     pEEType);
368             }
369 
370             return e;
371         }
372 
373         // RhExceptionHandling_ functions are used to throw exceptions out of our asm helpers. We tail-call from
374         // the asm helpers to these functions, which performs the throw. The tail-call is important: it ensures that
375         // the stack is crawlable from within these functions.
376         [RuntimeExport("RhExceptionHandling_ThrowClasslibOverflowException")]
ThrowClasslibOverflowException(IntPtr address)377         public static void ThrowClasslibOverflowException(IntPtr address)
378         {
379             // Throw the overflow exception defined by the classlib, using the return address of the asm helper
380             // to find the correct classlib.
381 
382             throw GetClasslibException(ExceptionIDs.Overflow, address);
383         }
384 
385         [RuntimeExport("RhExceptionHandling_ThrowClasslibDivideByZeroException")]
ThrowClasslibDivideByZeroException(IntPtr address)386         public static void ThrowClasslibDivideByZeroException(IntPtr address)
387         {
388             // Throw the divide by zero exception defined by the classlib, using the return address of the asm helper
389             // to find the correct classlib.
390 
391             throw GetClasslibException(ExceptionIDs.DivideByZero, address);
392         }
393 
394         [RuntimeExport("RhExceptionHandling_FailedAllocation")]
FailedAllocation(EETypePtr pEEType, bool fIsOverflow)395         public static void FailedAllocation(EETypePtr pEEType, bool fIsOverflow)
396         {
397             ExceptionIDs exID = fIsOverflow ? ExceptionIDs.Overflow : ExceptionIDs.OutOfMemory;
398 
399             // Throw the out of memory exception defined by the classlib, using the input EEType*
400             // to find the correct classlib.
401 
402             throw pEEType.ToPointer()->GetClasslibException(exID);
403         }
404 
405 #if !INPLACE_RUNTIME
406         private static OutOfMemoryException s_theOOMException = new OutOfMemoryException();
407 
408         // MRT exports GetRuntimeException for the few cases where we have a helper that throws an exception
409         // and may be called by either MRT or other classlibs and that helper needs to throw an exception.
410         // There are only a few cases where this happens now (the fast allocation helpers), so we limit the
411         // exception types that MRT will return.
412         [RuntimeExport("GetRuntimeException")]
GetRuntimeException(ExceptionIDs id)413         public static Exception GetRuntimeException(ExceptionIDs id)
414         {
415             switch (id)
416             {
417                 case ExceptionIDs.OutOfMemory:
418                     // Throw a preallocated exception to avoid infinite recursion.
419                     return s_theOOMException;
420 
421                 case ExceptionIDs.Overflow:
422                     return new OverflowException();
423 
424                 case ExceptionIDs.InvalidCast:
425                     return new InvalidCastException();
426 
427                 default:
428                     Debug.Assert(false, "unexpected ExceptionID");
429                     FallbackFailFast(RhFailFastReason.InternalError, null);
430                     return null;
431             }
432         }
433 #endif
434 
435         private enum HwExceptionCode : uint
436         {
437             STATUS_REDHAWK_NULL_REFERENCE = 0x00000000u,
438             STATUS_REDHAWK_WRITE_BARRIER_NULL_REFERENCE = 0x00000042u,
439             STATUS_REDHAWK_THREAD_ABORT = 0x00000043u,
440 
441             STATUS_DATATYPE_MISALIGNMENT = 0x80000002u,
442             STATUS_ACCESS_VIOLATION = 0xC0000005u,
443             STATUS_INTEGER_DIVIDE_BY_ZERO = 0xC0000094u,
444             STATUS_INTEGER_OVERFLOW = 0xC0000095u,
445         }
446 
447         [StructLayout(LayoutKind.Explicit, Size = AsmOffsets.SIZEOF__PAL_LIMITED_CONTEXT)]
448         public struct PAL_LIMITED_CONTEXT
449         {
450             [FieldOffset(AsmOffsets.OFFSETOF__PAL_LIMITED_CONTEXT__IP)]
451             internal IntPtr IP;
452             // the rest of the struct is left unspecified.
453         }
454 
455         // N.B. -- These values are burned into the throw helper assembly code and are also known the the
456         //         StackFrameIterator code.
457         [Flags]
458         internal enum ExKind : byte
459         {
460             None = 0,
461             Throw = 1,
462             HardwareFault = 2,
463             KindMask = 3,
464 
465             RethrowFlag = 4,
466 
467             SupersededFlag = 8,
468 
469             InstructionFaultFlag = 0x10
470         }
471 
472         [StructLayout(LayoutKind.Explicit)]
473         public ref struct ExInfo
474         {
InitSystem.Runtime.EH.ExInfo475             internal void Init(object exceptionObj, bool instructionFault = false)
476             {
477                 // _pPrevExInfo    -- set by asm helper
478                 // _pExContext     -- set by asm helper
479                 // _passNumber     -- set by asm helper
480                 // _kind           -- set by asm helper
481                 // _idxCurClause   -- set by asm helper
482                 // _frameIter      -- initialized explicitly during dispatch
483 
484                 _exception = exceptionObj;
485                 if (instructionFault)
486                     _kind |= ExKind.InstructionFaultFlag;
487                 _notifyDebuggerSP = UIntPtr.Zero;
488             }
489 
InitSystem.Runtime.EH.ExInfo490             internal void Init(object exceptionObj, ref ExInfo rethrownExInfo)
491             {
492                 // _pPrevExInfo    -- set by asm helper
493                 // _pExContext     -- set by asm helper
494                 // _passNumber     -- set by asm helper
495                 // _idxCurClause   -- set by asm helper
496                 // _frameIter      -- initialized explicitly during dispatch
497 
498                 _exception = exceptionObj;
499                 _kind = rethrownExInfo._kind | ExKind.RethrowFlag;
500                 _notifyDebuggerSP = UIntPtr.Zero;
501             }
502 
503             internal object ThrownException
504             {
505                 get
506                 {
507                     return _exception;
508                 }
509             }
510 
511             [FieldOffset(AsmOffsets.OFFSETOF__ExInfo__m_pPrevExInfo)]
512             internal void* _pPrevExInfo;
513 
514             [FieldOffset(AsmOffsets.OFFSETOF__ExInfo__m_pExContext)]
515             internal PAL_LIMITED_CONTEXT* _pExContext;
516 
517             [FieldOffset(AsmOffsets.OFFSETOF__ExInfo__m_exception)]
518             private object _exception;  // actual object reference, specially reported by GcScanRootsWorker
519 
520             [FieldOffset(AsmOffsets.OFFSETOF__ExInfo__m_kind)]
521             internal ExKind _kind;
522 
523             [FieldOffset(AsmOffsets.OFFSETOF__ExInfo__m_passNumber)]
524             internal byte _passNumber;
525 
526             // BEWARE: This field is used by the stackwalker to know if the dispatch code has reached the
527             //         point at which a handler is called.  In other words, it serves as an "is a handler
528             //         active" state where '_idxCurClause == MaxTryRegionIdx' means 'no'.
529             [FieldOffset(AsmOffsets.OFFSETOF__ExInfo__m_idxCurClause)]
530             internal uint _idxCurClause;
531 
532             [FieldOffset(AsmOffsets.OFFSETOF__ExInfo__m_frameIter)]
533             internal StackFrameIterator _frameIter;
534 
535             [FieldOffset(AsmOffsets.OFFSETOF__ExInfo__m_notifyDebuggerSP)]
536             volatile internal UIntPtr _notifyDebuggerSP;
537         }
538 
539         //
540         // Called by RhpThrowHwEx
541         //
542         [RuntimeExport("RhThrowHwEx")]
RhThrowHwEx(uint exceptionCode, ref ExInfo exInfo)543         public static void RhThrowHwEx(uint exceptionCode, ref ExInfo exInfo)
544         {
545             // trigger a GC (only if gcstress) to ensure we can stackwalk at this point
546             GCStress.TriggerGC();
547 
548             InternalCalls.RhpValidateExInfoStack();
549 
550             IntPtr faultingCodeAddress = exInfo._pExContext->IP;
551             bool instructionFault = true;
552             ExceptionIDs exceptionId = default(ExceptionIDs);
553             Exception exceptionToThrow = null;
554 
555             switch (exceptionCode)
556             {
557                 case (uint)HwExceptionCode.STATUS_REDHAWK_NULL_REFERENCE:
558                     exceptionId = ExceptionIDs.NullReference;
559                     break;
560 
561                 case (uint)HwExceptionCode.STATUS_REDHAWK_WRITE_BARRIER_NULL_REFERENCE:
562                     // The write barrier where the actual fault happened has been unwound already.
563                     // The IP of this fault needs to be treated as return address, not as IP of
564                     // faulting instruction.
565                     instructionFault = false;
566                     exceptionId = ExceptionIDs.NullReference;
567                     break;
568 
569                 case (uint)HwExceptionCode.STATUS_REDHAWK_THREAD_ABORT:
570                     exceptionToThrow = InternalCalls.RhpGetThreadAbortException();
571                     break;
572 
573                 case (uint)HwExceptionCode.STATUS_DATATYPE_MISALIGNMENT:
574                     exceptionId = ExceptionIDs.DataMisaligned;
575                     break;
576 
577                 // N.B. -- AVs that have a read/write address lower than 64k are already transformed to
578                 //         HwExceptionCode.REDHAWK_NULL_REFERENCE prior to calling this routine.
579                 case (uint)HwExceptionCode.STATUS_ACCESS_VIOLATION:
580                     exceptionId = ExceptionIDs.AccessViolation;
581                     break;
582 
583                 case (uint)HwExceptionCode.STATUS_INTEGER_DIVIDE_BY_ZERO:
584                     exceptionId = ExceptionIDs.DivideByZero;
585                     break;
586 
587                 case (uint)HwExceptionCode.STATUS_INTEGER_OVERFLOW:
588                     exceptionId = ExceptionIDs.Overflow;
589                     break;
590 
591                 default:
592                     // We don't wrap SEH exceptions from foreign code like CLR does, so we believe that we
593                     // know the complete set of HW faults generated by managed code and do not need to handle
594                     // this case.
595                     FailFastViaClasslib(RhFailFastReason.InternalError, null, faultingCodeAddress);
596                     break;
597             }
598 
599             if (exceptionId != default(ExceptionIDs))
600             {
601                 exceptionToThrow = GetClasslibException(exceptionId, faultingCodeAddress);
602             }
603 
604             exInfo.Init(exceptionToThrow, instructionFault);
605             DispatchEx(ref exInfo._frameIter, ref exInfo, MaxTryRegionIdx);
606             FallbackFailFast(RhFailFastReason.InternalError, null);
607         }
608 
609         private const uint MaxTryRegionIdx = 0xFFFFFFFFu;
610 
611         [RuntimeExport("RhThrowEx")]
RhThrowEx(object exceptionObj, ref ExInfo exInfo)612         public static void RhThrowEx(object exceptionObj, ref ExInfo exInfo)
613         {
614             // trigger a GC (only if gcstress) to ensure we can stackwalk at this point
615             GCStress.TriggerGC();
616 
617             InternalCalls.RhpValidateExInfoStack();
618 
619             // Transform attempted throws of null to a throw of NullReferenceException.
620             if (exceptionObj == null)
621             {
622                 IntPtr faultingCodeAddress = exInfo._pExContext->IP;
623                 exceptionObj = GetClasslibException(ExceptionIDs.NullReference, faultingCodeAddress);
624             }
625 
626             exInfo.Init(exceptionObj);
627             DispatchEx(ref exInfo._frameIter, ref exInfo, MaxTryRegionIdx);
628             FallbackFailFast(RhFailFastReason.InternalError, null);
629         }
630 
631         [RuntimeExport("RhRethrow")]
RhRethrow(ref ExInfo activeExInfo, ref ExInfo exInfo)632         public static void RhRethrow(ref ExInfo activeExInfo, ref ExInfo exInfo)
633         {
634             // trigger a GC (only if gcstress) to ensure we can stackwalk at this point
635             GCStress.TriggerGC();
636 
637             InternalCalls.RhpValidateExInfoStack();
638 
639             // We need to copy the exception object to this stack location because collided unwinds
640             // will cause the original stack location to go dead.
641             object rethrownException = activeExInfo.ThrownException;
642 
643             exInfo.Init(rethrownException, ref activeExInfo);
644             DispatchEx(ref exInfo._frameIter, ref exInfo, activeExInfo._idxCurClause);
645             FallbackFailFast(RhFailFastReason.InternalError, null);
646         }
647 
DispatchEx(ref StackFrameIterator frameIter, ref ExInfo exInfo, uint startIdx)648         private static void DispatchEx(ref StackFrameIterator frameIter, ref ExInfo exInfo, uint startIdx)
649         {
650             Debug.Assert(exInfo._passNumber == 1, "expected asm throw routine to set the pass");
651             object exceptionObj = exInfo.ThrownException;
652 
653             // ------------------------------------------------
654             //
655             // First pass
656             //
657             // ------------------------------------------------
658             UIntPtr handlingFrameSP = MaxSP;
659             byte* pCatchHandler = null;
660             uint catchingTryRegionIdx = MaxTryRegionIdx;
661 
662             bool isFirstRethrowFrame = (startIdx != MaxTryRegionIdx);
663             bool isFirstFrame = true;
664 
665             byte* prevControlPC = null;
666             UIntPtr prevFramePtr = UIntPtr.Zero;
667             bool unwoundReversePInvoke = false;
668 
669             bool isValid = frameIter.Init(exInfo._pExContext, (exInfo._kind & ExKind.InstructionFaultFlag) != 0);
670             Debug.Assert(isValid, "RhThrowEx called with an unexpected context");
671 
672             OnFirstChanceExceptionViaClassLib(exceptionObj);
673             DebuggerNotify.BeginFirstPass(exceptionObj, frameIter.ControlPC, frameIter.SP);
674 
675             for (; isValid; isValid = frameIter.Next(out startIdx, out unwoundReversePInvoke))
676             {
677                 // For GC stackwalking, we'll happily walk across native code blocks, but for EH dispatch, we
678                 // disallow dispatching exceptions across native code.
679                 if (unwoundReversePInvoke)
680                     break;
681 
682                 prevControlPC = frameIter.ControlPC;
683 
684                 DebugScanCallFrame(exInfo._passNumber, frameIter.ControlPC, frameIter.SP);
685 
686                 // A debugger can subscribe to get callbacks at a specific frame of exception dispatch
687                 // exInfo._notifyDebuggerSP can be populated by the debugger from out of process
688                 // at any time.
689                 if (exInfo._notifyDebuggerSP == frameIter.SP)
690                     DebuggerNotify.FirstPassFrameEntered(exceptionObj, frameIter.ControlPC, frameIter.SP);
691 
692                 UpdateStackTrace(exceptionObj, ref exInfo, ref isFirstRethrowFrame, ref prevFramePtr, ref isFirstFrame);
693 
694                 byte* pHandler;
695                 if (FindFirstPassHandler(exceptionObj, startIdx, ref frameIter,
696                                          out catchingTryRegionIdx, out pHandler))
697                 {
698                     handlingFrameSP = frameIter.SP;
699                     pCatchHandler = pHandler;
700 
701                     DebugVerifyHandlingFrame(handlingFrameSP);
702                     break;
703                 }
704             }
705             DebuggerNotify.EndFirstPass(exceptionObj, pCatchHandler, handlingFrameSP);
706 
707             if (pCatchHandler == null)
708             {
709                 UnhandledExceptionFailFastViaClasslib(
710                     RhFailFastReason.PN_UnhandledException,
711                     exceptionObj,
712                     (IntPtr)prevControlPC, // IP of the last frame that did not handle the exception
713                     ref exInfo);
714             }
715 
716             // We FailFast above if the exception goes unhandled.  Therefore, we cannot run the second pass
717             // without a catch handler.
718             Debug.Assert(pCatchHandler != null, "We should have a handler if we're starting the second pass");
719 
720             DebuggerNotify.BeginSecondPass();
721             // ------------------------------------------------
722             //
723             // Second pass
724             //
725             // ------------------------------------------------
726 
727             // Due to the stackwalker logic, we cannot tolerate triggering a GC from the dispatch code once we
728             // are in the 2nd pass.  This is because the stackwalker applies a particular unwind semantic to
729             // 'collapse' funclets which gets confused when we walk out of the dispatch code and encounter the
730             // 'main body' without first encountering the funclet.  The thunks used to invoke 2nd-pass
731             // funclets will always toggle this mode off before invoking them.
732             InternalCalls.RhpSetThreadDoNotTriggerGC();
733 
734             exInfo._passNumber = 2;
735             startIdx = MaxTryRegionIdx;
736             isValid = frameIter.Init(exInfo._pExContext, (exInfo._kind & ExKind.InstructionFaultFlag) != 0);
737             for (; isValid && ((byte*)frameIter.SP <= (byte*)handlingFrameSP); isValid = frameIter.Next(out startIdx))
738             {
739                 Debug.Assert(isValid, "second-pass EH unwind failed unexpectedly");
740                 DebugScanCallFrame(exInfo._passNumber, frameIter.ControlPC, frameIter.SP);
741 
742                 if (frameIter.SP == handlingFrameSP)
743                 {
744                     // invoke only a partial second-pass here...
745                     InvokeSecondPass(ref exInfo, startIdx, catchingTryRegionIdx);
746                     break;
747                 }
748 
749                 InvokeSecondPass(ref exInfo, startIdx);
750             }
751 
752             // ------------------------------------------------
753             //
754             // Call the handler and resume execution
755             //
756             // ------------------------------------------------
757             exInfo._idxCurClause = catchingTryRegionIdx;
758             InternalCalls.RhpCallCatchFunclet(
759                 exceptionObj, pCatchHandler, frameIter.RegisterSet, ref exInfo);
760             // currently, RhpCallCatchFunclet will resume after the catch
761             Debug.Assert(false, "unreachable");
762             FallbackFailFast(RhFailFastReason.InternalError, null);
763         }
764 
765         [System.Diagnostics.Conditional("DEBUG")]
DebugScanCallFrame(int passNumber, byte* ip, UIntPtr sp)766         private static void DebugScanCallFrame(int passNumber, byte* ip, UIntPtr sp)
767         {
768             Debug.Assert(ip != null, "IP address must not be null");
769         }
770 
771         [System.Diagnostics.Conditional("DEBUG")]
DebugVerifyHandlingFrame(UIntPtr handlingFrameSP)772         private static void DebugVerifyHandlingFrame(UIntPtr handlingFrameSP)
773         {
774             Debug.Assert(handlingFrameSP != MaxSP, "Handling frame must have an SP value");
775             Debug.Assert(((UIntPtr*)handlingFrameSP) > &handlingFrameSP,
776                 "Handling frame must have a valid stack frame pointer");
777         }
778 
UpdateStackTrace(object exceptionObj, ref ExInfo exInfo, ref bool isFirstRethrowFrame, ref UIntPtr prevFramePtr, ref bool isFirstFrame)779         private static void UpdateStackTrace(object exceptionObj, ref ExInfo exInfo,
780             ref bool isFirstRethrowFrame, ref UIntPtr prevFramePtr, ref bool isFirstFrame)
781         {
782             // We use the fact that all funclet stack frames belonging to the same logical method activation
783             // will have the same FramePointer value.  Additionally, the stackwalker will return a sequence of
784             // callbacks for all the funclet stack frames, one right after the other.  The classlib doesn't
785             // want to know about funclets, so we strip them out by only reporting the first frame of a
786             // sequence of funclets.  This is correct because the leafmost funclet is first in the sequence
787             // and corresponds to the current 'IP state' of the method.
788             UIntPtr curFramePtr = exInfo._frameIter.FramePointer;
789             if ((prevFramePtr == UIntPtr.Zero) || (curFramePtr != prevFramePtr))
790             {
791                 AppendExceptionStackFrameViaClasslib(exceptionObj, (IntPtr)exInfo._frameIter.ControlPC,
792                     ref isFirstRethrowFrame, ref isFirstFrame);
793             }
794             prevFramePtr = curFramePtr;
795         }
796 
FindFirstPassHandler(object exception, uint idxStart, ref StackFrameIterator frameIter, out uint tryRegionIdx, out byte* pHandler)797         private static bool FindFirstPassHandler(object exception, uint idxStart,
798             ref StackFrameIterator frameIter, out uint tryRegionIdx, out byte* pHandler)
799         {
800             pHandler = null;
801             tryRegionIdx = MaxTryRegionIdx;
802 
803             EHEnum ehEnum;
804             byte* pbMethodStartAddress;
805             if (!InternalCalls.RhpEHEnumInitFromStackFrameIterator(ref frameIter, &pbMethodStartAddress, &ehEnum))
806                 return false;
807 
808             byte* pbControlPC = frameIter.ControlPC;
809 
810             uint codeOffset = (uint)(pbControlPC - pbMethodStartAddress);
811 
812             uint lastTryStart = 0, lastTryEnd = 0;
813 
814             // Search the clauses for one that contains the current offset.
815             RhEHClause ehClause;
816             for (uint curIdx = 0; InternalCalls.RhpEHEnumNext(&ehEnum, &ehClause); curIdx++)
817             {
818                 //
819                 // Skip to the starting try region.  This is used by collided unwinds and rethrows to pickup where
820                 // the previous dispatch left off.
821                 //
822                 if (idxStart != MaxTryRegionIdx)
823                 {
824                     if (curIdx <= idxStart)
825                     {
826                         lastTryStart = ehClause._tryStartOffset; lastTryEnd = ehClause._tryEndOffset;
827                         continue;
828                     }
829 
830                     // Now, we continue skipping while the try region is identical to the one that invoked the
831                     // previous dispatch.
832                     if ((ehClause._tryStartOffset == lastTryStart) && (ehClause._tryEndOffset == lastTryEnd))
833                         continue;
834 
835                     // We are done skipping. This is required to handle empty finally block markers that are used
836                     // to separate runs of different try blocks with same native code offsets.
837                     idxStart = MaxTryRegionIdx;
838                 }
839 
840                 RhEHClauseKind clauseKind = ehClause._clauseKind;
841 
842                 if (((clauseKind != RhEHClauseKind.RH_EH_CLAUSE_TYPED) &&
843                      (clauseKind != RhEHClauseKind.RH_EH_CLAUSE_FILTER))
844                     || !ehClause.ContainsCodeOffset(codeOffset))
845                 {
846                     continue;
847                 }
848 
849                 // Found a containing clause. Because of the order of the clauses, we know this is the
850                 // most containing.
851                 if (clauseKind == RhEHClauseKind.RH_EH_CLAUSE_TYPED)
852                 {
853                     if (ShouldTypedClauseCatchThisException(exception, (EEType*)ehClause._pTargetType))
854                     {
855                         pHandler = ehClause._handlerAddress;
856                         tryRegionIdx = curIdx;
857                         return true;
858                     }
859                 }
860                 else
861                 {
862                     byte* pFilterFunclet = ehClause._filterAddress;
863                     bool shouldInvokeHandler =
864                         InternalCalls.RhpCallFilterFunclet(exception, pFilterFunclet, frameIter.RegisterSet);
865 
866                     if (shouldInvokeHandler)
867                     {
868                         pHandler = ehClause._handlerAddress;
869                         tryRegionIdx = curIdx;
870                         return true;
871                     }
872                 }
873             }
874 
875             return false;
876         }
877 
878         private static EEType* s_pLowLevelObjectType;
879 
ShouldTypedClauseCatchThisException(object exception, EEType* pClauseType)880         private static bool ShouldTypedClauseCatchThisException(object exception, EEType* pClauseType)
881         {
882             if (TypeCast.IsInstanceOfClass(exception, pClauseType) != null)
883                 return true;
884 
885             if (s_pLowLevelObjectType == null)
886             {
887                 // TODO: Avoid allocating here as that may fail
888                 s_pLowLevelObjectType = new System.Object().EEType;
889             }
890 
891             // This allows the typical try { } catch { }--which expands to a typed catch of System.Object--to work on
892             // all objects when the clause is in the low level runtime code.  This special case is needed because
893             // objects from foreign type systems are sometimes throw back up at runtime code and this is the only way
894             // to catch them outside of having a filter with no type check in it, which isn't currently possible to
895             // write in C#.  See https://github.com/dotnet/roslyn/issues/4388
896             if (pClauseType->IsEquivalentTo(s_pLowLevelObjectType))
897                 return true;
898 
899             return false;
900         }
901 
InvokeSecondPass(ref ExInfo exInfo, uint idxStart)902         private static void InvokeSecondPass(ref ExInfo exInfo, uint idxStart)
903         {
904             InvokeSecondPass(ref exInfo, idxStart, MaxTryRegionIdx);
905         }
InvokeSecondPass(ref ExInfo exInfo, uint idxStart, uint idxLimit)906         private static void InvokeSecondPass(ref ExInfo exInfo, uint idxStart, uint idxLimit)
907         {
908             EHEnum ehEnum;
909             byte* pbMethodStartAddress;
910             if (!InternalCalls.RhpEHEnumInitFromStackFrameIterator(ref exInfo._frameIter, &pbMethodStartAddress, &ehEnum))
911                 return;
912 
913             byte* pbControlPC = exInfo._frameIter.ControlPC;
914 
915             uint codeOffset = (uint)(pbControlPC - pbMethodStartAddress);
916 
917             uint lastTryStart = 0, lastTryEnd = 0;
918 
919             // Search the clauses for one that contains the current offset.
920             RhEHClause ehClause;
921             for (uint curIdx = 0; InternalCalls.RhpEHEnumNext(&ehEnum, &ehClause) && curIdx < idxLimit; curIdx++)
922             {
923                 //
924                 // Skip to the starting try region.  This is used by collided unwinds and rethrows to pickup where
925                 // the previous dispatch left off.
926                 //
927                 if (idxStart != MaxTryRegionIdx)
928                 {
929                     if (curIdx <= idxStart)
930                     {
931                         lastTryStart = ehClause._tryStartOffset; lastTryEnd = ehClause._tryEndOffset;
932                         continue;
933                     }
934 
935                     // Now, we continue skipping while the try region is identical to the one that invoked the
936                     // previous dispatch.
937                     if ((ehClause._tryStartOffset == lastTryStart) && (ehClause._tryEndOffset == lastTryEnd))
938                         continue;
939 
940                     // We are done skipping. This is required to handle empty finally block markers that are used
941                     // to separate runs of different try blocks with same native code offsets.
942                     idxStart = MaxTryRegionIdx;
943                 }
944 
945                 RhEHClauseKind clauseKind = ehClause._clauseKind;
946 
947                 if ((clauseKind != RhEHClauseKind.RH_EH_CLAUSE_FAULT)
948                     || !ehClause.ContainsCodeOffset(codeOffset))
949                 {
950                     continue;
951                 }
952 
953                 // Found a containing clause. Because of the order of the clauses, we know this is the
954                 // most containing.
955 
956                 // N.B. -- We need to suppress GC "in-between" calls to finallys in this loop because we do
957                 // not have the correct next-execution point live on the stack and, therefore, may cause a GC
958                 // hole if we allow a GC between invocation of finally funclets (i.e. after one has returned
959                 // here to the dispatcher, but before the next one is invoked).  Once they are running, it's
960                 // fine for them to trigger a GC, obviously.
961                 //
962                 // As a result, RhpCallFinallyFunclet will set this state in the runtime upon return from the
963                 // funclet, and we need to reset it if/when we fall out of the loop and we know that the
964                 // method will no longer get any more GC callbacks.
965 
966                 byte* pFinallyHandler = ehClause._handlerAddress;
967                 exInfo._idxCurClause = curIdx;
968                 InternalCalls.RhpCallFinallyFunclet(pFinallyHandler, exInfo._frameIter.RegisterSet);
969                 exInfo._idxCurClause = MaxTryRegionIdx;
970             }
971         }
972 
973         [NativeCallable(EntryPoint = "RhpFailFastForPInvokeExceptionPreemp", CallingConvention = CallingConvention.Cdecl)]
RhpFailFastForPInvokeExceptionPreemp(IntPtr PInvokeCallsiteReturnAddr, void* pExceptionRecord, void* pContextRecord)974         public static void RhpFailFastForPInvokeExceptionPreemp(IntPtr PInvokeCallsiteReturnAddr, void* pExceptionRecord, void* pContextRecord)
975         {
976             FailFastViaClasslib(RhFailFastReason.PN_UnhandledExceptionFromPInvoke, null, PInvokeCallsiteReturnAddr);
977         }
978         [RuntimeExport("RhpFailFastForPInvokeExceptionCoop")]
RhpFailFastForPInvokeExceptionCoop(IntPtr classlibBreadcrumb, void* pExceptionRecord, void* pContextRecord)979         public static void RhpFailFastForPInvokeExceptionCoop(IntPtr classlibBreadcrumb, void* pExceptionRecord, void* pContextRecord)
980         {
981             FailFastViaClasslib(RhFailFastReason.PN_UnhandledExceptionFromPInvoke, null, classlibBreadcrumb);
982         }
983     } // static class EH
984 }
985