Skip to content

Commit defd20c

Browse files
VSadovDan Moseley
andauthored
EE Suspension on x86 should use RtlRestoreContext when available (#65878)
* RestoreContextSimulated * probe for RtlRestoreContext * ntdll.dll * restore self-trap sequence * PR feedback * Clarify CopyContext in RedirectedHandledJITCaseExceptionFilter * simpler indentation. * restore last error on the legacy path. * Update src/coreclr/vm/threads.h Co-authored-by: Dan Moseley <danmose@microsoft.com>
1 parent 3330c83 commit defd20c

2 files changed

Lines changed: 134 additions & 144 deletions

File tree

src/coreclr/vm/threads.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3076,6 +3076,14 @@ class Thread
30763076
static void __stdcall RedirectedHandledJITCaseForGCStress();
30773077
#endif // defined(HAVE_GCCOVER) && USE_REDIRECT_FOR_GCSTRESS
30783078

3079+
#ifdef TARGET_X86
3080+
// RtlRestoreContext is available on x86, but relatively recently.
3081+
// RestoreContextSimulated uses SEH machinery for a similar result on legacy OS-es.
3082+
// This function should not be used on new OS-es as the pattern is not
3083+
// guaranteed to continue working in the future.
3084+
static void RestoreContextSimulated(Thread* pThread, CONTEXT* pCtx, void* pFrame, DWORD dwLastError);
3085+
#endif
3086+
30793087
friend void CPFH_AdjustContextForThreadSuspensionRace(T_CONTEXT *pContext, Thread *pThread);
30803088
#endif // FEATURE_HIJACK && !TARGET_UNIX
30813089

src/coreclr/vm/threadsuspend.cpp

Lines changed: 126 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -1948,6 +1948,9 @@ typedef BOOL(WINAPI* PINITIALIZECONTEXT2)(PVOID Buffer, DWORD ContextFlags, PCON
19481948
PINITIALIZECONTEXT2 pfnInitializeContext2 = NULL;
19491949

19501950
#ifdef TARGET_X86
1951+
typedef VOID(__cdecl* PRTLRESTORECONTEXT)(PCONTEXT ContextRecord, struct _EXCEPTION_RECORD* ExceptionRecord);
1952+
PRTLRESTORECONTEXT pfnRtlRestoreContext = NULL;
1953+
19511954
#define CONTEXT_COMPLETE (CONTEXT_FULL | CONTEXT_FLOATING_POINT | \
19521955
CONTEXT_DEBUG_REGISTERS | CONTEXT_EXTENDED_REGISTERS | CONTEXT_EXCEPTION_REQUEST)
19531956
#else
@@ -1967,6 +1970,14 @@ CONTEXT* AllocateOSContextHelper(BYTE** contextBuffer)
19671970
pfnInitializeContext2 = (PINITIALIZECONTEXT2)GetProcAddress(hm, "InitializeContext2");
19681971
}
19691972

