Skip to content

Commit bf4802d

Browse files
janvorlijkotas
andauthored
Move coreclr EH second pass to native code (#119863)
* [WIP] Move coreclr EH second pass to native code There were some GC holes discovered caused by the fact that GC can be triggered during 2nd pass of EH in-between calls to finally handlers and catch handler. After considering options, moving the 2nd pass to native code seems the most reasonable solution. * Several fixes * Reflect PR feedback * Implement rethrow * Implement new way of collided unwind detection now that the CallCatchFunclet is not called via pinvoke * Remove forced reporting of EH code from stack frame iterator, as we now cannot have that code on the stack during 2nd pass * Fix arm64 build and remove now useless stuff from stackwalk * Fix build break * Fix offsets, exception interception and MUSL build break * Alternative change with minimalistic managed code differences * Fix 32 bit offsets * Fix arm64 issue * Update src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/ExceptionHandling.cs * Add/fix some contracts * Fix another arm64 issue - need to use adjusted PC * Change the GC prevention to single frame only * Fix one new contract issue * Update GC forbid locations This is done to ensure that no GC is allowed between the scanned stack range is extended and a funclet for the current frame is called. * Fix/add comment based on review --------- Co-authored-by: Jan Kotas <[email protected]>
1 parent ea215d2 commit bf4802d

File tree

15 files changed

+538
-507
lines changed

15 files changed

+538
-507
lines changed

src/coreclr/System.Private.CoreLib/src/System/Runtime/ExceptionServices/AsmOffsets.cs

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,17 @@ class AsmOffsets
6868
public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0x132;
6969
#elif TARGET_X86
7070
public const int OFFSETOF__REGDISPLAY__m_pCurrentContext = 0x4;
71-
public const int SIZEOF__StackFrameIterator = 0x3d4;
71+
public const int SIZEOF__StackFrameIterator = 0x3d0;
7272
public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0x3c2;
73-
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0x3d0;
73+
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0x3cc;
7474
#else // TARGET_64BIT
7575
public const int OFFSETOF__REGDISPLAY__m_pCurrentContext = 0x4;
7676
#if FEATURE_INTERPRETER
77-
public const int SIZEOF__StackFrameIterator = 0xdc;
78-
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0xd8;
77+
public const int SIZEOF__StackFrameIterator = 0xd8;
78+
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0xd4;
7979
#else
80-
public const int SIZEOF__StackFrameIterator = 0xcc;
81-
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0xc8;
80+
public const int SIZEOF__StackFrameIterator = 0xc8;
81+
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0xc4;
8282
#endif
8383
public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0xba;
8484
#endif // TARGET_64BIT
@@ -139,17 +139,17 @@ class AsmOffsets
139139
public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0x12a;
140140
#elif TARGET_X86
141141
public const int OFFSETOF__REGDISPLAY__m_pCurrentContext = 0x4;
142-
public const int SIZEOF__StackFrameIterator = 0x3cc;
142+
public const int SIZEOF__StackFrameIterator = 0x3c8;
143143
public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0x3ba;
144-
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0x3c8;
144+
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0x3c4;
145145
#else // TARGET_64BIT
146146
public const int OFFSETOF__REGDISPLAY__m_pCurrentContext = 0x4;
147147
#if FEATURE_INTERPRETER
148-
public const int SIZEOF__StackFrameIterator = 0xd4;
149-
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0xd0;
148+
public const int SIZEOF__StackFrameIterator = 0xd0;
149+
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0xcc;
150150
#else
151-
public const int SIZEOF__StackFrameIterator = 0xc4;
152-
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0xc0;
151+
public const int SIZEOF__StackFrameIterator = 0xc0;
152+
public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0xbc;
153153
#endif
154154
public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0xb2;
155155
#endif // TARGET_64BIT
@@ -217,6 +217,18 @@ class AsmOffsets
217217
public const int OFFSETOF__ExInfo__m_idxCurClause = 0xbc;
218218
public const int OFFSETOF__ExInfo__m_frameIter = 0xc0;
219219
public const int OFFSETOF__ExInfo__m_notifyDebuggerSP = OFFSETOF__ExInfo__m_frameIter + SIZEOF__StackFrameIterator;
220+
public const int OFFSETOF__ExInfo__m_pCatchHandler = OFFSETOF__ExInfo__m_frameIter + SIZEOF__StackFrameIterator + 0x48;
221+
public const int OFFSETOF__ExInfo__m_handlingFrameSP = OFFSETOF__ExInfo__m_frameIter + SIZEOF__StackFrameIterator + 0x50;
222+
223+
#if TARGET_ARM64
224+
public const int OFFSETOF__ExInfo__m_handlingFramePC = OFFSETOF__ExInfo__m_frameIter + SIZEOF__StackFrameIterator + 0x58;
225+
#endif
226+
227+
#if TARGET_UNIX
228+
public const int OFFSETOF__ExInfo__m_pReversePInvokePropagationCallback = OFFSETOF__ExInfo__m_frameIter + SIZEOF__StackFrameIterator + 0x30;
229+
public const int OFFSETOF__ExInfo__m_pReversePInvokePropagationContext = OFFSETOF__ExInfo__m_frameIter + SIZEOF__StackFrameIterator + 0x38;
230+
#endif
231+
220232
#else // TARGET_64BIT
221233
public const int SIZEOF__EHEnum = 0x10;
222234
public const int OFFSETOF__StackFrameIterator__m_pRegDisplay = 0x14;
@@ -228,6 +240,14 @@ class AsmOffsets
228240
public const int OFFSETOF__ExInfo__m_idxCurClause = 0x68;
229241
public const int OFFSETOF__ExInfo__m_frameIter = 0x6c;
230242
public const int OFFSETOF__ExInfo__m_notifyDebuggerSP = OFFSETOF__ExInfo__m_frameIter + SIZEOF__StackFrameIterator;
243+
public const int OFFSETOF__ExInfo__m_pCatchHandler = OFFSETOF__ExInfo__m_frameIter + SIZEOF__StackFrameIterator + 0x2c;
244+
public const int OFFSETOF__ExInfo__m_handlingFrameSP = OFFSETOF__ExInfo__m_frameIter + SIZEOF__StackFrameIterator + 0x30;
245+
246+
#if TARGET_UNIX
247+
public const int OFFSETOF__ExInfo__m_pReversePInvokePropagationCallback = OFFSETOF__ExInfo__m_frameIter + SIZEOF__StackFrameIterator + 0x38;
248+
public const int OFFSETOF__ExInfo__m_pReversePInvokePropagationContext = OFFSETOF__ExInfo__m_frameIter + SIZEOF__StackFrameIterator + 0x3c;
249+
#endif
250+
231251
#endif // TARGET_64BIT
232252

233253
#if __cplusplus
@@ -268,8 +288,13 @@ class AsmOffsets
268288
static_assert(offsetof(ExInfo, m_idxCurClause) == OFFSETOF__ExInfo__m_idxCurClause);
269289
static_assert(offsetof(ExInfo, m_frameIter) == OFFSETOF__ExInfo__m_frameIter);
270290
static_assert(offsetof(ExInfo, m_notifyDebuggerSP) == OFFSETOF__ExInfo__m_notifyDebuggerSP);
291+
static_assert(offsetof(ExInfo, m_pCatchHandler) == OFFSETOF__ExInfo__m_pCatchHandler);
292+
static_assert(offsetof(ExInfo, m_handlingFrameSP) == OFFSETOF__ExInfo__m_handlingFrameSP);
293+
#if TARGET_ARM64
294+
static_assert(offsetof(ExInfo, m_handlingFramePC) == OFFSETOF__ExInfo__m_handlingFramePC);
271295
#endif
272296

297+
#endif
273298
}
274299
#if __cplusplus
275300
;

