Skip to content

Commit f6ecb6e

Browse files
authored
[debugger] Fix crash during Async Break when APC and CET are enabled (#111408)
When we are running an APC Callback we are not allowed to SetIP because when APC Callback is resuming it will check if the IP is not changed if CET is enabled. To avoid this problem we use the APC to suspend the thread, but then we enable the single step and continue the thread execution, this will exit the apc callback and pause in the single step, so we are allowed to SetIP to FuncEvalHijack to run FuncEvals.
1 parent f9cf3c8 commit f6ecb6e

File tree

6 files changed

+88
-7
lines changed

6 files changed

+88
-7
lines changed

src/coreclr/debug/ee/controller.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4469,7 +4469,19 @@ bool DebuggerController::DispatchNativeException(EXCEPTION_RECORD *pException,
44694469
ThisFunctionMayHaveTriggerAGC();
44704470
}
44714471
#endif
4472-
4472+
#ifdef FEATURE_SPECIAL_USER_MODE_APC
4473+
if (pCurThread->m_State & Thread::TS_SSToExitApcCall)
4474+
{
4475+
if (!CheckActivationSafePoint(GetIP(pContext)))
4476+
{
4477+
return FALSE;
4478+
}
4479+
pCurThread->SetThreadState(Thread::TS_SSToExitApcCallDone);
4480+
pCurThread->ResetThreadState(Thread::TS_SSToExitApcCall);
4481+
DebuggerController::UnapplyTraceFlag(pCurThread);
4482+
pCurThread->MarkForSuspensionAndWait(Thread::TS_DebugSuspendPending);
4483+
}
4484+
#endif
44734485

44744486

44754487
// Must restore the filter context. After the filter context is gone, we're

src/coreclr/debug/ee/debugger.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15009,6 +15009,14 @@ HRESULT Debugger::FuncEvalSetup(DebuggerIPCE_FuncEvalInfo *pEvalInfo,
1500915009
return CORDBG_E_ILLEGAL_IN_STACK_OVERFLOW;
1501015010
}
1501115011

15012+
#ifdef FEATURE_SPECIAL_USER_MODE_APC
15013+
if (pThread->m_hasPendingActivation)
15014+
{
15015+
_ASSERTE(!"Should never get here with a pending activation. (Debugger::FuncEvalSetup)");
15016+
return CORDBG_E_ILLEGAL_IN_NATIVE_CODE;
15017+
}
15018+
#endif
15019+
1501215020
bool fInException = pEvalInfo->evalDuringException;
1501315021

1501415022
// The thread has to be at a GC safe place for now, just in case the func eval causes a collection. Processing an
@@ -16780,6 +16788,15 @@ void Debugger::ExternalMethodFixupNextStep(PCODE address)
1678016788
{
1678116789
DebuggerController::DispatchExternalMethodFixup(address);
1678216790
}
16791+
#ifdef FEATURE_SPECIAL_USER_MODE_APC
16792+
void Debugger::SingleStepToExitApcCall(Thread* pThread, CONTEXT *interruptedContext)
16793+
{
16794+
pThread->SetThreadState(Thread::TS_SSToExitApcCall);
16795+
g_pEEInterface->SetThreadFilterContext(pThread, interruptedContext);
16796+
DebuggerController::EnableSingleStep(pThread);
16797+
g_pEEInterface->SetThreadFilterContext(pThread, NULL);
16798+
}
16799+
#endif //FEATURE_SPECIAL_USER_MODE_APC
1678316800
#endif //DACCESS_COMPILE
1678416801

1678516802
unsigned FuncEvalFrame::GetFrameAttribs_Impl(void)

src/coreclr/debug/ee/debugger.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3061,6 +3061,9 @@ class Debugger : public DebugInterface
30613061
// Used by Debugger::FirstChanceNativeException to update the context from out of process
30623062
void SendSetThreadContextNeeded(CONTEXT *context, DebuggerSteppingInfo *pDebuggerSteppingInfo = NULL);
30633063
BOOL IsOutOfProcessSetContextEnabled();
3064+
#ifdef FEATURE_SPECIAL_USER_MODE_APC
3065+
void SingleStepToExitApcCall(Thread* pThread, CONTEXT *interruptedContext);
3066+
#endif // FEATURE_SPECIAL_USER_MODE_APC
30643067
};
30653068

