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