src/coreclr/System.Private.CoreLib/src/System/Runtime/ExceptionServices/InternalCalls.cs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,6 @@ internal static partial class InternalCalls
2020
[return: MarshalAs(UnmanagedType.U1)]
2121
internal static unsafe partial bool RhpSfiNext(ref StackFrameIterator pThis, uint* uExCollideClauseIdx, bool* fUnwoundReversePInvoke, bool* fIsExceptionIntercepted);
2222

23-
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ResumeAtInterceptionLocation")]
24-
internal static unsafe partial void ResumeAtInterceptionLocation(void* pvRegDisplay);
25-
26-
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "CallCatchFunclet")]
27-
internal static unsafe partial IntPtr RhpCallCatchFunclet(
28-
ObjectHandleOnStack exceptionObj, byte* pHandlerIP, void* pvRegDisplay, EH.ExInfo* exInfo);
29-
30-
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "CallFinallyFunclet")]
31-
internal static unsafe partial void RhpCallFinallyFunclet(byte* pHandlerIP, void* pvRegDisplay, EH.ExInfo* exInfo);
32-
3323
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "CallFilterFunclet")]
3424
[return: MarshalAs(UnmanagedType.U1)]
3525
internal static unsafe partial bool RhpCallFilterFunclet(

src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/ExceptionHandling.cs

Lines changed: 51 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,30 @@ internal object ThrownException
549549

550550
[FieldOffset(AsmOffsets.OFFSETOF__ExInfo__m_notifyDebuggerSP)]
551551
internal volatile UIntPtr _notifyDebuggerSP;
552+
#if !NATIVEAOT
553+
[FieldOffset(AsmOffsets.OFFSETOF__ExInfo__m_pCatchHandler)]
554+
internal volatile byte* _pCatchHandler;
555+
556+
[FieldOffset(AsmOffsets.OFFSETOF__ExInfo__m_handlingFrameSP)]
557+
internal volatile UIntPtr _handlingFrameSP;
558+
559+
#if TARGET_ARM64
560+
// On ARM64, two frames can have the same SP, when a leaf function
561+
// doesn't use any stack. So to distinguish between the caller frame
562+
// and the leaf one, we also need to know the PC of the handling frame.
563+
[FieldOffset(AsmOffsets.OFFSETOF__ExInfo__m_handlingFramePC)]
564+
internal volatile byte* _handlingFramePC;
565+
#endif
566+
567+
#if TARGET_UNIX
568+
[FieldOffset(AsmOffsets.OFFSETOF__ExInfo__m_pReversePInvokePropagationCallback)]
569+
internal volatile IntPtr _pReversePInvokePropagationCallback;
570+
571+
[FieldOffset(AsmOffsets.OFFSETOF__ExInfo__m_pReversePInvokePropagationContext)]
572+
internal volatile IntPtr _pReversePInvokePropagationContext;
573+
#endif // TARGET_UNIX
574+
575+
#endif // !NATIVEAOT
552576
}
553577