30663069

src/coreclr/vm/dbginterface.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,9 @@ class DebugInterface
412412
virtual HRESULT IsMethodDeoptimized(Module *pModule, mdMethodDef methodDef, BOOL *pResult) = 0;
413413
virtual void MulticastTraceNextStep(DELEGATEREF pbDel, INT32 count) = 0;
414414
virtual void ExternalMethodFixupNextStep(PCODE address) = 0;
415+
#ifdef FEATURE_SPECIAL_USER_MODE_APC
416+
virtual void SingleStepToExitApcCall(Thread* pThread, CONTEXT *interruptedContext) = 0;
417+
#endif // FEATURE_SPECIAL_USER_MODE_APC
415418
#endif //DACCESS_COMPILE
416419
};
417420

src/coreclr/vm/threads.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,7 @@ class Thread
481481
friend void STDCALL OnHijackWorker(HijackArgs * pArgs);
482482
#ifdef FEATURE_THREAD_ACTIVATION
483483
friend void HandleSuspensionForInterruptedThread(CONTEXT *interruptedContext);
484+
friend void HandleSuspensionForInterruptedThread(CONTEXT *interruptedContext, bool suspendForDebugger);
484485
friend BOOL CheckActivationSafePoint(SIZE_T ip);
485486
#endif // FEATURE_THREAD_ACTIVATION
486487

@@ -549,7 +550,7 @@ class Thread
549550
TS_Hijacked = 0x00000080, // Return address has been hijacked
550551
#endif // FEATURE_HIJACK
551552

552-
// unused = 0x00000100,
553+
TS_SSToExitApcCall = 0x00000100, // Enable SS and resume the thread to exit an APC Call and keep the thread in suspend state
553554
TS_Background = 0x00000200, // Thread is a background thread
554555
TS_Unstarted = 0x00000400, // Thread has never been started
555556
TS_Dead = 0x00000800, // Thread is dead
@@ -566,7 +567,7 @@ class Thread
566567
TS_ReportDead = 0x00010000, // in WaitForOtherThreads()
567568
TS_FullyInitialized = 0x00020000, // Thread is fully initialized and we are ready to broadcast its existence to external clients
568569

569-
// unused = 0x00040000,
570+
TS_SSToExitApcCallDone = 0x00040000, // The thread exited an APC Call and it is already resumed and paused on SS
570571

571572
TS_SyncSuspended = 0x00080000, // Suspended via WaitSuspendEvent
572573
TS_DebugWillSync = 0x00100000, // Debugger will wait for this thread to sync
@@ -2570,7 +2571,9 @@ class Thread
25702571
//-------------------------------------------------------------
25712572
// Waiting & Synchronization
25722573
//-------------------------------------------------------------
2573-
2574+
friend class DebuggerController;
2575+
protected:
2576+
void MarkForSuspensionAndWait(ULONG bit);
25742577
// For suspends. The thread waits on this event. A client sets the event to cause
25752578
// the thread to resume.
25762579
void WaitSuspendEvents();

src/coreclr/vm/threadsuspend.cpp

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4215,6 +4215,18 @@ bool Thread::SysSweepThreadsForDebug(bool forceSync)
42154215
if ((thread->m_State & TS_DebugWillSync) == 0)
42164216
continue;
42174217

