Skip to content

Commit dadbc7d

Browse files
committed
[NativeAOT] Objective-C exception interception
1 parent 7ec3634 commit dadbc7d

File tree

14 files changed

+473
-26
lines changed

14 files changed

+473
-26
lines changed

src/coreclr/nativeaot/Bootstrap/main.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ extern "C" void IDynamicCastableGetInterfaceImplementation();
115115
extern "C" void ObjectiveCMarshalTryGetTaggedMemory();
116116
extern "C" void ObjectiveCMarshalGetIsTrackedReferenceCallback();
117117
extern "C" void ObjectiveCMarshalGetOnEnteredFinalizerQueueCallback();
118+
extern "C" void ObjectiveCMarshalGetUnhandledExceptionPropagationHandler();
118119
#endif
119120

120121
typedef void(*pfn)();
@@ -134,10 +135,12 @@ static const pfn c_classlibFunctions[] = {
134135
&ObjectiveCMarshalTryGetTaggedMemory,
135136
&ObjectiveCMarshalGetIsTrackedReferenceCallback,
136137
&ObjectiveCMarshalGetOnEnteredFinalizerQueueCallback,
138+
&ObjectiveCMarshalGetUnhandledExceptionPropagationHandler,
137139
#else
138140
nullptr,
139141
nullptr,
140142
nullptr,
143+
nullptr,
141144
#endif
142145
};
143146

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

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,8 @@ private static void DispatchEx(scoped ref StackFrameIterator frameIter, ref ExIn
611611
byte* prevOriginalPC = null;
612612
UIntPtr prevFramePtr = UIntPtr.Zero;
613613
bool unwoundReversePInvoke = false;
614+
IntPtr pReversePInvokePropagationCallback = IntPtr.Zero;
615+
IntPtr pReversePInvokePropagationContext = IntPtr.Zero;
614616

615617
bool isValid = frameIter.Init(exInfo._pExContext, (exInfo._kind & ExKind.InstructionFaultFlag) != 0);
616618
Debug.Assert(isValid, "RhThrowEx called with an unexpected context");
@@ -643,7 +645,29 @@ private static void DispatchEx(scoped ref StackFrameIterator frameIter, ref ExIn
643645
}
644646
}
645647