554578
//
@@ -630,7 +654,9 @@ public static void RhThrowHwEx(uint exceptionCode, ref ExInfo exInfo)
630654

631655
exInfo.Init(exceptionToThrow!, instructionFault);
632656
DispatchEx(ref exInfo._frameIter, ref exInfo);
657+
#if NATIVEAOT
633658
FallbackFailFast(RhFailFastReason.InternalError, null);
659+
#endif
634660
}
635661

636662
private const uint MaxTryRegionIdx = 0xFFFFFFFFu;
@@ -663,68 +689,11 @@ public static void RhThrowEx(object exceptionObj, ref ExInfo exInfo)
663689

664690
exInfo.Init(exceptionObj);
665691
DispatchEx(ref exInfo._frameIter, ref exInfo);
692+
#if NATIVEAOT
666693
FallbackFailFast(RhFailFastReason.InternalError, null);
694+
#endif
667695
}
668696

669-
#if !NATIVEAOT
670-
public static void RhUnwindAndIntercept(ref ExInfo exInfo, UIntPtr interceptStackFrameSP)
671-
{
672-
exInfo._passNumber = 2;
673-
exInfo._idxCurClause = MaxTryRegionIdx;
674-
uint startIdx = MaxTryRegionIdx;
675-
bool unwoundReversePInvoke = false;
676-
bool isExceptionIntercepted = false;
677-
bool isValid = exInfo._frameIter.Init(exInfo._pExContext, (exInfo._kind & ExKind.InstructionFaultFlag) != 0, &isExceptionIntercepted);
678-
for (; isValid && !isExceptionIntercepted && ((byte*)exInfo._frameIter.SP <= (byte*)interceptStackFrameSP); isValid = exInfo._frameIter.Next(&startIdx, &unwoundReversePInvoke, &isExceptionIntercepted))
679-
{
680-
Debug.Assert(isValid, "Unwind and intercept failed unexpectedly");
681-
DebugScanCallFrame(exInfo._passNumber, exInfo._frameIter.ControlPC, exInfo._frameIter.SP);
682-
683-
if (unwoundReversePInvoke)
684-
{
685-
// Found the native frame that called the reverse P/invoke.
686-
// It is not possible to run managed second pass handlers on a native frame.
687-
break;
688-
}
689-
690-
if (exInfo._frameIter.SP == interceptStackFrameSP)
691-
{
692-
break;
693-
}
694-
695-
InvokeSecondPass(ref exInfo, startIdx);
696-
if (isExceptionIntercepted)
697-
{
698-
Debug.Assert(false);
699-
break;
700-
}
701-
}
702-
703-
// ------------------------------------------------
704-
//
705-
// Call the interception code
706-
//
707-
// ------------------------------------------------
708-
if (unwoundReversePInvoke)
709-
{
710-
object exceptionObj = exInfo.ThrownException;
711-
fixed (EH.ExInfo* pExInfo = &exInfo)
712-
{
713-
InternalCalls.RhpCallCatchFunclet(
714-
ObjectHandleOnStack.Create(ref exceptionObj), null, exInfo._frameIter.RegisterSet, pExInfo);
715-
}
716-
}
717-
else
718-
{
719-
InternalCalls.ResumeAtInterceptionLocation(exInfo._frameIter.RegisterSet);
720-
}
721-
722-
Debug.Fail("unreachable");
723-
FallbackFailFast(RhFailFastReason.InternalError, null);
724-
}
725-
#endif // !NATIVEAOT
726-
727-
728697
#if NATIVEAOT
729698
[RuntimeExport("RhRethrow")]
730699
#endif
@@ -750,7 +719,9 @@ public static void RhRethrow(ref ExInfo activeExInfo, ref ExInfo exInfo)
750719