1973+
#ifdef TARGET_X86
1974+
if (pfnRtlRestoreContext == NULL)
1975+
{
1976+
HMODULE hm = GetModuleHandleW(_T("ntdll.dll"));
1977+
pfnRtlRestoreContext = (PRTLRESTORECONTEXT)GetProcAddress(hm, "RtlRestoreContext");
1978+
}
1979+
#endif //TARGET_X86
1980+
19701981
// Determine if the processor supports AVX so we could
19711982
// retrieve extended registers
19721983
DWORD64 FeatureMask = GetEnabledXStateFeatures();
@@ -2515,18 +2526,18 @@ void RedirectedThreadFrame::ExceptionUnwind()
25152526
int RedirectedHandledJITCaseExceptionFilter(
25162527
PEXCEPTION_POINTERS pExcepPtrs, // Exception data
25172528
RedirectedThreadFrame *pFrame, // Frame on stack
2518-
BOOL fDone, // Whether redirect completed without exception
2519-
CONTEXT *pCtx) // Saved context
2529+
CONTEXT *pCtx, // Saved context
2530+
DWORD dwLastError) // saved last error
25202531
{
25212532
// !!! Do not use a non-static contract here.
25222533
// !!! Contract may insert an exception handling record.
25232534
// !!! This function assumes that GetCurrentSEHRecord() returns the exception record set up in
2524-
// !!! Thread::RedirectedHandledJITCase
2535+
// !!! Thread::RestoreContextSimulated
25252536
//
25262537
// !!! Do not use an object with dtor, since it injects a fs:0 entry.
25272538
STATIC_CONTRACT_NOTHROW;
25282539
STATIC_CONTRACT_GC_TRIGGERS;
2529-
STATIC_CONTRACT_MODE_ANY;
2540+
STATIC_CONTRACT_MODE_COOPERATIVE;
25302541

25312542
if (pExcepPtrs->ExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW)
25322543
{
@@ -2535,48 +2546,18 @@ int RedirectedHandledJITCaseExceptionFilter(
25352546

25362547
// Get the thread handle
25372548
Thread *pThread = GetThread();
2538-
2539-
STRESS_LOG2(LF_SYNC, LL_INFO100, "In RedirectedHandledJITCaseExceptionFilter fDone = %d pFrame = %p\n", fDone, pFrame);
2540-
2541-
// If we get here via COM+ exception, gc-mode is unknown. We need it to
2542-
// be cooperative for this function.
2543-
GCX_COOP_NO_DTOR();
2544-
2545-
// If the exception was due to the called client, then we need to figure out if it
2546-
// is an exception that can be eaten or if it needs to be handled elsewhere.
2547-
if (!fDone)
2548-
{
2549-
if (pExcepPtrs->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE)
2550-
{
2551-
return (EXCEPTION_CONTINUE_SEARCH);
2552-
}
2553-
2554-
// Get the latest thrown object
2555-
OBJECTREF throwable = CLRException::GetThrowableFromExceptionRecord(pExcepPtrs->ExceptionRecord);
2556-
2557-
// If this is an uncatchable exception, then let the exception be handled elsewhere
2558-
if (IsUncatchable(&throwable))
2559-
{
2560-
pThread->EnablePreemptiveGC();
2561-
return (EXCEPTION_CONTINUE_SEARCH);
2562-
}
2563-
}
2564-
#ifdef _DEBUG
2565-
else
2566-
{
2567-
_ASSERTE(pExcepPtrs->ExceptionRecord->ExceptionCode == EXCEPTION_HIJACK);
2568-
}
2569-
#endif
2549+
STRESS_LOG1(LF_SYNC, LL_INFO100, "In RedirectedHandledJITCaseExceptionFilter pFrame = %p\n", pFrame);
2550+
_ASSERTE(pExcepPtrs->ExceptionRecord->ExceptionCode == EXCEPTION_HIJACK);
25702551

25712552
// Unlink the frame in preparation for resuming in managed code
25722553
pFrame->Pop();
25732554

2574-
// Copy the saved context record into the EH context;
2575-
// NB: cannot use ReplaceExceptionContextRecord here.
2576-
// these contexts may contain extended registers and may have different format
2577-
// for reasons such as alignment or context compaction
2555+
// Copy everything in the saved context record into the EH context.
2556+
// Historically the EH context has enough space for every enabled context feature.
2557+
// That may not hold for the future features beyond AVX, but this codepath is
2558+
// supposed to be used only on OSes that do not have RtlRestoreContext.
25782559
CONTEXT* pTarget = pExcepPtrs->ContextRecord;
2579-
if (!CopyContext(pTarget, pTarget->ContextFlags, pCtx))
2560+
if (!CopyContext(pTarget, pCtx->ContextFlags, pCtx))
25802561
{
25812562
STRESS_LOG1(LF_SYNC, LL_ERROR, "ERROR: Could not set context record, lastError = 0x%x\n", GetLastError());
25822563
EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE);
@@ -2610,6 +2591,9 @@ int RedirectedHandledJITCaseExceptionFilter(
26102591
// Register the special OS handler as the top handler with the OS
26112592
SetCurrentSEHRecord(pCurSEH);
26122593

2594+
// restore last error
2595+
SetLastError(dwLastError);
2596+
26132597
// Resume execution at point where thread was originally redirected
26142598
return (EXCEPTION_CONTINUE_EXECUTION);
26152599
}
@@ -2642,6 +2626,38 @@ extern "C" PCONTEXT __stdcall GetCurrentSavedRedirectContext()
26422626
return pContext;
26432627
}
26442628

2629+
#ifdef TARGET_X86
2630+
2631+
void Thread::RestoreContextSimulated(Thread* pThread, CONTEXT* pCtx, void* pFrame, DWORD dwLastError)
2632+
{
2633+
pThread->HandleThreadAbort(); // Might throw an exception.
2634+
2635+
// A counter to avoid a nasty case where an
2636+
// up-stack filter throws another exception
2637+
// causing our filter to be run again for
2638+
// some unrelated exception.
2639+
int filter_count = 0;
2640+
2641+
__try
2642+
{
2643+
// Save the instruction pointer where we redirected last. This does not race with the check
2644+
// against this variable in HandledJitCase because the GC will not attempt to redirect the
2645+
// thread until the instruction pointer of this thread is back in managed code.
2646+
pThread->m_LastRedirectIP = GetIP(pCtx);
2647+
pThread->m_SpinCount = 0;
2648+
2649+
RaiseException(EXCEPTION_HIJACK, 0, 0, NULL);
2650+
}
2651+
__except (++filter_count == 1
2652+
? RedirectedHandledJITCaseExceptionFilter(GetExceptionInformation(), (RedirectedThreadFrame*)pFrame, pCtx, dwLastError)
2653+
: EXCEPTION_CONTINUE_SEARCH)
2654+
{
2655+
_ASSERTE(!"Reached body of __except in Thread::RedirectedHandledJITCase");
2656+
}
2657+
}
2658+
2659+
#endif // TARGET_X86
2660+
26452661
void __stdcall Thread::RedirectedHandledJITCase(RedirectReason reason)
26462662
{
26472663
STATIC_CONTRACT_THROWS;
@@ -2665,140 +2681,106 @@ void __stdcall Thread::RedirectedHandledJITCase(RedirectReason reason)
26652681

26662682
STRESS_LOG5(LF_SYNC, LL_INFO1000, "In RedirectedHandledJITcase reason 0x%x pFrame = %p pc = %p sp = %p fp = %p", reason, &frame, GetIP(pCtx), GetSP(pCtx), GetFP(pCtx));
26672683

2668-
#ifdef TARGET_X86
2669-
// This will indicate to the exception filter whether or not the exception is caused
2670-
// by us or the client.
2671-
BOOL fDone = FALSE;
2672-
int filter_count = 0; // A counter to avoid a nasty case where an
2673-
// up-stack filter throws another exception
2674-
// causing our filter to be run again for
2675-
// some unrelated exception.
2676-
2677-
__try
2678-
#endif // TARGET_X86
2679-
{
2680-
// Make sure this thread doesn't reuse the context memory.
2681-
pThread->MarkRedirectContextInUse(pCtx);
2684+
// Make sure this thread doesn't reuse the context memory.
2685+
pThread->MarkRedirectContextInUse(pCtx);
26822686

2683-
// Link in the frame
2684-
frame.Push();
2687+
// Link in the frame
2688+
frame.Push();
26852689

26862690
#if defined(HAVE_GCCOVER) && defined(USE_REDIRECT_FOR_GCSTRESS) // GCCOVER
2687-
if (reason == RedirectReason_GCStress)
2688-
{
2689-
_ASSERTE(pThread->PreemptiveGCDisabledOther());
2690-
DoGcStress(frame.GetContext(), NULL);
2691-
}
2692-
else
2691+
if (reason == RedirectReason_GCStress)
2692+
{
2693+
_ASSERTE(pThread->PreemptiveGCDisabledOther());
2694+
DoGcStress(frame.GetContext(), NULL);
2695+
}
2696+
else
26932697
#endif // HAVE_GCCOVER && USE_REDIRECT_FOR_GCSTRESS
2694-
{
2695-
// Enable PGC before calling out to the client to allow runtime suspend to finish
2696-
GCX_PREEMP_NO_DTOR();
2698+
{
2699+
_ASSERTE(reason == RedirectReason_GCSuspension ||
2700+
reason == RedirectReason_DebugSuspension ||
2701+
reason == RedirectReason_UserSuspension);
26972702

2698-
// Notify the interface of the pending suspension
2699-
switch (reason) {
2700-
case RedirectReason_GCSuspension:
2701-
break;
2702-
case RedirectReason_DebugSuspension:
2703-
break;
2704-
case RedirectReason_UserSuspension:
2705-
// Do nothing;
2706-
break;
2707-
default:
2708-
_ASSERTE(!"Invalid redirect reason");
2709-
break;
2710-
}
2703+
// Actual self-suspension.
2704+
// Leave and reenter COOP mode to be trapped on the way back.
2705+
GCX_PREEMP_NO_DTOR();
2706+
GCX_PREEMP_NO_DTOR_END();
2707+
}
27112708

2712-
// Disable preemptive GC so we can unlink the frame
2713-
GCX_PREEMP_NO_DTOR_END();
2714-
}
2709+
// Once we get here the suspension is over!
2710+
// We will restore the state as it was at the point of redirection
2711+
// and continue normal execution.
27152712

27162713
#ifdef TARGET_X86
2717-
pThread->HandleThreadAbort(); // Might throw an exception.
2718-
2719-
// Indicate that the call to the service went without an exception, and that
2720-
// we're raising our own exception to resume the thread to where it was
2721-
// redirected from
2722-
fDone = TRUE;
2723-
2724-
// Save the instruction pointer where we redirected last. This does not race with the check
2725-
// against this variable in HandledJitCase because the GC will not attempt to redirect the
2726-
// thread until the instruction pointer of this thread is back in managed code.
2727-
pThread->m_LastRedirectIP = GetIP(pCtx);
2728-
pThread->m_SpinCount = 0;
2729-
2730-
RaiseException(EXCEPTION_HIJACK, 0, 0, NULL);
2714+
if (!pfnRtlRestoreContext)
2715+
{
2716+
RestoreContextSimulated(pThread, pCtx, &frame, dwLastError);
27312717

2732-
#else // TARGET_X86
2718+
// we never return to the caller.
2719+
__UNREACHABLE();
2720+
}
2721+
#endif // TARGET_X86
27332722

27342723
#if defined(HAVE_GCCOVER) && defined(USE_REDIRECT_FOR_GCSTRESS) // GCCOVER
2735-
//
2736-
// If GCStress interrupts an IL stub or inlined p/invoke while it's running in preemptive mode, it switches the mode to
2737-
// cooperative - but we will resume to preemptive below. We should not trigger an abort in that case, as it will fail
2738-
// due to the GC mode.
2739-
//
2740-
if (!pThread->m_fPreemptiveGCDisabledForGCStress)
2724+
//
2725+
// If GCStress interrupts an IL stub or inlined p/invoke while it's running in preemptive mode, it switches the mode to
2726+
// cooperative - but we will resume to preemptive below. We should not trigger an abort in that case, as it will fail
2727+
// due to the GC mode.
2728+
//
2729+
if (!pThread->m_fPreemptiveGCDisabledForGCStress)
27412730
#endif
2742-
{
2731+
{
27432732

2744-
UINT_PTR uAbortAddr;
2745-
UINT_PTR uResumePC = (UINT_PTR)GetIP(pCtx);
2746-
CopyOSContext(pThread->m_OSContext, pCtx);
2747-
uAbortAddr = (UINT_PTR)COMPlusCheckForAbort();
2748-
if (uAbortAddr)
2749-
{
2750-
LOG((LF_EH, LL_INFO100, "thread abort in progress, resuming thread under control... (handled jit case)\n"));
2733+
UINT_PTR uAbortAddr;
2734+
UINT_PTR uResumePC = (UINT_PTR)GetIP(pCtx);
2735+
CopyOSContext(pThread->m_OSContext, pCtx);
2736+
uAbortAddr = (UINT_PTR)COMPlusCheckForAbort();
2737+
if (uAbortAddr)
2738+
{
2739+
LOG((LF_EH, LL_INFO100, "thread abort in progress, resuming thread under control... (handled jit case)\n"));
27512740

2752-
CONSISTENCY_CHECK(CheckPointer(pCtx));
2741+
CONSISTENCY_CHECK(CheckPointer(pCtx));
27532742

2754-
STRESS_LOG1(LF_EH, LL_INFO10, "resume under control: ip: %p (handled jit case)\n", uResumePC);
2743+
STRESS_LOG1(LF_EH, LL_INFO10, "resume under control: ip: %p (handled jit case)\n", uResumePC);
27552744

2756-
SetIP(pThread->m_OSContext, uResumePC);
2745+
SetIP(pThread->m_OSContext, uResumePC);
27572746

27582747
#if defined(TARGET_ARM)
2759-
// Save the original resume PC in Lr
2760-
pCtx->Lr = uResumePC;
2748+
// Save the original resume PC in Lr
2749+
pCtx->Lr = uResumePC;
27612750

2762-
// Since we have set a new IP, we have to clear conditional execution flags too.
2763-
ClearITState(pThread->m_OSContext);
2751+
// Since we have set a new IP, we have to clear conditional execution flags too.
2752+
ClearITState(pThread->m_OSContext);
27642753
#endif // TARGET_ARM
27652754

2766-
SetIP(pCtx, uAbortAddr);
2767-
}
2755+
SetIP(pCtx, uAbortAddr);
27682756
}
2757+
}
27692758

2770-
// Unlink the frame in preparation for resuming in managed code
2771-
frame.Pop();
2759+
// Unlink the frame in preparation for resuming in managed code
2760+
frame.Pop();
27722761

2773-
{
2774-
// Allow future use of the context
2775-
pThread->UnmarkRedirectContextInUse(pCtx);
2762+
// Allow future use of the context
2763+
pThread->UnmarkRedirectContextInUse(pCtx);
27762764

27772765
#if defined(HAVE_GCCOVER) && defined(USE_REDIRECT_FOR_GCSTRESS) // GCCOVER
2778-
if (pThread->m_fPreemptiveGCDisabledForGCStress)
2779-
{
2780-
pThread->EnablePreemptiveGC();
2781-
pThread->m_fPreemptiveGCDisabledForGCStress = false;
2782-
}
2766+
if (pThread->m_fPreemptiveGCDisabledForGCStress)
2767+
{
2768+
pThread->EnablePreemptiveGC();
2769+
pThread->m_fPreemptiveGCDisabledForGCStress = false;
2770+
}
27832771
#endif
27842772

2785-
LOG((LF_SYNC, LL_INFO1000, "Resuming execution with RtlRestoreContext\n"));
2786-
2787-
SetLastError(dwLastError); // END_PRESERVE_LAST_ERROR
2773+
LOG((LF_SYNC, LL_INFO1000, "Resuming execution with RtlRestoreContext\n"));
2774+
SetLastError(dwLastError); // END_PRESERVE_LAST_ERROR
27882775

2789-
RtlRestoreContext(pCtx, NULL);
2790-
}
2791-
#endif // TARGET_X86
2792-
}
27932776
#ifdef TARGET_X86
2794-
__except (++filter_count == 1
2795-
? RedirectedHandledJITCaseExceptionFilter(GetExceptionInformation(), &frame, fDone, pCtx)
2796-
: EXCEPTION_CONTINUE_SEARCH)
2797-
{
2798-
_ASSERTE(!"Reached body of __except in Thread::RedirectedHandledJITCase");
2799-
}
2777+
pfnRtlRestoreContext(pCtx, NULL);
2778+
#else
2779+
RtlRestoreContext(pCtx, NULL);
2780+
#endif
28002781

2801-
#endif // TARGET_X86
2782+
// we never return to the caller.
2783+
__UNREACHABLE();
28022784
}
28032785

28042786
//****************************************************************************************

0 commit comments

Comments
 (0)