646-
if (pCatchHandler == null)
648+
#if FEATURE_OBJCMARSHAL
649+
if (unwoundReversePInvoke)
650+
{
651+
// We did not find any managed handlers before hitting a reverse P/Invoke boundary.
652+
// See if the classlib has a handler to propagate the exception to native code.
653+
IntPtr pGetHandlerClasslibFunction = (IntPtr)InternalCalls.RhpGetClasslibFunctionFromCodeAddress((IntPtr)prevControlPC,
654+
ClassLibFunctionId.ObjectiveCMarshalGetUnhandledExceptionPropagationHandler);
655+
if (pGetHandlerClasslibFunction != IntPtr.Zero)
656+
{
657+
var pGetHandler = (delegate*<object, IntPtr, out IntPtr, IntPtr>)pGetHandlerClasslibFunction;
658+
pReversePInvokePropagationCallback = pGetHandler(
659+
exceptionObj, (IntPtr)prevControlPC, out pReversePInvokePropagationContext);
660+
if (pReversePInvokePropagationCallback != IntPtr.Zero)
661+
{
662+
// Tell the second pass to unwind to this frame.
663+
handlingFrameSP = frameIter.SP;
664+
catchingTryRegionIdx = MaxTryRegionIdx;
665+
}
666+
}
667+
}
668+
#endif // FEATURE_OBJCMARSHAL
669+
670+
if (pCatchHandler == null && pReversePInvokePropagationCallback == IntPtr.Zero)
647671
{
648672
OnUnhandledExceptionViaClassLib(exceptionObj);
649673

@@ -655,8 +679,8 @@ private static void DispatchEx(scoped ref StackFrameIterator frameIter, ref ExIn
655679
}
656680

657681
// We FailFast above if the exception goes unhandled. Therefore, we cannot run the second pass
658-
// without a catch handler.
659-
Debug.Assert(pCatchHandler != null, "We should have a handler if we're starting the second pass");
682+
// without a catch handler or propagation callback.
683+
Debug.Assert(pCatchHandler != null || pReversePInvokePropagationCallback != IntPtr.Zero, "We should have a handler if we're starting the second pass");
660684

661685
// ------------------------------------------------
662686
//
@@ -673,12 +697,22 @@ private static void DispatchEx(scoped ref StackFrameIterator frameIter, ref ExIn
673697

674698
exInfo._passNumber = 2;
675699
startIdx = MaxTryRegionIdx;
700+
unwoundReversePInvoke = false;
676701
isValid = frameIter.Init(exInfo._pExContext, (exInfo._kind & ExKind.InstructionFaultFlag) != 0);
677-
for (; isValid && ((byte*)frameIter.SP <= (byte*)handlingFrameSP); isValid = frameIter.Next(&startIdx))
702+
for (; isValid && ((byte*)frameIter.SP <= (byte*)handlingFrameSP); isValid = frameIter.Next(&startIdx, &unwoundReversePInvoke))
678703
{
679704
Debug.Assert(isValid, "second-pass EH unwind failed unexpectedly");
680705
DebugScanCallFrame(exInfo._passNumber, frameIter.ControlPC, frameIter.SP);
681706

707+
if (unwoundReversePInvoke)
708+
{
709+
Debug.Assert(pReversePInvokePropagationCallback != IntPtr.Zero, "Unwound to a reverse P/Invoke in the second pass. We should have a propagation handler.");
710+
Debug.Assert(frameIter.SP == handlingFrameSP, "Encountered a different reverse P/Invoke frame in the second pass.");
711+
// Found the native frame that called the reverse P/invoke.
712+
// It is not possible to run managed second pass handlers on a native frame.
713+
break;
714+
}
715+
682716
if ((frameIter.SP == handlingFrameSP)
683717
#if TARGET_ARM64
684718
&& (frameIter.ControlPC == prevControlPC)
@@ -693,6 +727,18 @@ private static void DispatchEx(scoped ref StackFrameIterator frameIter, ref ExIn
693727
InvokeSecondPass(ref exInfo, startIdx);
694728
}
695729

730+
#if FEATURE_OBJCMARSHAL
731+
if (pReversePInvokePropagationCallback != IntPtr.Zero)
732+
{
733+
InternalCalls.RhpCallPropagateExceptionCallback(
734+
pReversePInvokePropagationContext, pReversePInvokePropagationCallback, frameIter.RegisterSet, ref exInfo);
735+
// the helper should jump to propagation handler and not return
736+
Debug.Assert(false, "unreachable");
737+
FallbackFailFast(RhFailFastReason.InternalError, null);
738+
}
739+
#endif // FEATURE_OBJCMARSHAL
740+
741+
696742
// ------------------------------------------------
697743
//
698744
// Call the handler and resume execution

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ internal enum ClassLibFunctionId
4747
ObjectiveCMarshalTryGetTaggedMemory = 10,
4848
ObjectiveCMarshalGetIsTrackedReferenceCallback = 11,
4949
ObjectiveCMarshalGetOnEnteredFinalizerQueueCallback = 12,
50+
ObjectiveCMarshalGetUnhandledExceptionPropagationHandler = 13,
5051
}
5152

5253
internal static class InternalCalls
@@ -230,6 +231,13 @@ internal static extern unsafe IntPtr RhpCallCatchFunclet(
230231
internal static extern unsafe bool RhpCallFilterFunclet(
231232
object exceptionObj, byte* pFilterIP, void* pvRegDisplay);
232233

234+
#if FEATURE_OBJCMARSHAL
235+
[RuntimeImport(Redhawk.BaseName, "RhpCallPropagateExceptionCallback")]
236+
[MethodImpl(MethodImplOptions.InternalCall)]
237+
internal static extern unsafe IntPtr RhpCallPropagateExceptionCallback(
238+
IntPtr callbackContext, IntPtr callback, void* pvRegDisplay, ref EH.ExInfo exInfo);
239+
#endif // FEATURE_OBJCMARSHAL
240+
233241
[RuntimeImport(Redhawk.BaseName, "RhpFallbackFailFast")]
234242
[MethodImpl(MethodImplOptions.InternalCall)]
235243
internal static extern unsafe void RhpFallbackFailFast();

src/coreclr/nativeaot/Runtime/ICodeManager.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ enum class ClasslibFunctionId
157157
ObjectiveCMarshalTryGetTaggedMemory = 10,
158158
ObjectiveCMarshalGetIsTrackedReferenceCallback = 11,
159159
ObjectiveCMarshalGetOnEnteredFinalizerQueueCallback = 12,
160+
ObjectiveCMarshalGetUnhandledExceptionPropagationHandler = 13,
160161
};
161162

162163
enum class AssociatedDataFlags : unsigned char
@@ -165,6 +166,14 @@ enum class AssociatedDataFlags : unsigned char
165166
HasUnboxingStubTarget = 1,
166167
};
167168

169+
enum UnwindStackFrameFlags
170+
{
171+
USFF_None = 0,
172+
// If this is a reverse P/Invoke frame, return the P/Invoke transition frame rather than doing a
173+
// normal unwind.
174+
USFF_UsePreviousTransitionFrame = 1,
175+
};
176+
168177
class ICodeManager
169178
{
170179
public:
@@ -185,7 +194,9 @@ class ICodeManager
185194
bool isActiveStackFrame) PURE_VIRTUAL
186195

187196
virtual bool UnwindStackFrame(MethodInfo * pMethodInfo,
197+
uint32_t flags,
188198
REGDISPLAY * pRegisterSet, // in/out
199+
bool * pFoundReversePInvoke, // out
189200
PInvokeTransitionFrame** ppPreviousTransitionFrame) PURE_VIRTUAL // out
190201

191202
virtual uintptr_t GetConservativeUpperBoundForOutgoingArgs(MethodInfo * pMethodInfo,

src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1413,6 +1413,8 @@ void StackFrameIterator::NextInternal()
14131413
{
14141414
UnwindOutOfCurrentManagedFrame:
14151415
ASSERT(m_dwFlags & MethodStateCalculated);
1416+
// Due to the lack of an ICodeManager for native code, we can't unwind from a native frame.
1417+
ASSERT((m_dwFlags & (SkipNativeFrames|UnwoundReversePInvoke)) != UnwoundReversePInvoke);
14161418
m_dwFlags &= ~(ExCollide|MethodStateCalculated|UnwoundReversePInvoke|ActiveStackFrame);
14171419
ASSERT(IsValid());
14181420

@@ -1432,14 +1434,29 @@ void StackFrameIterator::NextInternal()
14321434
uintptr_t DEBUG_preUnwindSP = m_RegDisplay.GetSP();
14331435
#endif
14341436

1437+
uint32_t unwindFlags = USFF_None;
1438+
if ((m_dwFlags & SkipNativeFrames) != 0)
1439+
{
1440+
unwindFlags |= USFF_UsePreviousTransitionFrame;
1441+
}
1442+
1443+
bool foundReversePInvoke;
14351444
PInvokeTransitionFrame* pPreviousTransitionFrame;
1436-
FAILFAST_OR_DAC_FAIL(GetCodeManager()->UnwindStackFrame(&m_methodInfo, &m_RegDisplay, &pPreviousTransitionFrame));
1445+
FAILFAST_OR_DAC_FAIL(GetCodeManager()->UnwindStackFrame(&m_methodInfo, unwindFlags, &m_RegDisplay,
1446+
&foundReversePInvoke, &pPreviousTransitionFrame));
1447+
1448+
if (foundReversePInvoke)
1449+
{
1450+
ASSERT(pPreviousTransitionFrame != nullptr || (unwindFlags & USFF_UsePreviousTransitionFrame) == 0);
1451+
m_dwFlags |= UnwoundReversePInvoke;
1452+
}
14371453

14381454
bool doingFuncletUnwind = GetCodeManager()->IsFunclet(&m_methodInfo);
14391455

14401456
if (pPreviousTransitionFrame != NULL)
14411457
{
14421458
ASSERT(!doingFuncletUnwind);
1459+
ASSERT(foundReversePInvoke);
14431460

14441461
if (pPreviousTransitionFrame == TOP_OF_STACK_MARKER)
14451462
{
@@ -1458,7 +1475,6 @@ void StackFrameIterator::NextInternal()
14581475
InternalInit(m_pThread, pPreviousTransitionFrame, GcStackWalkFlags);
14591476
ASSERT(m_pInstance->IsManaged(m_ControlPC));
14601477
}
1461-
m_dwFlags |= UnwoundReversePInvoke;
14621478
}
14631479
else
14641480
{
@@ -1579,11 +1595,12 @@ void StackFrameIterator::NextInternal()
15791595
}
15801596

15811597
// Now that all assembly thunks and ExInfo collisions have been processed, it is guaranteed
1582-
// that the next managed frame has been located. The located frame must now be yielded
1598+
// that the next managed frame has been located. Or the next native frame
1599+
// if we are not skipping them. The located frame must now be yielded
15831600
// from the iterator with the one and only exception being cases where a managed frame must
15841601
// be skipped due to funclet collapsing.
15851602

1586-
ASSERT(m_pInstance->IsManaged(m_ControlPC));
1603+
ASSERT(m_pInstance->IsManaged(m_ControlPC) || (foundReversePInvoke && (m_dwFlags & SkipNativeFrames) == 0));
15871604

15881605
if (collapsingTargetFrame != NULL)
15891606
{
@@ -1692,7 +1709,8 @@ void StackFrameIterator::PrepareToYieldFrame()
16921709
if (!IsValid())
16931710
return;
16941711

1695-
ASSERT(m_pInstance->IsManaged(m_ControlPC));
1712+
ASSERT(m_pInstance->IsManaged(m_ControlPC) ||
1713+
((m_dwFlags & SkipNativeFrames) == 0 && (m_dwFlags & UnwoundReversePInvoke) != 0));
16961714

16971715
if (m_dwFlags & ApplyReturnAddressAdjustment)
16981716
{
@@ -1748,6 +1766,7 @@ REGDISPLAY * StackFrameIterator::GetRegisterSet()
17481766
PTR_VOID StackFrameIterator::GetEffectiveSafePointAddress()
17491767
{
17501768
ASSERT(IsValid());
1769+
ASSERT(m_effectiveSafePointAddress);
17511770
return m_effectiveSafePointAddress;
17521771
}
17531772

@@ -1780,6 +1799,17 @@ void StackFrameIterator::CalculateCurrentMethodState()
17801799
if (m_dwFlags & MethodStateCalculated)
17811800
return;
17821801

1802+
// Check if we are on a native frame.
1803+
if ((m_dwFlags & (SkipNativeFrames|UnwoundReversePInvoke)) == UnwoundReversePInvoke)
1804+
{
1805+
// There is no implementation of ICodeManager for native code.
1806+
m_pCodeManager = nullptr;
1807+
m_effectiveSafePointAddress = nullptr;
1808+
m_FramePointer = nullptr;
1809+
m_dwFlags |= MethodStateCalculated;
1810+
return;
1811+
}
1812+
17831813
// Assume that the caller is likely to be in the same module
17841814
if (m_pCodeManager == NULL || !m_pCodeManager->FindMethodInfo(m_ControlPC, &m_methodInfo))
17851815
{

src/coreclr/nativeaot/Runtime/StackFrameIterator.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,10 @@ class StackFrameIterator
147147
// The thread was interrupted in the current frame at the current IP by a signal, SuspendThread or similar.
148148
ActiveStackFrame = 0x40,
149149

150-
GcStackWalkFlags = (CollapseFunclets | RemapHardwareFaultsToSafePoint),
150+
// When encountering a reverse P/Invoke, unwind directly to the P/Invoke frame using the saved transition frame.
151+
SkipNativeFrames = 0x80,
152+
153+
GcStackWalkFlags = (CollapseFunclets | RemapHardwareFaultsToSafePoint | SkipNativeFrames),
151154
EHStackWalkFlags = ApplyReturnAddressAdjustment,
152155
StackTraceStackWalkFlags = GcStackWalkFlags
153156
};
@@ -209,7 +212,7 @@ class StackFrameIterator
209212
GCRefKind m_HijackedReturnValueKind;
210213
PTR_UIntNative m_pConservativeStackRangeLowerBound;
211214
PTR_UIntNative m_pConservativeStackRangeUpperBound;
212-
uint32_t m_dwFlags;
215+
uint32_t m_dwFlags;
213216
PTR_ExInfo m_pNextExInfo;
214217
PTR_VOID m_pendingFuncletFramePointer;
215218
PreservedRegPtrs m_funcletPtrs; // @TODO: Placing the 'scratch space' in the StackFrameIterator is not

0 commit comments

Comments
 (0)