751720
exInfo.Init(rethrownException, ref activeExInfo);
752721
DispatchEx(ref exInfo._frameIter, ref exInfo);
722+
#if NATIVEAOT
753723
FallbackFailFast(RhFailFastReason.InternalError, null);
724+
#endif
754725
}
755726

756727
[StackTraceHidden]
@@ -870,6 +841,21 @@ private static void DispatchEx(scoped ref StackFrameIterator frameIter, ref ExIn
870841
Debug.Assert(pCatchHandler != null || pReversePInvokePropagationCallback != IntPtr.Zero || unwoundReversePInvoke || isExceptionIntercepted, "We should have a handler if we're starting the second pass");
871842
Debug.Assert(!isExceptionIntercepted || (pCatchHandler == null), "No catch handler should be returned for intercepted exceptions in the first pass");
872843

844+
#if !NATIVEAOT
845+
exInfo._pCatchHandler = pCatchHandler;
846+
exInfo._handlingFrameSP = handlingFrameSP;
847+
#if TARGET_ARM64
848+
exInfo._handlingFramePC = prevOriginalPC;
849+
#endif
850+
#if TARGET_UNIX
851+
exInfo._pReversePInvokePropagationCallback = pReversePInvokePropagationCallback;
852+
exInfo._pReversePInvokePropagationContext = pReversePInvokePropagationContext;
853+
#endif // TARGET_UNIX
854+
exInfo._idxCurClause = catchingTryRegionIdx;
855+
856+
return;
857+
858+
#else // !NATIVEAOT
873859
// ------------------------------------------------
874860
//
875861
// Second pass
@@ -881,9 +867,10 @@ private static void DispatchEx(scoped ref StackFrameIterator frameIter, ref ExIn
881867
// 'collapse' funclets which gets confused when we walk out of the dispatch code and encounter the
882868
// 'main body' without first encountering the funclet. The thunks used to invoke 2nd-pass
883869
// funclets will always toggle this mode off before invoking them.
884-
#if NATIVEAOT
870+
885871
InternalCalls.RhpSetThreadDoNotTriggerGC();
886-
#endif
872+
873+
887874
exInfo._passNumber = 2;
888875
exInfo._idxCurClause = catchingTryRegionIdx;
889876
startIdx = MaxTryRegionIdx;
@@ -903,10 +890,8 @@ private static void DispatchEx(scoped ref StackFrameIterator frameIter, ref ExIn
903890

904891
if (unwoundReversePInvoke)
905892
{
906-
#if NATIVEAOT
907893
Debug.Assert(pReversePInvokePropagationCallback != IntPtr.Zero, "Unwound to a reverse P/Invoke in the second pass. We should have a propagation handler.");
908894
Debug.Assert(frameIter.PreviousTransitionFrame != IntPtr.Zero, "Should have a transition frame for reverse P/Invoke.");
909-
#endif
910895
Debug.Assert(frameIter.SP == handlingFrameSP, "Encountered a different reverse P/Invoke frame in the second pass.");
911896
// Found the native frame that called the reverse P/invoke.
912897
// It is not possible to run managed second pass handlers on a native frame.
@@ -930,36 +915,26 @@ private static void DispatchEx(scoped ref StackFrameIterator frameIter, ref ExIn
930915
#if FEATURE_OBJCMARSHAL
931916
if (pReversePInvokePropagationCallback != IntPtr.Zero)
932917
{
933-
#if NATIVEAOT
934918
InternalCalls.RhpCallPropagateExceptionCallback(
935919
pReversePInvokePropagationContext, pReversePInvokePropagationCallback, frameIter.RegisterSet, ref exInfo, frameIter.PreviousTransitionFrame);
936920
// the helper should jump to propagation handler and not return
937-
#endif
938921
Debug.Fail("unreachable");
939922
FallbackFailFast(RhFailFastReason.InternalError, null);
940923
}
941924
#endif // FEATURE_OBJCMARSHAL
942925

943-
944926
// ------------------------------------------------
945927
//
946928
// Call the handler and resume execution
947929
//
948930
// ------------------------------------------------
949931
exInfo._idxCurClause = catchingTryRegionIdx;
950-
#if NATIVEAOT
951932
InternalCalls.RhpCallCatchFunclet(
952933
exceptionObj, pCatchHandler, frameIter.RegisterSet, ref exInfo);
953-
#else // NATIVEAOT
954-
fixed (EH.ExInfo* pExInfo = &exInfo)
955-
{
956-
InternalCalls.RhpCallCatchFunclet(
957-
ObjectHandleOnStack.Create(ref exceptionObj), pCatchHandler, frameIter.RegisterSet, pExInfo);
958-
}
959-
#endif // NATIVEAOT
960934
// currently, RhpCallCatchFunclet will resume after the catch
961935
Debug.Fail("unreachable");
962936
FallbackFailFast(RhFailFastReason.InternalError, null);
937+
#endif // !NATIVEAOT
963938
}
964939

965940
[System.Diagnostics.Conditional("DEBUG")]
@@ -1165,6 +1140,7 @@ private static bool ShouldTypedClauseCatchThisException(object exception, Method
11651140
#endif
11661141
}
11671142

1143+
#if NATIVEAOT
11681144
private static void InvokeSecondPass(ref ExInfo exInfo, uint idxStart)
11691145
{
11701146
InvokeSecondPass(ref exInfo, idxStart, MaxTryRegionIdx);
@@ -1202,11 +1178,7 @@ private static void InvokeSecondPass(ref ExInfo exInfo, uint idxStart, uint idxL
12021178

12031179
// Now, we continue skipping while the try region is identical to the one that invoked the
12041180
// previous dispatch.
1205-
if ((ehClause._tryStartOffset == lastTryStart) && (ehClause._tryEndOffset == lastTryEnd)
1206-
#if !NATIVEAOT
1207-
&& (ehClause._isSameTry)
1208-
#endif
1209-
)
1181+
if ((ehClause._tryStartOffset == lastTryStart) && (ehClause._tryEndOffset == lastTryEnd))
12101182
continue;
12111183

12121184
// We are done skipping. This is required to handle empty finally block markers that are used
@@ -1237,19 +1209,11 @@ private static void InvokeSecondPass(ref ExInfo exInfo, uint idxStart, uint idxL
12371209

12381210
byte* pFinallyHandler = ehClause._handlerAddress;
12391211
exInfo._idxCurClause = curIdx;
1240-
#if NATIVEAOT
12411212
InternalCalls.RhpCallFinallyFunclet(pFinallyHandler, exInfo._frameIter.RegisterSet);
1242-
#else // NATIVEAOT
1243-
fixed (EH.ExInfo* pExInfo = &exInfo)
1244-
{
1245-
InternalCalls.RhpCallFinallyFunclet(pFinallyHandler, exInfo._frameIter.RegisterSet, pExInfo);
1246-
}
1247-
#endif // NATIVEAOT
12481213
exInfo._idxCurClause = MaxTryRegionIdx;
12491214
}
12501215
}
12511216

1252-
#if NATIVEAOT
12531217
#pragma warning disable IDE0060
12541218
[UnmanagedCallersOnly(EntryPoint = "RhpFailFastForPInvokeExceptionPreemp")]
12551219
public static void RhpFailFastForPInvokeExceptionPreemp(IntPtr PInvokeCallsiteReturnAddr, void* pExceptionRecord, void* pContextRecord)

src/coreclr/vm/corelib.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1357,7 +1357,6 @@ DEFINE_CLASS(EH, Runtime, EH)
13571357
DEFINE_METHOD(EH, RH_THROW_EX, RhThrowEx, SM_Obj_RefExInfo_RetVoid)
13581358
DEFINE_METHOD(EH, RH_THROWHW_EX, RhThrowHwEx, SM_UInt_RefExInfo_RetVoid)
13591359
DEFINE_METHOD(EH, RH_RETHROW, RhRethrow, SM_RefExInfo_RefExInfo_RetVoid)
1360-
DEFINE_METHOD(EH, UNWIND_AND_INTERCEPT, RhUnwindAndIntercept, SM_RefExInfo_UIntPtr_RetVoid)
13611360
DEFINE_CLASS(EXCEPTIONSERVICES_INTERNALCALLS, ExceptionServices, InternalCalls)
13621361
DEFINE_CLASS(STACKFRAMEITERATOR, Runtime, StackFrameIterator)
13631362
#endif // FEATURE_EH_FUNCLETS

0 commit comments

Comments
 (0)