4218+
#ifdef FEATURE_SPECIAL_USER_MODE_APC
4219+
if (thread->m_State & Thread::TS_SSToExitApcCallDone)
4220+
{
4221+
thread->ResetThreadState(Thread::TS_SSToExitApcCallDone);
4222+
goto Label_MarkThreadAsSynced;
4223+
}
4224+
if (thread->m_State & Thread::TS_SSToExitApcCall)
4225+
{
4226+
continue;
4227+
}
4228+
#endif
4229+
42184230
if (!UseContextBasedThreadRedirection())
42194231
{
42204232
// On platforms that do not support safe thread suspension we either
@@ -5331,6 +5343,19 @@ BOOL Thread::HandledJITCase()
53315343
#endif // FEATURE_HIJACK
53325344

53335345
// Some simple helpers to keep track of the threads we are waiting for
5346+
void Thread::MarkForSuspensionAndWait(ULONG bit)
5347+
{
5348+
CONTRACTL {
5349+
NOTHROW;
5350+
GC_NOTRIGGER;
5351+
}
5352+
CONTRACTL_END;
5353+
m_DebugSuspendEvent.Reset();
5354+
InterlockedOr((LONG*)&m_State, bit);
5355+
ThreadStore::IncrementTrapReturningThreads();
5356+
m_DebugSuspendEvent.Wait(INFINITE,FALSE);
5357+
}
5358+
53345359
void Thread::MarkForSuspension(ULONG bit)
53355360
{
53365361
CONTRACTL {
@@ -5753,7 +5778,8 @@ BOOL CheckActivationSafePoint(SIZE_T ip)
57535778
// address to take the thread to the appropriate stub (based on the return
57545779
// type of the method) which will then handle preparing the thread for GC.
57555780
//
5756-
void HandleSuspensionForInterruptedThread(CONTEXT *interruptedContext)
5781+
5782+
void HandleSuspensionForInterruptedThread(CONTEXT *interruptedContext, bool suspendForDebugger)
57575783
{
57585784
struct AutoClearPendingThreadActivation
57595785
{
@@ -5789,6 +5815,18 @@ void HandleSuspensionForInterruptedThread(CONTEXT *interruptedContext)
57895815
if (!codeInfo.IsValid())
57905816
return;
57915817

5818+
#ifdef FEATURE_SPECIAL_USER_MODE_APC
5819+
// It's not allowed to change the IP while paused in an APC Callback for security reasons if CET is turned on
5820+
// So we enable the single step in the thread that is running the APC Callback
5821+
// and then it will be paused using single step exception after exiting the APC callback
5822+
// this will allow the debugger to setIp to execute FuncEvalHijack.
5823+
if (suspendForDebugger)
5824+
{
5825+
g_pDebugInterface->SingleStepToExitApcCall(pThread, interruptedContext);
5826+
return;
5827+
}
5828+
#endif
5829+
57925830
DWORD addrOffset = codeInfo.GetRelOffset();
57935831

57945832
ICodeManager *pEECM = codeInfo.GetCodeManager();
@@ -5863,6 +5901,11 @@ void HandleSuspensionForInterruptedThread(CONTEXT *interruptedContext)
58635901
}
58645902
}
58655903

5904+
void HandleSuspensionForInterruptedThread(CONTEXT *interruptedContext)
5905+
{
5906+
HandleSuspensionForInterruptedThread(interruptedContext, false);
5907+
}
5908+
58665909
#ifdef FEATURE_SPECIAL_USER_MODE_APC
58675910
void Thread::ApcActivationCallback(ULONG_PTR Parameter)
58685911
{
@@ -5889,10 +5932,10 @@ void Thread::ApcActivationCallback(ULONG_PTR Parameter)
58895932

58905933
switch (reason)
58915934
{
5892-
case ActivationReason::SuspendForGC:
58935935
case ActivationReason::SuspendForDebugger:
5936+
case ActivationReason::SuspendForGC:
58945937
case ActivationReason::ThreadAbort:
5895-
HandleSuspensionForInterruptedThread(pContext);
5938+
HandleSuspensionForInterruptedThread(pContext, reason == ActivationReason::SuspendForDebugger);
58965939
break;
58975940

58985941
default:

0 commit comments

Comments
 (0)