diff --git a/docs/design/coreclr/botr/readytorun-format.md b/docs/design/coreclr/botr/readytorun-format.md index 08d58deb0ab7bc..aaede3419780fd 100644 --- a/docs/design/coreclr/botr/readytorun-format.md +++ b/docs/design/coreclr/botr/readytorun-format.md @@ -858,6 +858,7 @@ enum ReadyToRunHelper READYTORUN_HELPER_FailFast = 0x24, READYTORUN_HELPER_ThrowNullRef = 0x25, READYTORUN_HELPER_ThrowDivZero = 0x26, + READYTORUN_HELPER_ThrowExact = 0x27, // Write barriers READYTORUN_HELPER_WriteBarrier = 0x30, diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index db08b551d1b7cc..a91112d2db47ee 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -87,6 +87,11 @@ internal enum ContinuationFlags ContinueOnCapturedTaskScheduler = 64, } + internal enum RhEHFrameType + { + RH_EH_RUNTIME_ASYNC_FRAME = 4, + } + // Keep in sync with CORINFO_AsyncResumeInfo in corinfo.h internal unsafe struct ResumeInfo { @@ -226,6 +231,14 @@ public void CaptureContexts() [ThreadStatic] private static RuntimeAsyncAwaitState t_runtimeAsyncAwaitState; +#if !NATIVEAOT + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "AsyncHelpers_AddContinuationToExInternal")] + private static unsafe partial void AddContinuationToExInternal(void* diagnosticIP, ObjectHandleOnStack ex); + + internal static unsafe void AddContinuationToExInternal(void* diagnosticIP, Exception e) + => AddContinuationToExInternal(diagnosticIP, ObjectHandleOnStack.Create(ref e)); +#endif private static unsafe Continuation AllocContinuation(Continuation prevContinuation, MethodTable* contMT) { @@ -434,6 +447,7 @@ internal void HandleSuspended() } } + [StackTraceHidden] private unsafe void DispatchContinuations() { ExecutionAndSyncBlockStore contexts = default; @@ -467,7 +481,7 @@ private unsafe void DispatchContinuations() } catch (Exception ex) { - Continuation? handlerContinuation = UnwindToPossibleHandler(asyncDispatcherInfo.NextContinuation); + Continuation? handlerContinuation = UnwindToPossibleHandler(asyncDispatcherInfo.NextContinuation, ex); if (handlerContinuation == null) { // Tail of AsyncTaskMethodBuilderT.SetException @@ -518,10 +532,34 @@ private unsafe void DispatchContinuations() private ref byte GetResultStorage() => ref Unsafe.As(ref m_result); - private static Continuation? UnwindToPossibleHandler(Continuation? continuation) + private static unsafe Continuation? UnwindToPossibleHandler(Continuation? continuation, Exception ex) { while (true) { + if (continuation != null && continuation.ResumeInfo != null && continuation.ResumeInfo->DiagnosticIP != null) +#if !NATIVEAOT + AddContinuationToExInternal(continuation.ResumeInfo->DiagnosticIP, ex); +#else + { + IntPtr ip = (IntPtr)continuation.ResumeInfo->DiagnosticIP; + int flags = (int)RhEHFrameType.RH_EH_RUNTIME_ASYNC_FRAME; + IntPtr pAppendStackFrame = (IntPtr)InternalCalls.RhpGetClasslibFunctionFromCodeAddress(ip, + ClassLibFunctionId.AppendExceptionStackFrame); + + if (pAppendStackFrame != IntPtr.Zero) + { + try + { + ((delegate*)pAppendStackFrame)(ex, ip, flags); + } + catch + { + // disallow all exceptions leaking out of callbacks + } + } + } + +#endif if (continuation == null || (continuation.Flags & ContinuationFlags.HasException) != 0) return continuation; @@ -767,12 +805,14 @@ private static void CaptureContinuationContext(ref object continuationContext, r flags |= ContinuationFlags.ContinueOnThreadPool; } + [StackTraceHidden] internal static T CompletedTaskResult(Task task) { TaskAwaiter.ValidateEnd(task); return task.ResultOnSuccess; } + [StackTraceHidden] internal static void CompletedTask(Task task) { TaskAwaiter.ValidateEnd(task); diff --git a/src/coreclr/inc/readytorun.h b/src/coreclr/inc/readytorun.h index db88393b101576..230ef919346560 100644 --- a/src/coreclr/inc/readytorun.h +++ b/src/coreclr/inc/readytorun.h @@ -336,6 +336,7 @@ enum ReadyToRunHelper READYTORUN_HELPER_FailFast = 0x24, READYTORUN_HELPER_ThrowNullRef = 0x25, READYTORUN_HELPER_ThrowDivZero = 0x26, + READYTORUN_HELPER_ThrowExact = 0x27, // Write barriers READYTORUN_HELPER_WriteBarrier = 0x30, diff --git a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp index 5762e817a88904..c2f8ed7eabdfbe 100644 --- a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp +++ b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp @@ -44,6 +44,7 @@ EXTERN_C CODE_LOCATION RhpCallFilterFunclet2; EXTERN_C CODE_LOCATION RhpThrowEx2; EXTERN_C CODE_LOCATION RhpThrowHwEx2; EXTERN_C CODE_LOCATION RhpRethrow2; +EXTERN_C CODE_LOCATION RhpThrowExact2; #endif // !defined(FEATURE_PORTABLE_HELPERS) // Addresses of functions in the DAC won't match their runtime counterparts so we @@ -2241,7 +2242,8 @@ StackFrameIterator::ReturnAddressCategory StackFrameIterator::CategorizeUnadjust if (EQUALS_RETURN_ADDRESS(returnAddress, RhpThrowEx2) || EQUALS_RETURN_ADDRESS(returnAddress, RhpThrowHwEx2) || - EQUALS_RETURN_ADDRESS(returnAddress, RhpRethrow2)) + EQUALS_RETURN_ADDRESS(returnAddress, RhpRethrow2) || + EQUALS_RETURN_ADDRESS(returnAddress, RhpThrowExact2)) { return InThrowSiteThunk; } diff --git a/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.S b/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.S index d2bce874cecaca..1578a4cdcba41b 100644 --- a/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.S +++ b/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.S @@ -78,6 +78,24 @@ ALTERNATE_ENTRY RhpThrowHwEx2 NESTED_END RhpThrowHwEx, _TEXT +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// RhpThrowExact +// +// SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +// +// INPUT: RDI: exception object +// +// OUTPUT: +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler + + mov r8d, 4 // r8d = ExKind.RethrowFlag + jmp LOCAL_LABEL(RhpThrowExImpl) + +NESTED_END RhpThrowExact, _TEXT + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // RhpThrowEx @@ -89,6 +107,10 @@ NESTED_END RhpThrowHwEx, _TEXT ////////////////////////////////////////////////////////////////////////////////////////////////////////////// NESTED_ENTRY RhpThrowEx, _TEXT, NoHandler + mov r8d, 1 // r8d = ExKind.Throw + +LOCAL_LABEL(RhpThrowExImpl): + STACKSIZEOF_ExInfo = ((SIZEOF__ExInfo + 15) & (~ 15)) rsp_offsetof_Context = STACKSIZEOF_ExInfo @@ -136,7 +158,7 @@ NESTED_ENTRY RhpThrowEx, _TEXT, NoHandler mov [rsi + OFFSETOF__ExInfo__m_exception], rdx // init the exception object to null mov byte ptr [rsi + OFFSETOF__ExInfo__m_passNumber], 1 // init to the first pass mov dword ptr [rsi + OFFSETOF__ExInfo__m_idxCurClause], 0xFFFFFFFF - mov byte ptr [rsi + OFFSETOF__ExInfo__m_kind], 1 // ExKind.Throw + mov byte ptr [rsi + OFFSETOF__ExInfo__m_kind], r8b // ExKind (from r8b) // link the ExInfo into the thread's ExInfo chain mov rdx, [rax + OFFSETOF__Thread__m_pExInfoStackHead] @@ -152,6 +174,7 @@ NESTED_ENTRY RhpThrowEx, _TEXT, NoHandler call EXTERNAL_C_FUNC(RhThrowEx) ALTERNATE_ENTRY RhpThrowEx2 +ALTERNATE_ENTRY RhpThrowExact2 // no return int 3 diff --git a/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.asm b/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.asm index 0e63c8808439fd..05b192a796d5ec 100644 --- a/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.asm +++ b/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.asm @@ -105,6 +105,24 @@ ALTERNATE_ENTRY RhpThrowHwEx2 NESTED_END RhpThrowHwEx, _TEXT +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; RhpThrowExact +;; +;; SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +;; +;; INPUT: RCX: exception object +;; +;; OUTPUT: +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +NESTED_ENTRY RhpThrowExact, _TEXT + + mov r9d, 4 ;; r9d = ExKind.RethrowFlag + jmp RhpThrowExImpl + +NESTED_END RhpThrowExact, _TEXT + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; RhpThrowEx @@ -116,6 +134,10 @@ NESTED_END RhpThrowHwEx, _TEXT ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; NESTED_ENTRY RhpThrowEx, _TEXT + mov r9d, 1 ;; r9d = ExKind.Throw + +ALTERNATE_ENTRY RhpThrowExImpl + SIZEOF_XmmSaves equ SIZEOF__PAL_LIMITED_CONTEXT - OFFSETOF__PAL_LIMITED_CONTEXT__Xmm6 STACKSIZEOF_ExInfo equ ((SIZEOF__ExInfo + 15) AND (NOT 15)) @@ -172,7 +194,7 @@ NESTED_ENTRY RhpThrowEx, _TEXT ;; address could have been hijacked when we were in that C# code and we must remove the hijack and ;; reflect the correct return address in our exception context record. The other throw helpers don't ;; need this because they cannot be tail-called from C#. - INLINE_THREAD_UNHIJACK rax, r9, rdx ;; trashes R9, RDX + INLINE_THREAD_UNHIJACK rax, r10, rdx ;; trashes R10, RDX (use r10 instead of r9 to preserve ExKind) mov rdx, [rbx] ;; rdx <- return address mov [rsp + rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__IP], rdx ;; set 'faulting' IP after unhijack @@ -181,7 +203,7 @@ NESTED_ENTRY RhpThrowEx, _TEXT mov [rdx + OFFSETOF__ExInfo__m_exception], r8 ;; init the exception object to null mov byte ptr [rdx + OFFSETOF__ExInfo__m_passNumber], 1 ;; init to the first pass mov dword ptr [rdx + OFFSETOF__ExInfo__m_idxCurClause], 0FFFFFFFFh - mov byte ptr [rdx + OFFSETOF__ExInfo__m_kind], 1 ;; ExKind.Throw + mov byte ptr [rdx + OFFSETOF__ExInfo__m_kind], r9b ;; ExKind (from r9b) ;; link the ExInfo into the thread's ExInfo chain mov r8, [rax + OFFSETOF__Thread__m_pExInfoStackHead] @@ -197,6 +219,7 @@ NESTED_ENTRY RhpThrowEx, _TEXT call RhThrowEx ALTERNATE_ENTRY RhpThrowEx2 +ALTERNATE_ENTRY RhpThrowExact2 ;; no return int 3 diff --git a/src/coreclr/nativeaot/Runtime/arm/ExceptionHandling.S b/src/coreclr/nativeaot/Runtime/arm/ExceptionHandling.S index 9be4b24b7a2305..e90606f752f0f2 100644 --- a/src/coreclr/nativeaot/Runtime/arm/ExceptionHandling.S +++ b/src/coreclr/nativeaot/Runtime/arm/ExceptionHandling.S @@ -79,6 +79,24 @@ GLOBAL_LABEL RhpThrowHwEx2 NESTED_END RhpThrowHwEx +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// RhpThrowExact +// +// SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +// +// INPUT: R0: exception object +// +// OUTPUT: +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler + + mov r5, #4 // r5 = ExKind.RethrowFlag + b LOCAL_LABEL(RhpThrowExImpl) + +NESTED_END RhpThrowExact, _TEXT + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // RhpThrowEx @@ -90,6 +108,10 @@ NESTED_END RhpThrowHwEx ////////////////////////////////////////////////////////////////////////////////////////////////////////////// NESTED_ENTRY RhpThrowEx, _TEXT, NoHandler + mov r5, #1 // r5 = ExKind.Throw + +LOCAL_LABEL(RhpThrowExImpl): + // Setup a PAL_LIMITED_CONTEXT on the stack { PROLOG_VPUSH {d8-d15} PROLOG_PUSH "{r0,lr}" // Reserve space for SP and store LR @@ -158,9 +180,9 @@ LOCAL_LABEL(NotHiJacked): str r3, [r1, #OFFSETOF__ExInfo__m_exception] // init the exception object to null mov r3, #1 strb r3, [r1, #OFFSETOF__ExInfo__m_passNumber] // init to the first pass - strb r3, [r1, #OFFSETOF__ExInfo__m_kind] + strb r5, [r1, #OFFSETOF__ExInfo__m_kind] // ExKind (from r5) mov r3, #0xFFFFFFFF - str r3, [r1, #OFFSETOF__ExInfo__m_idxCurClause] // ExKind.Throw + str r3, [r1, #OFFSETOF__ExInfo__m_idxCurClause] // link the ExInfo into the thread's ExInfo chain ldr r3, [r0, #OFFSETOF__Thread__m_pExInfoStackHead] @@ -177,6 +199,7 @@ LOCAL_LABEL(NotHiJacked): bl C_FUNC(RhThrowEx) GLOBAL_LABEL RhpThrowEx2 +GLOBAL_LABEL RhpThrowExact2 // no return EMIT_BREAKPOINT diff --git a/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.S b/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.S index 5c992615f66637..643bc934a62c0d 100644 --- a/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.S +++ b/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.S @@ -252,6 +252,23 @@ NESTED_END RhpThrowHwEx, _TEXT +// +// RhpThrowExact +// +// SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +// +// INPUT: X0: exception object +// +// OUTPUT: +// + + NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler + + mov w4, #4 // w4 = ExKind.RethrowFlag + b LOCAL_LABEL(RhpThrowExImpl) + + NESTED_END RhpThrowExact, _TEXT + // // RhpThrowEx // @@ -262,6 +279,10 @@ NESTED_ENTRY RhpThrowEx, _TEXT, NoHandler + mov w4, #1 // w4 = ExKind.Throw + +LOCAL_LABEL(RhpThrowExImpl): + ALLOC_THROW_FRAME SOFTWARE_EXCEPTION GetThreadX2 @@ -316,8 +337,7 @@ LOCAL_LABEL(NotHijacked): strb w3, [x1, #OFFSETOF__ExInfo__m_passNumber] // pExInfo->m_passNumber = 1 mov w3, #0xFFFFFFFF str w3, [x1, #OFFSETOF__ExInfo__m_idxCurClause] // pExInfo->m_idxCurClause = MaxTryRegionIdx - mov w3, #1 - strb w3, [x1, #OFFSETOF__ExInfo__m_kind] // pExInfo->m_kind = ExKind.Throw + strb w4, [x1, #OFFSETOF__ExInfo__m_kind] // pExInfo->m_kind = ExKind (from w4) // link the ExInfo into the thread's ExInfo chain ldr x3, [x2, #OFFSETOF__Thread__m_pExInfoStackHead] @@ -333,12 +353,12 @@ LOCAL_LABEL(NotHijacked): bl C_FUNC(RhThrowEx) ALTERNATE_ENTRY RhpThrowEx2 + ALTERNATE_ENTRY RhpThrowExact2 // no return EMIT_BREAKPOINT NESTED_END RhpThrowEx, _TEXT - // // void FASTCALL RhpRethrow() // diff --git a/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.asm b/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.asm index 15fe87b22fc358..13824b07f4d4e9 100644 --- a/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.asm +++ b/src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.asm @@ -250,6 +250,24 @@ NESTED_END RhpThrowHwEx +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; RhpThrowExact +;; +;; SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +;; +;; INPUT: X0: exception object +;; +;; OUTPUT: +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + NESTED_ENTRY RhpThrowExact + + mov w4, #4 ;; w4 = ExKind.RethrowFlag + b RhpThrowExImpl + + NESTED_END RhpThrowExact + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; RhpThrowEx @@ -261,6 +279,10 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; NESTED_ENTRY RhpThrowEx + mov w4, #1 ;; w4 = ExKind.Throw + + ALTERNATE_ENTRY RhpThrowExImpl + ALLOC_THROW_FRAME SOFTWARE_EXCEPTION ;; x2 = GetThread(), TRASHES x1 @@ -317,8 +339,7 @@ NotHijacked strb w3, [x1, #OFFSETOF__ExInfo__m_passNumber] ;; pExInfo->m_passNumber = 1 mov w3, #0xFFFFFFFF str w3, [x1, #OFFSETOF__ExInfo__m_idxCurClause] ;; pExInfo->m_idxCurClause = MaxTryRegionIdx - mov w3, #1 - strb w3, [x1, #OFFSETOF__ExInfo__m_kind] ;; pExInfo->m_kind = ExKind.Throw + strb w4, [x1, #OFFSETOF__ExInfo__m_kind] ;; pExInfo->m_kind = ExKind (from w4) ;; link the ExInfo into the thread's ExInfo chain ldr x3, [x2, #OFFSETOF__Thread__m_pExInfoStackHead] @@ -334,6 +355,7 @@ NotHijacked bl RhThrowEx ALTERNATE_ENTRY RhpThrowEx2 + ALTERNATE_ENTRY RhpThrowExact2 ;; no return EMIT_BREAKPOINT diff --git a/src/coreclr/nativeaot/Runtime/i386/ExceptionHandling.asm b/src/coreclr/nativeaot/Runtime/i386/ExceptionHandling.asm index 810058235b7d32..35a3aecb3e91f6 100644 --- a/src/coreclr/nativeaot/Runtime/i386/ExceptionHandling.asm +++ b/src/coreclr/nativeaot/Runtime/i386/ExceptionHandling.asm @@ -89,6 +89,24 @@ ALTERNATE_ENTRY _RhpThrowHwEx2 FASTCALL_ENDFUNC +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; RhpThrowExact +;; +;; SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +;; +;; INPUT: ECX: exception object +;; +;; OUTPUT: +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +FASTCALL_FUNC RhpThrowExact, 4 + + mov edi, 4 ;; edi = ExKind.RethrowFlag + jmp RhpThrowExImpl + +FASTCALL_ENDFUNC + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; RhpThrowEx @@ -100,6 +118,10 @@ FASTCALL_ENDFUNC ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; FASTCALL_FUNC RhpThrowEx, 4 + mov edi, 1 ;; edi = ExKind.Throw + +ALTERNATE_ENTRY RhpThrowExImpl + esp_offsetof_ExInfo textequ %0 esp_offsetof_Context textequ %SIZEOF__ExInfo @@ -144,7 +166,7 @@ FASTCALL_FUNC RhpThrowEx, 4 mov [edx + OFFSETOF__ExInfo__m_exception], esi ;; init the exception object to null mov byte ptr [edx + OFFSETOF__ExInfo__m_passNumber], 1 ;; init to the first pass mov dword ptr [edx + OFFSETOF__ExInfo__m_idxCurClause], 0FFFFFFFFh - mov byte ptr [edx + OFFSETOF__ExInfo__m_kind], 1 ;; ExKind.Throw + mov byte ptr [edx + OFFSETOF__ExInfo__m_kind], dil ;; ExKind (from edi/dil) ;; link the ExInfo into the thread's ExInfo chain mov ebx, [eax + OFFSETOF__Thread__m_pExInfoStackHead] @@ -160,6 +182,7 @@ FASTCALL_FUNC RhpThrowEx, 4 call RhThrowEx ALTERNATE_ENTRY _RhpThrowEx2 +ALTERNATE_ENTRY _RhpThrowExact2 ;; no return int 3 diff --git a/src/coreclr/nativeaot/Runtime/loongarch64/ExceptionHandling.S b/src/coreclr/nativeaot/Runtime/loongarch64/ExceptionHandling.S index 93f2afd3ea786b..46a72f1ed06a5c 100644 --- a/src/coreclr/nativeaot/Runtime/loongarch64/ExceptionHandling.S +++ b/src/coreclr/nativeaot/Runtime/loongarch64/ExceptionHandling.S @@ -268,6 +268,23 @@ NESTED_END RhpThrowHwEx, _TEXT +// +// RhpThrowExact +// +// SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +// +// INPUT: a0: exception object +// +// OUTPUT: +// + + NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler + + ori $a4, $zero, 4 // a4 = ExKind.RethrowFlag + b LOCAL_LABEL(RhpThrowExImpl) + + NESTED_END RhpThrowExact, _TEXT + // // RhpThrowEx // @@ -278,6 +295,10 @@ NESTED_ENTRY RhpThrowEx, _TEXT, NoHandler + ori $a4, $zero, 1 // a4 = ExKind.Throw + +LOCAL_LABEL(RhpThrowExImpl): + ALLOC_THROW_FRAME SOFTWARE_EXCEPTION GetThreadA2 @@ -331,8 +352,7 @@ LOCAL_LABEL(NotHijacked): st.b $a3, $a1, OFFSETOF__ExInfo__m_passNumber // pExInfo->m_passNumber = 1 addi.w $a3, $zero, -1 st.w $a3, $a1, OFFSETOF__ExInfo__m_idxCurClause // pExInfo->m_idxCurClause = MaxTryRegionIdx - ori $a3, $zero, 1 - st.b $a3, $a1, OFFSETOF__ExInfo__m_kind // pExInfo->m_kind = ExKind.Throw + st.b $a4, $a1, OFFSETOF__ExInfo__m_kind // pExInfo->m_kind = ExKind (from a4) // link the ExInfo into the thread's ExInfo chain ld.d $a3, $a2, OFFSETOF__Thread__m_pExInfoStackHead @@ -348,11 +368,12 @@ LOCAL_LABEL(NotHijacked): bl C_FUNC(RhThrowEx) ALTERNATE_ENTRY RhpThrowEx2 + ALTERNATE_ENTRY RhpThrowExact2 // no return EMIT_BREAKPOINT NESTED_END RhpThrowEx, _TEXT - + NESTED_END RhpThrowExact, _TEXT // // void FASTCALL RhpRethrow() diff --git a/src/coreclr/nativeaot/Runtime/riscv64/ExceptionHandling.S b/src/coreclr/nativeaot/Runtime/riscv64/ExceptionHandling.S index 15aed3af521ea4..cd6f21e11b2a3d 100644 --- a/src/coreclr/nativeaot/Runtime/riscv64/ExceptionHandling.S +++ b/src/coreclr/nativeaot/Runtime/riscv64/ExceptionHandling.S @@ -303,6 +303,23 @@ NESTED_END RhpThrowHwEx, _TEXT +// +// RhpThrowExact +// +// SUMMARY: Similar to RhpThrowEx, except that it sets the rethrow flag +// +// INPUT: a0: exception object +// +// OUTPUT: +// + + NESTED_ENTRY RhpThrowExact, _TEXT, NoHandler + + li a4, 4 // a4 = ExKind.RethrowFlag + j LOCAL_LABEL(RhpThrowExImpl) + + NESTED_END RhpThrowExact, _TEXT + // // RhpThrowEx // @@ -313,6 +330,10 @@ NESTED_ENTRY RhpThrowEx, _TEXT, NoHandler + li a4, 1 // a4 = ExKind.Throw + +LOCAL_LABEL(RhpThrowExImpl): + ALLOC_THROW_FRAME SOFTWARE_EXCEPTION GetThreadA2 @@ -328,7 +349,7 @@ // Normal case where a valid return address location is hijacked sd a1, 0(a3) - tail LOCAL_LABEL(ClearThreadState) + j LOCAL_LABEL(ClearThreadState) LOCAL_LABEL(TailCallWasHijacked): @@ -357,8 +378,7 @@ LOCAL_LABEL(NotHijacked): sb a3, OFFSETOF__ExInfo__m_passNumber(a1) // pExInfo->m_passNumber = 1 addiw a3, zero, -1 sw a3, OFFSETOF__ExInfo__m_idxCurClause(a1) // pExInfo->m_idxCurClause = MaxTryRegionIdx - li a3, 1 - sb a3, OFFSETOF__ExInfo__m_kind(a1) // pExInfo->m_kind = ExKind.Throw + sb a4, OFFSETOF__ExInfo__m_kind(a1) // pExInfo->m_kind = ExKind (from a4) // Link the ExInfo into the thread's ExInfo chain ld a3, OFFSETOF__Thread__m_pExInfoStackHead(a2) @@ -374,6 +394,7 @@ LOCAL_LABEL(NotHijacked): call C_FUNC(RhThrowEx) ALTERNATE_ENTRY RhpThrowEx2 + ALTERNATE_ENTRY RhpThrowExact2 // No return EMIT_BREAKPOINT diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Exception.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Exception.NativeAot.cs index 9fbfcc9302c562..d1d6fff879b50a 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Exception.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Exception.NativeAot.cs @@ -95,6 +95,7 @@ private enum RhEHFrameType { RH_EH_FIRST_FRAME = 1, RH_EH_FIRST_RETHROW_FRAME = 2, + RH_EH_RUNTIME_ASYNC_FRAME = 4, } // Performance metric to count the number of exceptions thrown @@ -117,9 +118,10 @@ private static void AppendExceptionStackFrame(object exceptionObj, IntPtr IP, in bool isFirstFrame = (flags & (int)RhEHFrameType.RH_EH_FIRST_FRAME) != 0; bool isFirstRethrowFrame = (flags & (int)RhEHFrameType.RH_EH_FIRST_RETHROW_FRAME) != 0; + bool isRuntimeAsyncFrame = (flags & (int)RhEHFrameType.RH_EH_RUNTIME_ASYNC_FRAME) != 0; // track count for metrics - if (isFirstFrame && !isFirstRethrowFrame) + if ((isFirstFrame && !isFirstRethrowFrame) || isRuntimeAsyncFrame) Interlocked.Increment(ref s_exceptionCount); // When we're throwing an exception object, we first need to clear its stacktrace with two exceptions: @@ -137,7 +139,7 @@ private static void AppendExceptionStackFrame(object exceptionObj, IntPtr IP, in ex.AppendStackIP(IP, isFirstRethrowFrame); #if FEATURE_PERFTRACING - if (isFirstFrame && NativeRuntimeEventSource.Log.IsEnabled()) + if ((isFirstFrame || isRuntimeAsyncFrame) && NativeRuntimeEventSource.Log.IsEnabled()) { string typeName = !fatalOutOfMemory ? ex.GetType().ToString() : "System.OutOfMemoryException"; string message = !fatalOutOfMemory ? ex.Message : diff --git a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs index 250e316e1d37ce..da985df738d319 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs @@ -229,6 +229,7 @@ public enum ReadyToRunHelper FailFast = 0x24, ThrowNullRef = 0x25, ThrowDivZero = 0x26, + ThrowExact = 0x27, // Write barriers WriteBarrier = 0x30, diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs index 6297eab0b03814..16154f87c97d25 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs @@ -34,6 +34,8 @@ public AsyncResumptionStub(MethodDesc targetMethod, TypeDesc owningType) public override TypeSystemContext Context => _targetMethod.Context; + public MethodDesc TargetMethod => _targetMethod; + private MethodSignature InitializeSignature() { TypeDesc objectType = Context.GetWellKnownType(WellKnownType.Object); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs index 3c0f11a65c9af6..8b3a191f9862c9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs @@ -28,6 +28,9 @@ public static void GetEntryPoint(TypeSystemContext context, ReadyToRunHelper id, case ReadyToRunHelper.Rethrow: mangledName = "RhpRethrow"; break; + case ReadyToRunHelper.ThrowExact: + mangledName = "RhpThrowExact"; + break; case ReadyToRunHelper.Overflow: methodDesc = context.GetHelperEntryPoint("ThrowHelpers"u8, "ThrowOverflowException"u8); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs index ad923111add5a6..84d4fead7cce6a 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs @@ -778,8 +778,10 @@ protected void ComputeMetadata( flags |= StackTraceRecordFlags.IsHidden; if ((stackVisibility & MethodStackTraceVisibilityFlags.HasLineNumbers) != 0) flags |= StackTraceRecordFlags.HasLineNumbers; + if ((stackVisibility & MethodStackTraceVisibilityFlags.RuntimeAsync) != 0) + flags |= StackTraceRecordFlags.RuntimeAsync; - if ((stackVisibility & MethodStackTraceVisibilityFlags.HasMetadata) != 0) + if ((stackVisibility & (MethodStackTraceVisibilityFlags.HasMetadata | MethodStackTraceVisibilityFlags.RuntimeAsync)) != 0) { StackTraceRecordData record = CreateStackTraceRecord(transform, method, flags); @@ -789,6 +791,11 @@ protected void ComputeMetadata( writer.AdditionalRootRecords.Add(record.MethodName); writer.AdditionalRootRecords.Add(record.MethodSignature); writer.AdditionalRootRecords.Add(record.MethodInstantiationArgumentCollection); + // hide the resumption stubs + if ((flags & StackTraceRecordFlags.RuntimeAsync) != 0) + { + stackTraceRecords.Add(new StackTraceRecordData(method, null, null, null, null, StackTraceRecordFlags.IsHidden)); + } } else if ((stackVisibility & MethodStackTraceVisibilityFlags.IsHidden) != 0) { @@ -930,7 +937,11 @@ record ??= transformed.GetTransformedTypeReference(definition); protected StackTraceRecordData CreateStackTraceRecord(Metadata.MetadataTransform transform, MethodDesc method, StackTraceRecordFlags flags) { // In the metadata, we only represent the generic definition - MethodDesc methodToGenerateMetadataFor = method.GetTypicalMethodDefinition(); + if ((flags & StackTraceRecordFlags.RuntimeAsync) != 0) + { + method = ((ILCompiler.AsyncResumptionStub)method).TargetMethod; + } + MethodDesc methodToGenerateMetadataFor = method.GetTypicalMethodDefinition(); ConstantStringValue name = (ConstantStringValue)methodToGenerateMetadataFor.GetName(); MetadataRecord signature = transform.HandleMethodSignature(methodToGenerateMetadataFor.Signature); @@ -1349,6 +1360,7 @@ public enum StackTraceRecordFlags None = 0, IsHidden = 1, HasLineNumbers = 2, + RuntimeAsync = 4, } public readonly struct StackTraceRecordData diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/StackTraceEmissionPolicy.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/StackTraceEmissionPolicy.cs index 256d9a81c53230..cbed05a63b80a0 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/StackTraceEmissionPolicy.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/StackTraceEmissionPolicy.cs @@ -45,6 +45,11 @@ public override MethodStackTraceVisibilityFlags GetMethodVisibility(MethodDesc m result |= MethodStackTraceVisibilityFlags.IsHidden; } + if (method is ILCompiler.AsyncResumptionStub asyncStub && asyncStub.TargetMethod.IsAsyncVariant()) + { + result |= MethodStackTraceVisibilityFlags.RuntimeAsync; + } + return method.GetTypicalMethodDefinition() is Internal.TypeSystem.Ecma.EcmaMethod ? result | MethodStackTraceVisibilityFlags.HasMetadata : result; @@ -57,5 +62,6 @@ public enum MethodStackTraceVisibilityFlags HasMetadata = 0x1, IsHidden = 0x2, HasLineNumbers = 0x4, + RuntimeAsync = 0x8, } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index ad04e7f202c1be..addb3a67c196f2 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -987,6 +987,9 @@ private ISymbolNode GetHelperFtnUncached(CorInfoHelpFunc ftnNum) case CorInfoHelpFunc.CORINFO_HELP_RETHROW: id = ReadyToRunHelper.Rethrow; break; + case CorInfoHelpFunc.CORINFO_HELP_THROWEXACT: + id = ReadyToRunHelper.ThrowExact; + break; case CorInfoHelpFunc.CORINFO_HELP_OVERFLOW: id = ReadyToRunHelper.Overflow; break; diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs index 6cf54e7854c1d5..1b5eb5d6fc0bf7 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs @@ -1639,6 +1639,10 @@ private void ParseHelper(StringBuilder builder) builder.Append("RETHROW"); break; + case ReadyToRunHelper.ThrowExact: + builder.Append("THROW_EXACT"); + break; + case ReadyToRunHelper.Overflow: builder.Append("OVERFLOW"); break; diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs index 4f705b6453141a..381b6d0e9b03ba 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs @@ -467,12 +467,14 @@ private ISymbolNode GetHelperFtnUncached(CorInfoHelpFunc ftnNum) switch (ftnNum) { case CorInfoHelpFunc.CORINFO_HELP_THROW: - case CorInfoHelpFunc.CORINFO_HELP_THROWEXACT: // TODO: (async): THROWEXACT id = ReadyToRunHelper.Throw; break; case CorInfoHelpFunc.CORINFO_HELP_RETHROW: id = ReadyToRunHelper.Rethrow; break; + case CorInfoHelpFunc.CORINFO_HELP_THROWEXACT: + id = ReadyToRunHelper.ThrowExact; + break; case CorInfoHelpFunc.CORINFO_HELP_USER_BREAKPOINT: id = ReadyToRunHelper.DebugBreak; break; diff --git a/src/coreclr/vm/clrex.h b/src/coreclr/vm/clrex.h index 353804cb71d196..03353f9b0d949a 100644 --- a/src/coreclr/vm/clrex.h +++ b/src/coreclr/vm/clrex.h @@ -30,6 +30,7 @@ enum StackTraceElementFlags // Set if the element references a method that needs a keep alive object STEF_KEEPALIVE = 0x0004, + STEF_CONTINUATION = 0x0008, }; // This struct is used by SOS in the diagnostic repo. diff --git a/src/coreclr/vm/debugdebugger.cpp b/src/coreclr/vm/debugdebugger.cpp index 1d09d0d1a6b797..cde08139faa938 100644 --- a/src/coreclr/vm/debugdebugger.cpp +++ b/src/coreclr/vm/debugdebugger.cpp @@ -284,6 +284,46 @@ static void GetStackFrames(DebugStackTrace::GetStackFramesData *pData) } } +extern "C" void QCALLTYPE AsyncHelpers_AddContinuationToExInternal( + void* diagnosticIP, + QCall::ObjectHandleOnStack exception) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + GCX_COOP(); + + struct + { + OBJECTREF pException; + } gc{}; + gc.pException = NULL; + GCPROTECT_BEGIN(gc); + gc.pException = (OBJECTREF)exception.Get(); + + _ASSERTE(gc.pException != NULL); + + // extract the information from the continuation object + // and populate the exception object + // get the state + OBJECTHANDLE handle = AppDomain::GetCurrentDomain()->CreateHandle(gc.pException); + EECodeInfo codeInfo((PCODE)diagnosticIP); + if (codeInfo.IsValid()) + { + MethodDesc* methodDesc = codeInfo.GetMethodDesc(); + StackTraceInfo::AppendElement( + handle, + (UINT_PTR)diagnosticIP, + NULL, + methodDesc, + NULL); + } + + GCPROTECT_END(); + END_QCALL; +} + extern "C" void QCALLTYPE StackTrace_GetStackFramesInternal( QCall::ObjectHandleOnStack stackFrameHelper, BOOL fNeedFileInfo, @@ -951,25 +991,37 @@ void DebugStackTrace::GetStackFramesFromException(OBJECTREF * e, // push frames and the method body is therefore non-contiguous. // Currently such methods always return an IP of 0, so they're easy // to spot. - DWORD dwNativeOffset; - + DWORD dwNativeOffset = 0; UINT_PTR ip = cur.ip; -#if defined(DACCESS_COMPILE) && defined(TARGET_AMD64) - // Compensate for a bug in the old EH that for a frame that faulted - // has the ip pointing to an address before the faulting instruction - if ((i == 0) && ((cur.flags & STEF_IP_ADJUSTED) == 0)) + if (cur.flags & STEF_CONTINUATION) { - ip -= 1; - } -#endif // DACCESS_COMPILE && TARGET_AMD64 - if (ip) - { - EECodeInfo codeInfo(ip); - dwNativeOffset = codeInfo.GetRelOffset(); + EECodeInfo codeInfo((PCODE)ip); + if (codeInfo.IsValid()) + { + PCODE startAddress = codeInfo.GetStartAddress(); + dwNativeOffset = (DWORD)(ip - startAddress); + } } + else { - dwNativeOffset = 0; +#if defined(DACCESS_COMPILE) && defined(TARGET_AMD64) + // Compensate for a bug in the old EH that for a frame that faulted + // has the ip pointing to an address before the faulting instruction + if ((i == 0) && ((cur.flags & STEF_IP_ADJUSTED) == 0)) + { + ip -= 1; + } +#endif // DACCESS_COMPILE && TARGET_AMD64 + if (ip) + { + EECodeInfo codeInfo(ip); + dwNativeOffset = codeInfo.GetRelOffset(); + } + else + { + dwNativeOffset = 0; + } } pData->pElements[i].InitPass1( @@ -1554,7 +1606,7 @@ void DebugStackTrace::Element::InitPass2() bool bRes = false; - bool fAdjustOffset = (this->flags & STEF_IP_ADJUSTED) == 0 && this->dwOffset > 0; + bool fAdjustOffset = (this->flags & STEF_IP_ADJUSTED) == 0 && this->dwOffset > 0 && !(this->flags & STEF_CONTINUATION); // Check the cache! uint32_t dwILOffsetFromCache; diff --git a/src/coreclr/vm/debugdebugger.h b/src/coreclr/vm/debugdebugger.h index d2bdb47c070f19..8cd17439db17cc 100644 --- a/src/coreclr/vm/debugdebugger.h +++ b/src/coreclr/vm/debugdebugger.h @@ -135,6 +135,10 @@ extern "C" void QCALLTYPE StackTrace_GetStackFramesInternal( BOOL fNeedFileInfo, QCall::ObjectHandleOnStack exception); +extern "C" void QCALLTYPE AsyncHelpers_AddContinuationToExInternal( + void* diagnosticIP, + QCall::ObjectHandleOnStack exception); + extern "C" MethodDesc* QCALLTYPE StackFrame_GetMethodDescFromNativeIP(LPVOID ip); diff --git a/src/coreclr/vm/excep.cpp b/src/coreclr/vm/excep.cpp index 7b694e634a7e31..8c5d1846b2952c 100644 --- a/src/coreclr/vm/excep.cpp +++ b/src/coreclr/vm/excep.cpp @@ -52,6 +52,7 @@ #endif // HAVE_GCCOVER #include "exinfo.h" +#include "exkind.h" //---------------------------------------------------------------------------- // @@ -3060,8 +3061,14 @@ void StackTraceInfo::AppendElement(OBJECTHANDLE hThrowable, UINT_PTR currentIP, } } + else + { + stackTraceElem.flags |= STEF_CONTINUATION; + } + #ifndef TARGET_UNIX // Watson is supported on Windows only - SetupWatsonBucket(currentIP, pCf); + if (pCf != NULL) + SetupWatsonBucket(currentIP, pCf); #endif // !TARGET_UNIX EX_TRY diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index cea6ccc4a375ef..5d1edeaf03f2c4 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -1563,7 +1563,7 @@ void NormalizeThrownObject(OBJECTREF *ppThrowable) } } -VOID DECLSPEC_NORETURN DispatchManagedException(OBJECTREF throwable, CONTEXT* pExceptionContext, EXCEPTION_RECORD* pExceptionRecord) +VOID DECLSPEC_NORETURN DispatchManagedException(OBJECTREF throwable, CONTEXT* pExceptionContext, EXCEPTION_RECORD* pExceptionRecord, ExKind exKind /* = ExKind::None */) { STATIC_CONTRACT_THROWS; STATIC_CONTRACT_GC_TRIGGERS; @@ -1591,7 +1591,7 @@ VOID DECLSPEC_NORETURN DispatchManagedException(OBJECTREF throwable, CONTEXT* pE newExceptionRecord.ExceptionRecord = NULL; } - ExInfo exInfo(pThread, &newExceptionRecord, pExceptionContext, ExKind::Throw); + ExInfo exInfo(pThread, &newExceptionRecord, pExceptionContext, (ExKind)((uint8_t)ExKind::Throw | (uint8_t)exKind)); #ifdef HOST_WINDOWS // On Windows, this enables the possibility to propagate a longjmp across managed frames. Longjmp diff --git a/src/coreclr/vm/exceptionhandling.h b/src/coreclr/vm/exceptionhandling.h index 472074fa3e5325..b2fffb0ed2cade 100644 --- a/src/coreclr/vm/exceptionhandling.h +++ b/src/coreclr/vm/exceptionhandling.h @@ -11,6 +11,7 @@ #include "eexcp.h" #include "exstatecommon.h" +#include "exkind.h" // This address lies in the NULL pointer partition of the process memory. // Accessing it will result in AV. @@ -30,7 +31,7 @@ CallDescrWorkerUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRe void NormalizeThrownObject(OBJECTREF *ppThrowable); -VOID DECLSPEC_NORETURN DispatchManagedException(OBJECTREF throwable, CONTEXT *pExceptionContext, EXCEPTION_RECORD *pExceptionRecord = NULL); +VOID DECLSPEC_NORETURN DispatchManagedException(OBJECTREF throwable, CONTEXT *pExceptionContext, EXCEPTION_RECORD *pExceptionRecord = NULL, ExKind exKind = ExKind::None); VOID DECLSPEC_NORETURN DispatchManagedException(OBJECTREF throwable); VOID DECLSPEC_NORETURN DispatchManagedException(RuntimeExceptionKind reKind); VOID DECLSPEC_NORETURN DispatchRethrownManagedException(); diff --git a/src/coreclr/vm/exinfo.cpp b/src/coreclr/vm/exinfo.cpp index 6751c4b85525b8..dab768e4754944 100644 --- a/src/coreclr/vm/exinfo.cpp +++ b/src/coreclr/vm/exinfo.cpp @@ -6,6 +6,7 @@ #include "common.h" #include "exinfo.h" +#include "exkind.h" #include "dbginterface.h" #ifdef FEATURE_EH_FUNCLETS diff --git a/src/coreclr/vm/exinfo.h b/src/coreclr/vm/exinfo.h index 26ee34c393e942..090b298047d14e 100644 --- a/src/coreclr/vm/exinfo.h +++ b/src/coreclr/vm/exinfo.h @@ -190,20 +190,6 @@ struct RhEHClause } }; -enum class ExKind : uint8_t -{ - None = 0, - Throw = 1, - HardwareFault = 2, - KindMask = 3, - - RethrowFlag = 4, - - SupersededFlag = 8, - - InstructionFaultFlag = 0x10 -}; - struct PAL_SEHException; struct LastReportedFuncletInfo diff --git a/src/coreclr/vm/exkind.h b/src/coreclr/vm/exkind.h new file mode 100644 index 00000000000000..08f3251a3e74d5 --- /dev/null +++ b/src/coreclr/vm/exkind.h @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// RuntimeExceptionKind.h +// + +#ifndef __exkind_h__ +#define __exkind_h__ + +#include + +//========================================================================== +// Identifies exception kinds. +//========================================================================== +enum class ExKind : uint8_t +{ + None = 0, + Throw = 1, + HardwareFault = 2, + KindMask = 3, + + RethrowFlag = 4, + + SupersededFlag = 8, + + InstructionFaultFlag = 0x10 +}; + +#endif // __exkind_h__ diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index 9edf5b97eb33a1..4b744575a74d7e 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -58,6 +58,7 @@ #include "excep.h" #endif #include "exinfo.h" +#include "exkind.h" #include "arraynative.inl" using std::isfinite; @@ -890,7 +891,6 @@ HCIMPL1(void, IL_ThrowExact, Object* obj) ResetCurrentContext(); OBJECTREF oref = ObjectToOBJECTREF(obj); - GetThread()->GetExceptionState()->SetRaisingForeignException(); Thread *pThread = GetThread(); @@ -905,7 +905,7 @@ HCIMPL1(void, IL_ThrowExact, Object* obj) FC_CAN_TRIGGER_GC(); #ifdef FEATURE_EH_FUNCLETS - DispatchManagedException(oref, exceptionFrame.GetContext()); + DispatchManagedException(oref, exceptionFrame.GetContext(), NULL, ExKind::RethrowFlag); #elif defined(TARGET_X86) INSTALL_MANAGED_EXCEPTION_DISPATCHER; INSTALL_UNWIND_AND_CONTINUE_HANDLER; diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 4b38d25d77fd73..58943d6aae2359 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -186,6 +186,7 @@ static const Entry s_QCall[] = DllImportEntry(RuntimeFieldHandle_GetFieldDataReference) DllImportEntry(UnsafeAccessors_ResolveGenericParamToTypeHandle) DllImportEntry(StackTrace_GetStackFramesInternal) + DllImportEntry(AsyncHelpers_AddContinuationToExInternal) DllImportEntry(StackFrame_GetMethodDescFromNativeIP) DllImportEntry(ModuleBuilder_GetStringConstant) DllImportEntry(ModuleBuilder_GetTypeRef) diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index a55e2adab5bfb4..d090b6b2057ff1 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs @@ -442,6 +442,8 @@ public static bool IsSubstAvailable } } + public static bool IsRuntimeAsyncSupported => !IsCoreClrInterpreter && !IsMonoRuntime && !IsMonoAOT && !IsMonoInterpreter; + private static Version GetICUVersion() { int version = 0; diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/AsyncAssembly.csproj b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/AsyncAssembly.csproj new file mode 100644 index 00000000000000..da2ed04bb7294e --- /dev/null +++ b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/AsyncAssembly.csproj @@ -0,0 +1,22 @@ + + + + net10.0 + Library + enable + enable + true + true + $(Features);runtime-async=on + $(NoWarn);SYSLIB5007 + + + + + + + + + + + diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/Program.cs b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/Program.cs new file mode 100644 index 00000000000000..1ee0d9d8f94646 --- /dev/null +++ b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncAssembly/Program.cs @@ -0,0 +1,129 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +public class Program +{ + // v2 -> v1 -> v2 -> v1 + public static async Task Foo() + { + await Task.Yield(); + try + { +#line 12 "Program.cs" + await V1Methods.Test0(Foo1); + } + catch (NotImplementedException) + { + throw; + } + } + + private static async Task Foo1(int i) + { + await Task.Yield(); + try + { +#line 26 "Program.cs" + await Foo2(i); + return i * 2; + } + catch (NotImplementedException) + { + throw; + } + } + + private static async Task Foo2(int i) + { + try + { + await Task.Yield(); + await Task.Yield(); + await Task.Yield(); + for (int j = i; j > 0; j--) + { + await Foo2(j - 1); + } + if (i == 0) + await V1Methods.Test2(i); + } + finally + { + Console.WriteLine($"In finally block of Foo2 with {i}"); +#line 53 "Program.cs" + throw new NotImplementedException("Not Found from Foo2"); + } + } + + public static async Task Bar(int i) + { + if (i == 0) +#line 61 "Program.cs" + throw new Exception("Exception from Bar"); +#line 63 "Program.cs" + await Bar(i - 1); + } + + public static async Task Baz() + { +#line 69 "Program.cs" + throw new Exception("Exception from Baz"); + } + + public static async Task Qux(int i) + { + await Task.Yield(); + try + { + return 9 / i; + } + catch (DivideByZeroException) + { +#line 82 "Program.cs" + throw new DivideByZeroException("Exception from Qux"); + } + } + + // also v2 v1 chaining but this time we don't have finally + public static async Task Quux() + { + await Task.Yield(); + try + { +#line 93 "Program.cs" + await V1Methods.Test0(Quux1); + } + catch (NotImplementedException) + { + throw; + } + } + + private static async Task Quux1(int i) + { + try + { + await Task.Yield(); +#line 107 "Program.cs" + throw new NotImplementedException("Not Found from Quux1"); + } + catch (NotImplementedException) + { + throw; + } + } + + public static async Task Quuux() + { + var task = Quuux2(); + Console.WriteLine("hello from Quuux"); +#line 120 "Program.cs" + return await task; + } + + private static async Task Quuux2() + { + await Task.Yield(); +#line 127 "Program.cs" + throw new Exception("Exception from Quuux2"); + } +} diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/AsyncV1Assembly.csproj b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/AsyncV1Assembly.csproj new file mode 100644 index 00000000000000..4fc55cd39a70be --- /dev/null +++ b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/AsyncV1Assembly.csproj @@ -0,0 +1,14 @@ + + + + Library + net10.0 + enable + enable + + + + + + + diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/Program.cs b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/Program.cs new file mode 100644 index 00000000000000..73171d6bbe64a2 --- /dev/null +++ b/src/libraries/System.Diagnostics.StackTrace/tests/AsyncV1Assembly/Program.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +public static class V1Methods +{ + public static async Task Test0(Func method) + { +#line 8 "Program.cs" + await Test1(method); + await Task.Yield(); + } + + public static async Task Test1(Func method) + { + try + { +#line 17 "Program.cs" + await method(3); + } + catch (Exception ex) when (ex.Message.Contains("404")) + { + Console.WriteLine($"Caught exception in Test1 with: {ex}"); + } + } + + public static async Task Test2(int i) + { + Console.WriteLine($"In Test2 with {i}"); + throw new NullReferenceException("Exception from Test2"); + } +} diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs b/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs index 1b2c9fb7274efd..4e8936efae9149 100644 --- a/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs +++ b/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs @@ -11,6 +11,7 @@ using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using Microsoft.DotNet.RemoteExecutor; +using System.Threading.Tasks; using Xunit; namespace System.Diagnostics @@ -406,6 +407,93 @@ public void ToString_ShowILOffset() }, regPattern).Dispose(); } + public static Dictionary MethodExceptionStrings = new() + { + { "Foo", new[] { + @"Not Found from Foo2", + @"Program\.Foo2\(Int32 i\).*Program\.cs:line 53", + @"Program\.Foo1\(Int32 i\).*Program\.cs:line 26", + @"V1Methods\.Test1\(Func`2 method\).*Program\.cs:line 17", + @"V1Methods\.Test0\(Func`2 method\).*Program\.cs:line 8", + @"Program\.Foo\(\).*Program\.cs:line 12" + }}, + { "Bar", new[] { + @"Exception from Bar", + @"Program\.Bar\(Int32 i\).*Program\.cs:line 61", + @"Program\.Bar\(Int32 i\).*Program\.cs:line 63" + }}, + { "Baz", new[] { + @"Exception from Baz", + @"Program\.Baz\(\).*Program\.cs:line 69" + }}, + { "Qux", new[] { + @"Exception from Qux", + @"Program\.Qux\(Int32 i\).*Program\.cs:line 82" + }}, + {"Quux", new[] { + @"Not Found from Quux1", + @"Program\.Quux1\(Int32 i\).*Program\.cs:line 107", + @"V1Methods\.Test1\(Func`2 method\).*Program\.cs:line 17", + @"V1Methods\.Test0\(Func`2 method\).*Program\.cs:line 8", + @"Program\.Quux\(\).*Program\.cs:line 93" + }}, + { "Quuux", new[] { + @"Exception from Quuux2", + @"Program\.Quuux2\(\).*Program\.cs:line 127", + @"Program\.Quuux\(\).*Program\.cs:line 120" + }} + }; + + public static IEnumerable Ctor_Async_TestData() + { + yield return new object[] { "Foo", null, MethodExceptionStrings["Foo"] }; + yield return new object[] { "Bar", 3, MethodExceptionStrings["Bar"] }; + yield return new object[] { "Baz", null, MethodExceptionStrings["Baz"] }; + yield return new object[] { "Qux", 0, MethodExceptionStrings["Qux"] }; + yield return new object[] { "Quux", null, MethodExceptionStrings["Quux"] }; + yield return new object[] { "Quuux", null, MethodExceptionStrings["Quuux"] }; + } + + public static class RuntimeAsyncAndRemoteExecutor + { + public static bool IsSupported => RemoteExecutor.IsSupported && PlatformDetection.IsRuntimeAsyncSupported; + } + + [ConditionalTheory(typeof(RuntimeAsyncAndRemoteExecutor), nameof(RuntimeAsyncAndRemoteExecutor.IsSupported))] + [MemberData(nameof(Ctor_Async_TestData))] + public void ToString_Async(string methodName, object arg1, string[] expectedPatterns) + { + string AssemblyName = "AsyncAssembly.dll"; + string SourceTestAssemblyPath = Path.Combine(Environment.CurrentDirectory, AssemblyName); + RemoteInvokeOptions options = new RemoteInvokeOptions(); + options.StartInfo.EnvironmentVariables["DOTNET_RuntimeAsync"] = "1"; + string joinedPatterns = string.Join("|", expectedPatterns); + string arg1String = arg1?.ToString() ?? "null"; + Func method = async (asmPath, methodName, arg1String, joinedPatterns) => + { + var asm = Assembly.LoadFrom(asmPath); + try + { + object arg1 = arg1String == "null" ? null : int.Parse(arg1String, CultureInfo.InvariantCulture); + var result = asm.GetType("Program").GetMethod(methodName).Invoke(null, arg1 == null ? null : new[] { arg1 }); + if (result is Task task) + { + await task; + } + } + catch (Exception e) + { + string exceptionString = e.ToString(); + string[] patterns = joinedPatterns.Split('|'); + foreach (string pattern in patterns) + { + Assert.Matches(pattern, exceptionString); + } + } + }; + RemoteExecutor.Invoke(method, SourceTestAssemblyPath, methodName, arg1String, joinedPatterns, options).Dispose(); + } + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] private static StackTrace NoParameters() => new StackTrace(); [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/System.Diagnostics.StackTrace.Tests.csproj b/src/libraries/System.Diagnostics.StackTrace/tests/System.Diagnostics.StackTrace.Tests.csproj index 87f7d5be40d428..db1df29697f0ef 100644 --- a/src/libraries/System.Diagnostics.StackTrace/tests/System.Diagnostics.StackTrace.Tests.csproj +++ b/src/libraries/System.Diagnostics.StackTrace/tests/System.Diagnostics.StackTrace.Tests.csproj @@ -25,5 +25,6 @@ +