From eccd22cef54495fafb6f5402892192d07fea0843 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Sat, 9 Mar 2024 23:18:51 -0800 Subject: [PATCH 1/2] Add support for building runtime with FEATURE_EH_FUNCLETS on win-x86 This doesn't change the runtime configuration but it enables the code paths necessary to support it in future if we decide to switch. --- src/coreclr/debug/ee/debugger.h | 2 +- src/coreclr/debug/ee/funceval.cpp | 5 +- src/coreclr/gcdump/i386/gcdumpx86.cpp | 12 -- src/coreclr/inc/crosscomp.h | 30 ++++ src/coreclr/inc/regdisp.h | 4 + src/coreclr/vm/CMakeLists.txt | 1 + src/coreclr/vm/codeman.cpp | 6 + src/coreclr/vm/excep.h | 20 ++- src/coreclr/vm/exceptionhandling.cpp | 112 +++++++++------ src/coreclr/vm/exceptionhandling.h | 2 +- src/coreclr/vm/frames.h | 4 + src/coreclr/vm/i386/asmconstants.h | 2 + src/coreclr/vm/i386/asmhelpers.asm | 114 +++++++++++++++- src/coreclr/vm/i386/cgenx86.cpp | 10 +- src/coreclr/vm/i386/ehhelpers.asm | 101 ++++++++++++++ src/coreclr/vm/i386/excepcpu.h | 8 +- src/coreclr/vm/i386/excepx86.cpp | 190 +++++++++++++------------- src/coreclr/vm/jithelpers.cpp | 18 +++ src/coreclr/vm/readytoruninfo.cpp | 2 +- src/coreclr/vm/stackwalk.cpp | 4 +- src/coreclr/vm/stackwalk.h | 4 +- src/coreclr/vm/threads.cpp | 11 +- 22 files changed, 483 insertions(+), 179 deletions(-) create mode 100644 src/coreclr/vm/i386/ehhelpers.asm diff --git a/src/coreclr/debug/ee/debugger.h b/src/coreclr/debug/ee/debugger.h index 9da256056f71d0..a7d5335835fe15 100644 --- a/src/coreclr/debug/ee/debugger.h +++ b/src/coreclr/debug/ee/debugger.h @@ -3994,7 +3994,7 @@ HANDLE OpenWin32EventOrThrow( // Returns true if the specified IL offset has a special meaning (eg. prolog, etc.) bool DbgIsSpecialILOffset(DWORD offset); -#if defined(TARGET_WINDOWS) +#if defined(TARGET_WINDOWS) && !defined(TARGET_X86) void FixupDispatcherContext(T_DISPATCHER_CONTEXT* pDispatcherContext, T_CONTEXT* pContext, PEXCEPTION_ROUTINE pUnwindPersonalityRoutine = NULL); #endif diff --git a/src/coreclr/debug/ee/funceval.cpp b/src/coreclr/debug/ee/funceval.cpp index 66a85ab0d239da..3b62a6addf2e7b 100644 --- a/src/coreclr/debug/ee/funceval.cpp +++ b/src/coreclr/debug/ee/funceval.cpp @@ -3990,7 +3990,7 @@ void * STDCALL FuncEvalHijackWorker(DebuggerEval *pDE) } -#if defined(FEATURE_EH_FUNCLETS) && !defined(TARGET_UNIX) +#if defined(FEATURE_EH_FUNCLETS) && !defined(TARGET_UNIX) && !defined(TARGET_X86) EXTERN_C EXCEPTION_DISPOSITION FuncEvalHijackPersonalityRoutine(IN PEXCEPTION_RECORD pExceptionRecord, @@ -4028,7 +4028,6 @@ FuncEvalHijackPersonalityRoutine(IN PEXCEPTION_RECORD pExceptionRecord, return ExceptionCollidedUnwind; } - -#endif // FEATURE_EH_FUNCLETS && !TARGET_UNIX +#endif // FEATURE_EH_FUNCLETS && !TARGET_UNIX && !TARGET_X86 #endif // ifndef DACCESS_COMPILE diff --git a/src/coreclr/gcdump/i386/gcdumpx86.cpp b/src/coreclr/gcdump/i386/gcdumpx86.cpp index 680504db6f0585..d32cd8b8f738cf 100644 --- a/src/coreclr/gcdump/i386/gcdumpx86.cpp +++ b/src/coreclr/gcdump/i386/gcdumpx86.cpp @@ -13,7 +13,6 @@ #endif #include "gcdump.h" - /*****************************************************************************/ #define castto(var,typ) (*(typ *)&var) @@ -323,11 +322,7 @@ size_t GCDump::DumpGCTable(PTR_CBYTE table, gcPrintf("%s%s pointer\n", (lowBits & byref_OFFSET_FLAG) ? "byref " : "", -#ifndef FEATURE_EH_FUNCLETS - (lowBits & this_OFFSET_FLAG) ? "this" : "" -#else (lowBits & pinned_OFFSET_FLAG) ? "pinned" : "" -#endif ); _ASSERTE(endOffs <= methodSize); @@ -456,10 +451,6 @@ size_t GCDump::DumpGCTable(PTR_CBYTE table, /* non-ptr arg push */ curOffs += (val & 0x07); -#ifndef FEATURE_EH_FUNCLETS - // For funclets, non-ptr arg pushes can be reported even for EBP frames - _ASSERTE(!header.ebpFrame); -#endif // FEATURE_EH_FUNCLETS argCnt++; DumpEncoding(bp, table-bp); bp = table; @@ -681,9 +672,6 @@ size_t GCDump::DumpGCTable(PTR_CBYTE table, { argTab += decodeUnsigned(argTab, &val); -#ifndef FEATURE_EH_FUNCLETS - assert((val & this_OFFSET_FLAG) == 0); -#endif unsigned stkOffs = val & ~byref_OFFSET_FLAG; unsigned lowBit = val & byref_OFFSET_FLAG; diff --git a/src/coreclr/inc/crosscomp.h b/src/coreclr/inc/crosscomp.h index 36081dee778ab4..f6e65f3f8cf57e 100644 --- a/src/coreclr/inc/crosscomp.h +++ b/src/coreclr/inc/crosscomp.h @@ -667,6 +667,36 @@ typedef struct _T_KNONVOLATILE_CONTEXT_POINTERS { #define T_DISPATCHER_CONTEXT DISPATCHER_CONTEXT #define PT_DISPATCHER_CONTEXT PDISPATCHER_CONTEXT +#if defined(HOST_WINDOWS) && defined(TARGET_X86) +typedef struct _KNONVOLATILE_CONTEXT { + + DWORD Edi; + DWORD Esi; + DWORD Ebx; + DWORD Ebp; + +} KNONVOLATILE_CONTEXT, *PKNONVOLATILE_CONTEXT; + +typedef struct _KNONVOLATILE_CONTEXT_POINTERS_EX +{ + // The ordering of these fields should be aligned with that + // of corresponding fields in CONTEXT + // + // (See FillRegDisplay in inc/regdisp.h for details) + PDWORD Edi; + PDWORD Esi; + PDWORD Ebx; + PDWORD Edx; + PDWORD Ecx; + PDWORD Eax; + + PDWORD Ebp; + +} KNONVOLATILE_CONTEXT_POINTERS_EX, *PKNONVOLATILE_CONTEXT_POINTERS_EX; + +#define KNONVOLATILE_CONTEXT_POINTERS KNONVOLATILE_CONTEXT_POINTERS_EX +#define PKNONVOLATILE_CONTEXT_POINTERS PKNONVOLATILE_CONTEXT_POINTERS_EX +#endif #define T_KNONVOLATILE_CONTEXT_POINTERS KNONVOLATILE_CONTEXT_POINTERS #define PT_KNONVOLATILE_CONTEXT_POINTERS PKNONVOLATILE_CONTEXT_POINTERS diff --git a/src/coreclr/inc/regdisp.h b/src/coreclr/inc/regdisp.h index 8f1f2a736be256..33ad56d9d28e72 100644 --- a/src/coreclr/inc/regdisp.h +++ b/src/coreclr/inc/regdisp.h @@ -523,6 +523,10 @@ inline void FillRegDisplay(const PREGDISPLAY pRD, PT_CONTEXT pctx, PT_CONTEXT pC // This will setup the PC and SP SyncRegDisplayToCurrentContext(pRD); +#ifdef TARGET_X86 + pRD->PCTAddr = (UINT_PTR)&(pctx->Eip); +#endif + #if !defined(DACCESS_COMPILE) #if defined(TARGET_AMD64) && defined(TARGET_WINDOWS) pRD->SSP = GetSSP(pctx); diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index ed401ea0ab99aa..436fc7ae5db107 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -652,6 +652,7 @@ elseif(CLR_CMAKE_TARGET_ARCH_I386) set(VM_SOURCES_WKS_ARCH_ASM ${ARCH_SOURCES_DIR}/RedirectedHandledJITCase.asm ${ARCH_SOURCES_DIR}/asmhelpers.asm + ${ARCH_SOURCES_DIR}/ehhelpers.asm ${ARCH_SOURCES_DIR}/gmsasm.asm ${ARCH_SOURCES_DIR}/jithelp.asm ${ARCH_SOURCES_DIR}/PInvokeStubs.asm diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index ae338fc7a8345f..08930f10d7dd4c 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -6559,6 +6559,11 @@ BOOL ReadyToRunJitManager::IsFilterFunclet(EECodeInfo * pCodeInfo) if (!pCodeInfo->IsFunclet()) return FALSE; +#ifdef TARGET_X86 + // x86 doesn't use personality routines in unwind data, so we have to fallback to + // the slow implementation + return IJitManager::IsFilterFunclet(pCodeInfo); +#else // Get address of the personality routine for the function being queried. SIZE_T size; PTR_VOID pUnwindData = GetUnwindDataBlob(pCodeInfo->GetModuleBase(), pCodeInfo->GetFunctionEntry(), &size); @@ -6583,6 +6588,7 @@ BOOL ReadyToRunJitManager::IsFilterFunclet(EECodeInfo * pCodeInfo) _ASSERTE(fRet == IJitManager::IsFilterFunclet(pCodeInfo)); return fRet; +#endif } #endif // FEATURE_EH_FUNCLETS diff --git a/src/coreclr/vm/excep.h b/src/coreclr/vm/excep.h index b31d34f431f038..a693aedf86d2b6 100644 --- a/src/coreclr/vm/excep.h +++ b/src/coreclr/vm/excep.h @@ -396,7 +396,6 @@ VOID DECLSPEC_NORETURN RealCOMPlusThrowInvalidCastException(TypeHandle thCastFro VOID DECLSPEC_NORETURN RealCOMPlusThrowInvalidCastException(OBJECTREF *pObj, TypeHandle thCastTo); - #ifndef FEATURE_EH_FUNCLETS #include "eexcp.h" @@ -510,6 +509,15 @@ BOOL IsThreadHijackedForThreadStop(Thread* pThread, EXCEPTION_RECORD* pEx void AdjustContextForThreadStop(Thread* pThread, T_CONTEXT* pContext); OBJECTREF CreateCOMPlusExceptionObject(Thread* pThread, EXCEPTION_RECORD* pExceptionRecord, BOOL bAsynchronousThreadStop); +#if defined(TARGET_WINDOWS) && defined(TARGET_X86) +// Pop off any SEH handlers we have registered below pTargetSP +VOID PopSEHRecords(LPVOID pTargetSP); + +// Misc functions to access and update the SEH chain. Be very, very careful about updating the SEH chain. +PEXCEPTION_REGISTRATION_RECORD GetCurrentSEHRecord(); +VOID SetCurrentSEHRecord(EXCEPTION_REGISTRATION_RECORD *pSEH); +#endif + #if !defined(FEATURE_EH_FUNCLETS) EXCEPTION_HANDLER_DECL(COMPlusFrameHandler); EXCEPTION_HANDLER_DECL(COMPlusNestedExceptionHandler); @@ -517,9 +525,6 @@ EXCEPTION_HANDLER_DECL(COMPlusNestedExceptionHandler); EXCEPTION_HANDLER_DECL(COMPlusFrameHandlerRevCom); #endif // FEATURE_COMINTEROP -// Pop off any SEH handlers we have registered below pTargetSP -VOID PopSEHRecords(LPVOID pTargetSP); - #ifdef DEBUGGING_SUPPORTED VOID UnwindExceptionTrackerAndResumeInInterceptionFrame(ExInfo* pExInfo, EHContext* context); #endif // DEBUGGING_SUPPORTED @@ -527,13 +532,6 @@ VOID UnwindExceptionTrackerAndResumeInInterceptionFrame(ExInfo* pExInfo, EHConte BOOL PopNestedExceptionRecords(LPVOID pTargetSP, BOOL bCheckForUnknownHandlers = FALSE); VOID PopNestedExceptionRecords(LPVOID pTargetSP, T_CONTEXT *pCtx, void *pSEH); -// Misc functions to access and update the SEH chain. Be very, very careful about updating the SEH chain. -// Frankly, if you think you need to use one of these function, please -// consult with the owner of the exception system. -PEXCEPTION_REGISTRATION_RECORD GetCurrentSEHRecord(); -VOID SetCurrentSEHRecord(EXCEPTION_REGISTRATION_RECORD *pSEH); - - #define STACK_OVERWRITE_BARRIER_SIZE 20 #define STACK_OVERWRITE_BARRIER_VALUE 0xabcdefab diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index 046f8fea6d14a0..fb57a8cd54ef63 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -47,8 +47,13 @@ ClrUnwindEx(EXCEPTION_RECORD* pExceptionRecord, UINT_PTR ReturnValue, UINT_PTR TargetIP, UINT_PTR TargetFrameSp); +#ifdef TARGET_X86 +EXTERN_C BOOL CallRtlUnwind(EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame, PVOID callback, EXCEPTION_RECORD *pExceptionRecord, PVOID retval); +#endif #endif // !TARGET_UNIX +bool IsCallDescrWorkerInternalReturnAddress(PCODE pCode); + #ifdef USE_CURRENT_CONTEXT_IN_FILTER inline void CaptureNonvolatileRegisters(PKNONVOLATILE_CONTEXT pNonvolatileContext, PCONTEXT pContext) { @@ -366,11 +371,15 @@ void ExceptionTracker::UpdateNonvolatileRegisters(CONTEXT *pContextRecord, REGDI pAbortContext = GetThread()->GetAbortContext(); } -#ifndef TARGET_UNIX -#define HANDLE_NULL_CONTEXT_POINTER _ASSERTE(false) -#else // TARGET_UNIX + // Windows/x86 doesn't have unwinding mechanism for native code. RtlUnwind in + // ProcessCLRException leaves us with the original exception context. Thus we + // rely solely on our frames and managed code unwinding. This also means that + // if we pass through InlinedCallFrame we end up with empty context pointers. +#if defined(TARGET_UNIX) || defined(TARGET_X86) #define HANDLE_NULL_CONTEXT_POINTER -#endif // TARGET_UNIX +#else // TARGET_UNIX || TARGET_X86 +#define HANDLE_NULL_CONTEXT_POINTER _ASSERTE(false) +#endif // TARGET_UNIX || TARGET_X86 #define UPDATEREG(reg) \ do { \ @@ -520,6 +529,10 @@ void CleanUpForSecondPass(Thread* pThread, bool fIsSO, LPVOID MemoryStackFpForFr static void PopExplicitFrames(Thread *pThread, void *targetSp, void *targetCallerSp, bool popGCFrames = true) { +#if defined(TARGET_X86) && defined(TARGET_WINDOWS) && defined(FEATURE_EH_FUNCLETS) + PopSEHRecords((void*)targetSp); +#endif + Frame* pFrame = pThread->GetFrame(); while (pFrame < targetSp) { @@ -572,7 +585,7 @@ static void PopExplicitFrames(Thread *pThread, void *targetSp, void *targetCalle } } -EXTERN_C EXCEPTION_DISPOSITION +EXTERN_C EXCEPTION_DISPOSITION __cdecl ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord, IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, @@ -589,12 +602,15 @@ ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord, Thread* pThread = GetThread(); + // On x86 we don't have dispatcher context +#ifndef TARGET_X86 // Skip native frames of asm helpers that have the ProcessCLRException set as their personality routine. // There is nothing to do for those with the new exception handling. if (!ExecutionManager::IsManagedCode((PCODE)pDispatcherContext->ControlPc)) { return ExceptionContinueSearch; } +#endif if (pThread->HasThreadStateNC(Thread::TSNC_UnhandledException2ndPass) && !(pExceptionRecord->ExceptionFlags & EXCEPTION_UNWINDING)) { @@ -632,6 +648,7 @@ ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord, } #ifndef HOST_UNIX + // First pass (filtering) if (!(pExceptionRecord->ExceptionFlags & EXCEPTION_UNWINDING)) { // If the exception is a breakpoint, let it go. The managed exception handling @@ -648,28 +665,33 @@ ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord, EEPOLICY_HANDLE_FATAL_ERROR(pExceptionRecord->ExceptionCode); } +#ifdef TARGET_X86 + CallRtlUnwind((PEXCEPTION_REGISTRATION_RECORD)pEstablisherFrame, NULL, pExceptionRecord, 0); +#else ClrUnwindEx(pExceptionRecord, (UINT_PTR)pThread, INVALID_RESUME_ADDRESS, pDispatcherContext->EstablisherFrame); + UNREACHABLE(); +#endif + } + + // Second pass (handling) + GCX_COOP(); + ThreadExceptionState* pExState = pThread->GetExceptionState(); + ExInfo *pPrevExInfo = (ExInfo*)pExState->GetCurrentExceptionTracker(); + if (pPrevExInfo != NULL && pPrevExInfo->m_DebuggerExState.GetDebuggerInterceptContext() != NULL) + { + ContinueExceptionInterceptionUnwind(); + UNREACHABLE(); } else { - GCX_COOP(); - ThreadExceptionState* pExState = pThread->GetExceptionState(); - ExInfo *pPrevExInfo = (ExInfo*)pExState->GetCurrentExceptionTracker(); - if (pPrevExInfo != NULL && pPrevExInfo->m_DebuggerExState.GetDebuggerInterceptContext() != NULL) - { - ContinueExceptionInterceptionUnwind(); - UNREACHABLE(); - } - else - { - OBJECTREF oref = ExceptionTracker::CreateThrowable(pExceptionRecord, FALSE); - DispatchManagedException(oref, pContextRecord, pExceptionRecord); - } + OBJECTREF oref = ExceptionTracker::CreateThrowable(pExceptionRecord, FALSE); + DispatchManagedException(oref, pContextRecord, pExceptionRecord); } #endif // !HOST_UNIX + EEPOLICY_HANDLE_FATAL_ERROR_WITH_MESSAGE(COR_E_EXECUTIONENGINE, _T("SEH exception leaked into managed code")); UNREACHABLE(); } @@ -1754,7 +1776,7 @@ VOID DECLSPEC_NORETURN DispatchManagedException(OBJECTREF throwable) STATIC_CONTRACT_MODE_COOPERATIVE; CONTEXT exceptionContext; - RtlCaptureContext(&exceptionContext); + ClrCaptureContext(&exceptionContext); DispatchManagedException(throwable, &exceptionContext); UNREACHABLE(); @@ -1915,7 +1937,7 @@ void TrackerAllocator::FreeTrackerMemory(ExceptionTracker* pTracker) InterlockedExchangeT(&(pTracker->m_pThread), NULL); } -#ifdef TARGET_WINDOWS +#if defined(TARGET_WINDOWS) && !defined(TARGET_X86) // This is Windows specific implementation as it is based upon the notion of collided unwind that is specific // to Windows 64bit. // @@ -2137,8 +2159,7 @@ HijackHandler(IN PEXCEPTION_RECORD pExceptionRecord, return ExceptionCollidedUnwind; } - -#endif // !TARGET_WINDOWS +#endif // TARGET_WINDOWS && !TARGET_X86 #ifdef _DEBUG // IsSafeToUnwindFrameChain: @@ -2262,7 +2283,7 @@ UnhandledExceptionHandlerUnix( #else // TARGET_UNIX -EXTERN_C EXCEPTION_DISPOSITION +EXTERN_C EXCEPTION_DISPOSITION __cdecl UMThunkUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord, IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, @@ -2306,7 +2327,7 @@ UMThunkUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord, return ExceptionContinueSearch; } -EXTERN_C EXCEPTION_DISPOSITION +EXTERN_C EXCEPTION_DISPOSITION __cdecl UMEntryPrestubUnwindFrameChainHandler( IN PEXCEPTION_RECORD pExceptionRecord, IN PVOID pEstablisherFrame, @@ -2324,10 +2345,9 @@ UMEntryPrestubUnwindFrameChainHandler( return disposition; } - // This is the personality routine setup for the assembly helper (CallDescrWorker) that calls into // managed code. -EXTERN_C EXCEPTION_DISPOSITION +EXTERN_C EXCEPTION_DISPOSITION __cdecl CallDescrWorkerUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord, IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, @@ -2373,7 +2393,7 @@ CallDescrWorkerUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionReco #endif // TARGET_UNIX #ifdef FEATURE_COMINTEROP -EXTERN_C EXCEPTION_DISPOSITION +EXTERN_C EXCEPTION_DISPOSITION __cdecl ReverseComUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord, IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, @@ -2388,8 +2408,8 @@ ReverseComUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord, } #endif // FEATURE_COMINTEROP -#ifndef TARGET_UNIX -EXTERN_C EXCEPTION_DISPOSITION +#if !defined(TARGET_UNIX) && !defined(TARGET_X86) +EXTERN_C EXCEPTION_DISPOSITION __cdecl FixRedirectContextHandler( IN PEXCEPTION_RECORD pExceptionRecord, IN PVOID pEstablisherFrame, @@ -2421,7 +2441,7 @@ FixRedirectContextHandler( // (which was broken when we whacked the IP to get control over the thread) return ExceptionCollidedUnwind; } -#endif // !TARGET_UNIX +#endif // !TARGET_UNIX && !TARGET_X86 #endif // DACCESS_COMPILE void ExceptionTrackerBase::StackRange::Reset() @@ -3371,6 +3391,7 @@ VOID DECLSPEC_NORETURN PropagateLongJmpThroughNativeFrames(jmp_buf *pJmpBuf, int // from the target context without removing the frames from the stack. Those frames // can contain e.g. a C++ exception object that needs to be preserved during the exception // propagation. +#if !defined(HOST_X86) EXTERN_C EXCEPTION_DISPOSITION PropagateForeignExceptionThroughNativeFrames(IN PEXCEPTION_RECORD pExceptionRecord, IN PVOID pEstablisherFrame, @@ -3388,6 +3409,7 @@ PropagateForeignExceptionThroughNativeFrames(IN PEXCEPTION_RECORD pExcepti RaiseException(pExceptionToPropagateRecord->ExceptionCode, pExceptionToPropagateRecord->ExceptionFlags, pExceptionToPropagateRecord->NumberParameters, pExceptionToPropagateRecord->ExceptionInformation); UNREACHABLE(); } +#endif #endif // HOST_WINDOWS @@ -3574,6 +3596,12 @@ extern "C" void * QCALLTYPE CallCatchFunclet(QCall::ObjectHandleOnStack exceptio #ifdef HOST_WINDOWS if ((pLongJmpBuf == NULL) && !IsComPlusException(&lastExceptionRecord) && MapWin32FaultToCOMPlusException(&lastExceptionRecord) == kSEHException) { +#if defined(HOST_X86) + PopSEHRecords((void *)GetSP(pvRegDisplay->pCurrentContext)); + GCX_PREEMP_NO_DTOR(); + RaiseException(lastExceptionRecord.ExceptionCode, lastExceptionRecord.ExceptionFlags, lastExceptionRecord.NumberParameters, lastExceptionRecord.ExceptionInformation); + UNREACHABLE(); +#else // Propagate an external exception to the caller context. This is done in a special way, since the native stack // frames below the caller context may contain e.g. C++ exception object that the external exception references. // So we rely on a special mode of the RtlRestoreContext with EXCEPTION_RECORD passed in with STATUS_UNWIND_CONSOLIDATE @@ -3584,6 +3612,7 @@ extern "C" void * QCALLTYPE CallCatchFunclet(QCall::ObjectHandleOnStack exceptio exceptionRecord.ExceptionInformation[0] = (ULONG_PTR)PropagateForeignExceptionThroughNativeFrames; exceptionRecord.ExceptionInformation[1] = (ULONG_PTR)&lastExceptionRecord; RtlRestoreContext(pvRegDisplay->pCurrentContext, &exceptionRecord); +#endif } #endif // HOST_WINDOWS @@ -3596,9 +3625,22 @@ extern "C" void * QCALLTYPE CallCatchFunclet(QCall::ObjectHandleOnStack exceptio targetSSP -= sizeof(size_t); } #endif // HOST_WINDOWS + SetSP(pvRegDisplay->pCurrentContext, targetSp - 8); #elif defined(HOST_X86) - ULONG32* returnAddress = (ULONG32*)targetSp; + +#ifdef HOST_WINDOWS + // Disarm the managed code SEH handler installed in CallDescrWorkerInternal + if (IsCallDescrWorkerInternalReturnAddress(pvRegDisplay->pCurrentContext->Eip)) + { + PEXCEPTION_REGISTRATION_RECORD currentContext = GetCurrentSEHRecord(); + if (currentContext->Handler == (PEXCEPTION_ROUTINE)ProcessCLRException) + currentContext->Handler = (PEXCEPTION_ROUTINE)CallDescrWorkerUnwindFrameChainHandler; + } +#endif + + ULONG32* returnAddress = (ULONG32*)(targetSp - 4); *returnAddress = pvRegDisplay->pCurrentContext->Eip; + SetSP(pvRegDisplay->pCurrentContext, targetSp - 4); #elif defined(HOST_ARM64) pvRegDisplay->pCurrentContext->Lr = GetIP(pvRegDisplay->pCurrentContext); #elif defined(HOST_ARM) @@ -3606,11 +3648,6 @@ extern "C" void * QCALLTYPE CallCatchFunclet(QCall::ObjectHandleOnStack exceptio #elif defined(HOST_RISCV64) || defined(HOST_LOONGARCH64) pvRegDisplay->pCurrentContext->Ra = GetIP(pvRegDisplay->pCurrentContext); #endif -#if defined(HOST_AMD64) - SetSP(pvRegDisplay->pCurrentContext, targetSp - 8); -#elif defined(HOST_X86) - SetSP(pvRegDisplay->pCurrentContext, targetSp - 4); -#endif // The SECOND_ARG_REG is defined only for Windows, it is used to handle longjmp propagation over managed frames #ifdef HOST_AMD64 @@ -3622,6 +3659,7 @@ extern "C" void * QCALLTYPE CallCatchFunclet(QCall::ObjectHandleOnStack exceptio #endif #elif defined(HOST_X86) #define FIRST_ARG_REG Ecx +#define SECOND_ARG_REG Edx #elif defined(HOST_ARM64) #define FIRST_ARG_REG X0 #define SECOND_ARG_REG X1 @@ -4267,8 +4305,6 @@ static StackWalkAction MoveToNextNonSkippedFrame(StackFrameIterator* pStackFrame return retVal; } -bool IsCallDescrWorkerInternalReturnAddress(PCODE pCode); - extern "C" CLR_BOOL QCALLTYPE SfiNext(StackFrameIterator* pThis, uint* uExCollideClauseIdx, CLR_BOOL* fUnwoundReversePInvoke, CLR_BOOL* pfIsExceptionIntercepted) { QCALL_CONTRACT; diff --git a/src/coreclr/vm/exceptionhandling.h b/src/coreclr/vm/exceptionhandling.h index 15f59d50d515c5..38fe013fd0ee7b 100644 --- a/src/coreclr/vm/exceptionhandling.h +++ b/src/coreclr/vm/exceptionhandling.h @@ -16,7 +16,7 @@ // Accessing it will result in AV. #define INVALID_RESUME_ADDRESS 0x000000000000bad0 -EXTERN_C EXCEPTION_DISPOSITION +EXTERN_C EXCEPTION_DISPOSITION __cdecl ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord, IN PVOID pEstablisherFrame, IN OUT PT_CONTEXT pContextRecord, diff --git a/src/coreclr/vm/frames.h b/src/coreclr/vm/frames.h index e13a411a8d4ae4..dfa33ac950eda9 100644 --- a/src/coreclr/vm/frames.h +++ b/src/coreclr/vm/frames.h @@ -2599,8 +2599,12 @@ struct ReversePInvokeFrame { Thread* currentThread; MethodDesc* pMD; +#if defined(TARGET_X86) && defined(TARGET_WINDOWS) #ifndef FEATURE_EH_FUNCLETS FrameHandlerExRecord record; +#else + EXCEPTION_REGISTRATION_RECORD m_ExReg; +#endif #endif }; diff --git a/src/coreclr/vm/i386/asmconstants.h b/src/coreclr/vm/i386/asmconstants.h index d3f19959bb52a7..b80b459c52e548 100644 --- a/src/coreclr/vm/i386/asmconstants.h +++ b/src/coreclr/vm/i386/asmconstants.h @@ -203,6 +203,7 @@ ASMCONSTANTS_C_ASSERT(SIZEOF_InterfaceInfo_t == sizeof(InterfaceInfo_t)) #ifdef FEATURE_COMINTEROP +#ifndef FEATURE_EH_FUNCLETS #define SIZEOF_FrameHandlerExRecord 0x0c #define OFFSETOF__FrameHandlerExRecord__m_ExReg__Next 0 #define OFFSETOF__FrameHandlerExRecord__m_ExReg__Handler 4 @@ -211,6 +212,7 @@ ASMCONSTANTS_C_ASSERT(SIZEOF_FrameHandlerExRecord == sizeof(FrameHandlerExRecord ASMCONSTANTS_C_ASSERT(OFFSETOF__FrameHandlerExRecord__m_ExReg__Next == offsetof(FrameHandlerExRecord, m_ExReg) + offsetof(EXCEPTION_REGISTRATION_RECORD, Next)) ASMCONSTANTS_C_ASSERT(OFFSETOF__FrameHandlerExRecord__m_ExReg__Handler == offsetof(FrameHandlerExRecord, m_ExReg) + offsetof(EXCEPTION_REGISTRATION_RECORD, Handler)) ASMCONSTANTS_C_ASSERT(OFFSETOF__FrameHandlerExRecord__m_pEntryFrame == offsetof(FrameHandlerExRecord, m_pEntryFrame)) +#endif #ifdef _DEBUG #ifndef STACK_OVERWRITE_BARRIER_SIZE diff --git a/src/coreclr/vm/i386/asmhelpers.asm b/src/coreclr/vm/i386/asmhelpers.asm index ed3ecbd575d363..2cd464fe93a88d 100644 --- a/src/coreclr/vm/i386/asmhelpers.asm +++ b/src/coreclr/vm/i386/asmhelpers.asm @@ -27,10 +27,19 @@ endif ifdef FEATURE_HIJACK EXTERN _OnHijackWorker@4:PROC endif ;FEATURE_HIJACK +ifdef FEATURE_EH_FUNCLETS +EXTERN _ProcessCLRException:PROC +EXTERN _UMEntryPrestubUnwindFrameChainHandler:PROC +EXTERN _CallDescrWorkerUnwindFrameChainHandler:PROC +ifdef FEATURE_COMINTEROP +EXTERN _ReverseComUnwindFrameChainHandler:PROC +endif ; FEATURE_COMINTEROP +else EXTERN _COMPlusFrameHandler:PROC ifdef FEATURE_COMINTEROP EXTERN _COMPlusFrameHandlerRevCom:PROC endif ; FEATURE_COMINTEROP +endif ; FEATURE_EH_FUNCLETS EXTERN __alloca_probe:PROC EXTERN _NDirectImportWorker@4:PROC @@ -71,6 +80,11 @@ EXTERN g_chained_lookup_miss_counter:DWORD EXTERN g_dispatch_cache_chain_success_counter:DWORD endif +ifdef FEATURE_EH_FUNCLETS +EXTERN @IL_Throw_x86@8:PROC +EXTERN @IL_Rethrow_x86@4:PROC +endif ; FEATURE_EH_FUNCLETS + UNREFERENCED macro arg local unref unref equ size arg @@ -85,6 +99,7 @@ FASTCALL_ENDFUNC macro FuncNameReal endp endm +ifndef FEATURE_EH_FUNCLETS ifdef FEATURE_COMINTEROP ifdef _DEBUG CPFH_STACK_SIZE equ SIZEOF_FrameHandlerExRecord + STACK_OVERWRITE_BARRIER_SIZE*4 @@ -129,6 +144,38 @@ endif endm ; POP_CPFH_FOR_COM endif ; FEATURE_COMINTEROP +PUSH_CLR_EXCEPTION_HANDLER macro handlerName +endm +POP_CLR_EXCEPTION_HANDLER macro +endm + +else ; FEATURE_EH_FUNCLETS + +CPFH_STACK_SIZE equ 8 + +PUSH_CLR_EXCEPTION_HANDLER macro handlerName + ; setup frame exception handler + push handlerName + push fs:[0] + mov fs:[0], esp +endm + +POP_CLR_EXCEPTION_HANDLER macro + ; remove frame exception handler + pop fs:[0] + add esp, 4 +endm + +PUSH_CPFH_FOR_COM macro trashReg, pFrameBaseReg, pFrameOffset + PUSH_CLR_EXCEPTION_HANDLER _ProcessCLRException +endm + +POP_CPFH_FOR_COM macro trashReg + POP_CLR_EXCEPTION_HANDLER +endm + +endif ; FEATURE_EH_FUNCLETS + ; ; FramedMethodFrame prolog ; @@ -243,19 +290,29 @@ _RestoreFPUContext@4 ENDP ; Note that these directives must be in a file that defines symbols that will be used during linking, ; otherwise it's possible that the resulting .obj will completely be ignored by the linker and these ; directives will have no effect. +ifndef FEATURE_EH_FUNCLETS COMPlusFrameHandler proto c .safeseh COMPlusFrameHandler - COMPlusNestedExceptionHandler proto c .safeseh COMPlusNestedExceptionHandler - FastNExportExceptHandler proto c .safeseh FastNExportExceptHandler - ifdef FEATURE_COMINTEROP COMPlusFrameHandlerRevCom proto c .safeseh COMPlusFrameHandlerRevCom endif +else ; FEATURE_EH_FUNCLETS +ProcessCLRException proto c +.safeseh ProcessCLRException +UMEntryPrestubUnwindFrameChainHandler proto c +.safeseh UMEntryPrestubUnwindFrameChainHandler +CallDescrWorkerUnwindFrameChainHandler proto c +.safeseh CallDescrWorkerUnwindFrameChainHandler +ifdef FEATURE_COMINTEROP +ReverseComUnwindFrameChainHandler proto c +.safeseh ReverseComUnwindFrameChainHandler +endif ; FEATURE_COMINTEROP +endif ; FEATURE_EH_FUNCLETS ifdef HAS_ADDRESS_SANITIZER EXTERN ___asan_handle_no_return:PROC @@ -277,6 +334,7 @@ CallRtlUnwind PROC stdcall public USES ebx esi edi, pEstablisherFrame :DWORD, ca RET CallRtlUnwind ENDP +ifndef FEATURE_EH_FUNCLETS _ResumeAtJitEHHelper@4 PROC public ; Call ___asan_handle_no_return here as we are not going to return. ifdef HAS_ADDRESS_SANITIZER @@ -398,6 +456,7 @@ endif pop ebp ; don't use 'leave' here, as ebp as been trashed retn 8 _CallJitEHFinallyHelper@8 ENDP +endif ;------------------------------------------------------------------------------ ; This helper routine enregisters the appropriate arguments and makes the @@ -409,6 +468,11 @@ CallDescrWorkerInternal PROC stdcall public USES EBX, mov ebx, pParams + ; We are about to run managed code so we need to put an exception + ; handler on the SEH stack; Note that ThePreStub and CallCatchFunclet + ; may replace where the handler points to! + PUSH_CLR_EXCEPTION_HANDLER _ProcessCLRException + mov ecx, [ebx+CallDescrData__numStackSlots] mov eax, [ebx+CallDescrData__pSrc] ; copy the stack test ecx, ecx @@ -434,6 +498,8 @@ donestack: mov ecx, dword ptr [eax+4] call [ebx+CallDescrData__pTarget] + +CallDescrWorkerInternalReturnAddress: ifdef _DEBUG nop ; This is a tag that we use in an assert. Fcalls expect to ; be called from Jitted code or from certain blessed call sites like @@ -457,7 +523,8 @@ ReturnsInt: mov [ebx+CallDescrData__returnValue+4], edx Epilog: - RET + POP_CLR_EXCEPTION_HANDLER + ret ReturnsFloat: fstp dword ptr [ebx+CallDescrData__returnValue] ; Spill the Float return value @@ -467,6 +534,10 @@ ReturnsDouble: fstp qword ptr [ebx+CallDescrData__returnValue] ; Spill the Double return value jmp Epilog +public _CallDescrWorkerInternalReturnAddressOffset +_CallDescrWorkerInternalReturnAddressOffset: + dd CallDescrWorkerInternalReturnAddress - CallDescrWorkerInternal + CallDescrWorkerInternal endp ifdef _DEBUG @@ -1134,7 +1205,29 @@ _ThePreStub@0 proc public push esi +ifdef FEATURE_EH_FUNCLETS + cmp [esi + 24], CallDescrWorkerInternalReturnAddress + jne NoSEHReplace + + ; If we were called from CallDescrWorkerInternal then swap the last + ; SEH registration for _CallDescrWorkerUnwindFrameChainHandler to ensure + ; that class loading exceptions are propagated through unmanaged code + ; before being forwarded to the managed one. + mov edi, fs:[0] + ; mov esi, [edi] + ; mov fs:[0], esi + mov [edi + 4], _CallDescrWorkerUnwindFrameChainHandler call _PreStubWorker@8 + mov [edi + 4], _ProcessCLRException + ; mov fs:[0], edi + jmp AfterPreStubWorker + +NoSEHReplace: +endif ; FEATURE_EH_FUNCLETS + + call _PreStubWorker@8 + +AfterPreStubWorker: ; eax now contains replacement stub. PreStubWorker will never return ; NULL (it throws an exception if stub creation fails.) @@ -1167,9 +1260,13 @@ _TheUMEntryPrestub@0 proc public push ecx push edx + PUSH_CLR_EXCEPTION_HANDLER _UMEntryPrestubUnwindFrameChainHandler + push eax ; UMEntryThunkData* call _TheUMEntryPrestubWorker@4 + POP_CLR_EXCEPTION_HANDLER + ; pop argument registers pop edx pop ecx @@ -1236,9 +1333,15 @@ _GenericComCallStub@0 proc public push eax ; UnmanagedToManagedFrame::m_pvDatum = ComCallMethodDesc* sub esp, OFFSETOF__UnmanagedToManagedFrame__m_pvDatum - push esp + mov esi, esp + + PUSH_CLR_EXCEPTION_HANDLER _ReverseComUnwindFrameChainHandler + + push esi call _COMToCLRWorker@4 + POP_CLR_EXCEPTION_HANDLER + add esp, OFFSETOF__UnmanagedToManagedFrame__m_pvDatum ; pop the ComCallMethodDesc* @@ -1248,6 +1351,7 @@ _GenericComCallStub@0 proc public pop edi pop esi pop ebx + pop ebp sub ecx, COMMETHOD_PREPAD_ASM diff --git a/src/coreclr/vm/i386/cgenx86.cpp b/src/coreclr/vm/i386/cgenx86.cpp index 27384caffcf751..0446f296df5c7b 100644 --- a/src/coreclr/vm/i386/cgenx86.cpp +++ b/src/coreclr/vm/i386/cgenx86.cpp @@ -551,11 +551,17 @@ void InlinedCallFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateF DWORD stackArgSize = 0; #if !defined(UNIX_X86_ABI) - stackArgSize = (DWORD) dac_cast(m_Datum); + TADDR datum = dac_cast(m_Datum); + +#ifdef FEATURE_EH_FUNCLETS + datum &= ~(TADDR)InlinedCallFrameMarker::Mask; +#endif + + stackArgSize = (DWORD)datum; if (stackArgSize & ~0xFFFF) { - NDirectMethodDesc * pMD = PTR_NDirectMethodDesc(m_Datum); + NDirectMethodDesc * pMD = PTR_NDirectMethodDesc(datum); /* if this is not an NDirect frame, something is really wrong */ diff --git a/src/coreclr/vm/i386/ehhelpers.asm b/src/coreclr/vm/i386/ehhelpers.asm new file mode 100644 index 00000000000000..ddae1ff34a7577 --- /dev/null +++ b/src/coreclr/vm/i386/ehhelpers.asm @@ -0,0 +1,101 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. + + .586 + .model flat + +include asmconstants.inc + + assume fs: nothing + option casemap:none + .code + +ifdef FEATURE_EH_FUNCLETS + +; DWORD_PTR STDCALL CallEHFunclet(Object *pThrowable, UINT_PTR pFuncletToInvoke, UINT_PTR *pFirstNonVolReg, UINT_PTR *pFuncletCallerSP); +; ESP based frame +_CallEHFunclet@16 proc public + + push ebp + push ebx + push esi + push edi + + lea ebp, [esp + 3*4] + + ; On entry: + ; + ; [ebp+ 8] = throwable + ; [ebp+12] = PC to invoke + ; [ebp+16] = address of EDI register in CONTEXT record ; used to restore the non-volatile registers of CrawlFrame + ; [ebp+20] = address of the location where the SP of funclet's caller (i.e. this helper) should be saved. + ; + + ; Save the SP of this function + mov eax, [ebp + 20] + mov [eax], esp + ; Save the funclet PC for later call + mov edx, [ebp + 12] + ; Pass throwable object to funclet + mov eax, [ebp + 8] + ; Restore non-volatiles registers + mov ecx, [ebp + 16] + mov edi, [ecx] + mov esi, [ecx + 4] + mov ebx, [ecx + 8] + mov ebp, [ecx + 24] + ; Invoke the funclet + call edx + + pop edi + pop esi + pop ebx + pop ebp + + ret 16 + +_CallEHFunclet@16 endp + +; DWORD_PTR STDCALL CallEHFilterFunclet(Object *pThrowable, TADDR CallerSP, UINT_PTR pFuncletToInvoke, UINT_PTR *pFuncletCallerSP); +; ESP based frame +_CallEHFilterFunclet@16 proc public + + push ebp + push ebx + push esi + push edi + + lea ebp, [esp + 3*4] + + ; On entry: + ; + ; [ebp+ 8] = throwable + ; [ebp+12] = FP to restore + ; [ebp+16] = PC to invoke + ; [ebp+20] = address of the location where the SP of funclet's caller (i.e. this helper) should be saved. + ; + + ; Save the SP of this function + mov eax, [ebp + 20] + mov [eax], esp + ; Save the funclet PC for later call + mov edx, [ebp + 16] + ; Pass throwable object to funclet + mov eax, [ebp + 8] + ; Restore FP + mov ebp, [ebp + 12] + ; Invoke the funclet + call edx + + pop edi + pop esi + pop ebx + pop ebp + + ret 16 + +_CallEHFilterFunclet@16 endp + +endif ; FEATURE_EH_FUNCLETS + + end diff --git a/src/coreclr/vm/i386/excepcpu.h b/src/coreclr/vm/i386/excepcpu.h index 779b6d61841a65..6eca4a596ca79a 100644 --- a/src/coreclr/vm/i386/excepcpu.h +++ b/src/coreclr/vm/i386/excepcpu.h @@ -20,9 +20,7 @@ #define STATUS_CLR_GCCOVER_CODE STATUS_PRIVILEGED_INSTRUCTION -#ifndef FEATURE_EH_FUNCLETS -class Thread; - +#ifdef TARGET_WINDOWS #define INSTALL_SEH_RECORD(record) \ { \ (record)->Next = (PEXCEPTION_REGISTRATION_RECORD)__readfsdword(0); \ @@ -33,6 +31,10 @@ class Thread; { \ __writefsdword(0, (DWORD) ((record)->Next)); \ } +#endif // TARGET_WINDOWS + +#ifndef FEATURE_EH_FUNCLETS +class Thread; #define INSTALL_EXCEPTION_HANDLING_RECORD(record) \ { \ diff --git a/src/coreclr/vm/i386/excepx86.cpp b/src/coreclr/vm/i386/excepx86.cpp index 1006eb5510a3b0..8b263257149409 100644 --- a/src/coreclr/vm/i386/excepx86.cpp +++ b/src/coreclr/vm/i386/excepx86.cpp @@ -349,71 +349,6 @@ CPFH_VerifyThreadIsInValidState(Thread* pThread, DWORD exceptionCode, EXCEPTION_ } -#ifdef FEATURE_HIJACK -void -CPFH_AdjustContextForThreadSuspensionRace(CONTEXT *pContext, Thread *pThread) -{ - WRAPPER_NO_CONTRACT; - - PCODE f_IP = GetIP(pContext); - if (Thread::IsAddrOfRedirectFunc((PVOID)f_IP)) { - - // This is a very rare case where we tried to redirect a thread that was - // just about to dispatch an exception, and our update of EIP took, but - // the thread continued dispatching the exception. - // - // If this should happen (very rare) then we fix it up here. - // - _ASSERTE(pThread->GetSavedRedirectContext()); - SetIP(pContext, GetIP(pThread->GetSavedRedirectContext())); - STRESS_LOG1(LF_EH, LL_INFO100, "CPFH_AdjustContextForThreadSuspensionRace: Case 1 setting IP = %x\n", pContext->Eip); - } - - if (f_IP == GetEEFuncEntryPoint(THROW_CONTROL_FOR_THREAD_FUNCTION)) { - - // This is a very rare case where we tried to redirect a thread that was - // just about to dispatch an exception, and our update of EIP took, but - // the thread continued dispatching the exception. - // - // If this should happen (very rare) then we fix it up here. - // - SetIP(pContext, GetIP(pThread->m_OSContext)); - STRESS_LOG1(LF_EH, LL_INFO100, "CPFH_AdjustContextForThreadSuspensionRace: Case 2 setting IP = %x\n", pContext->Eip); - } - -// We have another even rarer race condition: -// - A) On thread A, Debugger puts an int 3 in the code stream at address X -// - A) We hit it and the begin an exception. The eip will be X + 1 (int3 is special) -// - B) Meanwhile, thread B redirects A's eip to Y. (Although A is really somewhere -// in the kernel, it looks like it's still in user code, so it can fall under the -// HandledJitCase and can be redirected) -// - A) The OS, trying to be nice, expects we have a breakpoint exception at X+1, -// but does -1 on the address since it knows int3 will leave the eip +1. -// So the context structure it will pass to the Handler is ideally (X+1)-1 = X -// -// ** Here's the race: Since thread B redirected A, the eip is actually Y (not X+1), -// but the kernel still touches it up to Y-1. So there's a window between when we hit a -// bp and when the handler gets called that this can happen. -// This causes an unhandled BP (since the debugger doesn't recognize the bp at Y-1) -// -// So what to do: If we land at Y-1 (ie, if f_IP+1 is the addr of a Redirected Func), -// then restore the EIP back to X. This will skip the redirection. -// Fortunately, this only occurs in cases where it's ok -// to skip. The debugger will recognize the patch and handle it. - - if (Thread::IsAddrOfRedirectFunc((PVOID)(f_IP + 1))) { - _ASSERTE(pThread->GetSavedRedirectContext()); - SetIP(pContext, GetIP(pThread->GetSavedRedirectContext()) - 1); - STRESS_LOG1(LF_EH, LL_INFO100, "CPFH_AdjustContextForThreadSuspensionRace: Case 3 setting IP = %x\n", pContext->Eip); - } - - if (f_IP + 1 == GetEEFuncEntryPoint(THROW_CONTROL_FOR_THREAD_FUNCTION)) { - SetIP(pContext, GetIP(pThread->m_OSContext) - 1); - STRESS_LOG1(LF_EH, LL_INFO100, "CPFH_AdjustContextForThreadSuspensionRace: Case 4 setting IP = %x\n", pContext->Eip); - } -} -#endif // FEATURE_HIJACK - uint32_t g_exceptionCount; //****************************************************************************** @@ -1826,13 +1761,6 @@ LPVOID STDCALL COMPlusEndCatch(LPVOID ebp, DWORD ebx, DWORD edi, DWORD esi, LPVO return esp; } -PEXCEPTION_REGISTRATION_RECORD GetCurrentSEHRecord() -{ - WRAPPER_NO_CONTRACT; - - return (PEXCEPTION_REGISTRATION_RECORD)__readfsdword(0); -} - PEXCEPTION_REGISTRATION_RECORD GetFirstCOMPlusSEHRecord(Thread *pThread) { WRAPPER_NO_CONTRACT; EXCEPTION_REGISTRATION_RECORD *pEHR = *(pThread->GetExceptionListPtr()); @@ -1863,28 +1791,6 @@ PEXCEPTION_REGISTRATION_RECORD GetPrevSEHRecord(EXCEPTION_REGISTRATION_RECORD *n return pBest; } -VOID SetCurrentSEHRecord(EXCEPTION_REGISTRATION_RECORD *pSEH) -{ - WRAPPER_NO_CONTRACT; - - __writefsdword(0, (DWORD)pSEH); -} - -VOID PopSEHRecords(LPVOID pTargetSP) -{ - STATIC_CONTRACT_NOTHROW; - STATIC_CONTRACT_GC_NOTRIGGER; - - PEXCEPTION_REGISTRATION_RECORD currentContext = GetCurrentSEHRecord(); - // The last record in the chain is EXCEPTION_CHAIN_END which is defined as maxiumum - // pointer value so it cannot satisfy the loop condition. - while (currentContext < pTargetSP) - { - currentContext = currentContext->Next; - } - SetCurrentSEHRecord(currentContext); -} - // // Unwind pExinfo, pops FS:[0] handlers until the interception context SP, and // resumes at interception context. @@ -3444,4 +3350,100 @@ AdjustContextForVirtualStub( return TRUE; } +#ifdef TARGET_WINDOWS + +PEXCEPTION_REGISTRATION_RECORD GetCurrentSEHRecord() +{ + WRAPPER_NO_CONTRACT; + + return (PEXCEPTION_REGISTRATION_RECORD)__readfsdword(0); +} + +VOID SetCurrentSEHRecord(EXCEPTION_REGISTRATION_RECORD *pSEH) +{ + WRAPPER_NO_CONTRACT; + + __writefsdword(0, (DWORD)pSEH); +} + +VOID PopSEHRecords(LPVOID pTargetSP) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + PEXCEPTION_REGISTRATION_RECORD currentContext = GetCurrentSEHRecord(); + // The last record in the chain is EXCEPTION_CHAIN_END which is defined as maxiumum + // pointer value so it cannot satisfy the loop condition. + while (currentContext < pTargetSP) + { + currentContext = currentContext->Next; + } + SetCurrentSEHRecord(currentContext); +} + +#ifdef FEATURE_HIJACK +void +CPFH_AdjustContextForThreadSuspensionRace(CONTEXT *pContext, Thread *pThread) +{ + WRAPPER_NO_CONTRACT; + + PCODE f_IP = GetIP(pContext); + if (Thread::IsAddrOfRedirectFunc((PVOID)f_IP)) { + + // This is a very rare case where we tried to redirect a thread that was + // just about to dispatch an exception, and our update of EIP took, but + // the thread continued dispatching the exception. + // + // If this should happen (very rare) then we fix it up here. + // + _ASSERTE(pThread->GetSavedRedirectContext()); + SetIP(pContext, GetIP(pThread->GetSavedRedirectContext())); + STRESS_LOG1(LF_EH, LL_INFO100, "CPFH_AdjustContextForThreadSuspensionRace: Case 1 setting IP = %x\n", pContext->Eip); + } + + if (f_IP == GetEEFuncEntryPoint(THROW_CONTROL_FOR_THREAD_FUNCTION)) { + + // This is a very rare case where we tried to redirect a thread that was + // just about to dispatch an exception, and our update of EIP took, but + // the thread continued dispatching the exception. + // + // If this should happen (very rare) then we fix it up here. + // + SetIP(pContext, GetIP(pThread->m_OSContext)); + STRESS_LOG1(LF_EH, LL_INFO100, "CPFH_AdjustContextForThreadSuspensionRace: Case 2 setting IP = %x\n", pContext->Eip); + } + +// We have another even rarer race condition: +// - A) On thread A, Debugger puts an int 3 in the code stream at address X +// - A) We hit it and the begin an exception. The eip will be X + 1 (int3 is special) +// - B) Meanwhile, thread B redirects A's eip to Y. (Although A is really somewhere +// in the kernel, it looks like it's still in user code, so it can fall under the +// HandledJitCase and can be redirected) +// - A) The OS, trying to be nice, expects we have a breakpoint exception at X+1, +// but does -1 on the address since it knows int3 will leave the eip +1. +// So the context structure it will pass to the Handler is ideally (X+1)-1 = X +// +// ** Here's the race: Since thread B redirected A, the eip is actually Y (not X+1), +// but the kernel still touches it up to Y-1. So there's a window between when we hit a +// bp and when the handler gets called that this can happen. +// This causes an unhandled BP (since the debugger doesn't recognize the bp at Y-1) +// +// So what to do: If we land at Y-1 (ie, if f_IP+1 is the addr of a Redirected Func), +// then restore the EIP back to X. This will skip the redirection. +// Fortunately, this only occurs in cases where it's ok +// to skip. The debugger will recognize the patch and handle it. + + if (Thread::IsAddrOfRedirectFunc((PVOID)(f_IP + 1))) { + _ASSERTE(pThread->GetSavedRedirectContext()); + SetIP(pContext, GetIP(pThread->GetSavedRedirectContext()) - 1); + STRESS_LOG1(LF_EH, LL_INFO100, "CPFH_AdjustContextForThreadSuspensionRace: Case 3 setting IP = %x\n", pContext->Eip); + } + + if (f_IP + 1 == GetEEFuncEntryPoint(THROW_CONTROL_FOR_THREAD_FUNCTION)) { + SetIP(pContext, GetIP(pThread->m_OSContext) - 1); + STRESS_LOG1(LF_EH, LL_INFO100, "CPFH_AdjustContextForThreadSuspensionRace: Case 4 setting IP = %x\n", pContext->Eip); + } +} +#endif // FEATURE_HIJACK +#endif // TARGET_WINDOWS #endif // !DACCESS_COMPILE diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index dc647f8345f24d..c8049fc4fc684e 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -3121,10 +3121,15 @@ HCIMPL3_RAW(void, JIT_ReversePInvokeEnterTrackTransitions, ReversePInvokeFrame* JIT_ReversePInvokeEnterRare(frame, _ReturnAddress(), GetMethod(handle)->IsILStub() ? ((UMEntryThunkData*)secretArg)->m_pUMEntryThunk : (UMEntryThunk*)NULL); } +#if defined(TARGET_X86) && defined(TARGET_WINDOWS) #ifndef FEATURE_EH_FUNCLETS frame->record.m_pEntryFrame = frame->currentThread->GetFrame(); frame->record.m_ExReg.Handler = (PEXCEPTION_ROUTINE)FastNExportExceptHandler; INSTALL_EXCEPTION_HANDLING_RECORD(&frame->record.m_ExReg); +#else + frame->m_ExReg.Handler = (PEXCEPTION_ROUTINE)ProcessCLRException; + INSTALL_SEH_RECORD(&frame->m_ExReg); +#endif #endif } HCIMPLEND_RAW @@ -3154,10 +3159,15 @@ HCIMPL1_RAW(void, JIT_ReversePInvokeEnter, ReversePInvokeFrame* frame) JIT_ReversePInvokeEnterRare(frame, _ReturnAddress()); } +#if defined(TARGET_X86) && defined(TARGET_WINDOWS) #ifndef FEATURE_EH_FUNCLETS frame->record.m_pEntryFrame = frame->currentThread->GetFrame(); frame->record.m_ExReg.Handler = (PEXCEPTION_ROUTINE)FastNExportExceptHandler; INSTALL_EXCEPTION_HANDLING_RECORD(&frame->record.m_ExReg); +#else + frame->m_ExReg.Handler = (PEXCEPTION_ROUTINE)ProcessCLRException; + INSTALL_SEH_RECORD(&frame->m_ExReg); +#endif #endif } HCIMPLEND_RAW @@ -3172,8 +3182,12 @@ HCIMPL1_RAW(void, JIT_ReversePInvokeExitTrackTransitions, ReversePInvokeFrame* f // to make this exit faster. frame->currentThread->m_fPreemptiveGCDisabled.StoreWithoutBarrier(0); +#if defined(TARGET_X86) && defined(TARGET_WINDOWS) #ifndef FEATURE_EH_FUNCLETS UNINSTALL_EXCEPTION_HANDLING_RECORD(&frame->record.m_ExReg); +#else + UNINSTALL_SEH_RECORD(&frame->m_ExReg); +#endif #endif #ifdef PROFILING_SUPPORTED @@ -3195,8 +3209,12 @@ HCIMPL1_RAW(void, JIT_ReversePInvokeExit, ReversePInvokeFrame* frame) // to make this exit faster. frame->currentThread->m_fPreemptiveGCDisabled.StoreWithoutBarrier(0); +#if defined(TARGET_X86) && defined(TARGET_WINDOWS) #ifndef FEATURE_EH_FUNCLETS UNINSTALL_EXCEPTION_HANDLING_RECORD(&frame->record.m_ExReg); +#else + UNINSTALL_SEH_RECORD(&frame->m_ExReg); +#endif #endif } HCIMPLEND_RAW diff --git a/src/coreclr/vm/readytoruninfo.cpp b/src/coreclr/vm/readytoruninfo.cpp index 487676d3ba2c40..15a771ddd91025 100644 --- a/src/coreclr/vm/readytoruninfo.cpp +++ b/src/coreclr/vm/readytoruninfo.cpp @@ -362,7 +362,7 @@ PTR_MethodDesc ReadyToRunInfo::GetMethodDescForEntryPointInNativeImage(PCODE ent } CONTRACTL_END; -#if defined(TARGET_AMD64) || (defined(TARGET_X86) && defined(TARGET_UNIX)) +#if defined(TARGET_AMD64) || (defined(TARGET_X86) && defined(FEATURE_EH_FUNCLETS)) // A normal method entry point is always 8 byte aligned, but a funclet can start at an odd address. // Since PtrHashMap can't handle odd pointers, check for this case and return NULL. if ((entryPoint & 0x1) != 0) diff --git a/src/coreclr/vm/stackwalk.cpp b/src/coreclr/vm/stackwalk.cpp index 859e87c2d3e551..60e4654a53c855 100644 --- a/src/coreclr/vm/stackwalk.cpp +++ b/src/coreclr/vm/stackwalk.cpp @@ -625,7 +625,7 @@ PCODE Thread::VirtualUnwindLeafCallFrame(T_CONTEXT* pContext) { PCODE uControlPc; -#if defined(_DEBUG) && !defined(TARGET_UNIX) +#if defined(_DEBUG) && defined(TARGET_WINDOWS) && !defined(TARGET_X86) UINT_PTR uImageBase; PT_RUNTIME_FUNCTION pFunctionEntry = RtlLookupFunctionEntry((UINT_PTR)GetIP(pContext), @@ -633,7 +633,7 @@ PCODE Thread::VirtualUnwindLeafCallFrame(T_CONTEXT* pContext) NULL); CONSISTENCY_CHECK(NULL == pFunctionEntry); -#endif // _DEBUG && !TARGET_UNIX +#endif // _DEBUG && TARGET_WINDOWS && !TARGET_X86 #if defined(TARGET_AMD64) diff --git a/src/coreclr/vm/stackwalk.h b/src/coreclr/vm/stackwalk.h index 20b425405e7efd..9a51a69b97e5cf 100644 --- a/src/coreclr/vm/stackwalk.h +++ b/src/coreclr/vm/stackwalk.h @@ -35,14 +35,14 @@ class AppDomain; // on the stack. The FEF is used for unwinding. If not defined, the unwinding // uses the exception context. #define USE_FEF // to mark where code needs to be changed to eliminate the FEF -#if defined(TARGET_X86) && !defined(TARGET_UNIX) +#if defined(TARGET_X86) && !defined(FEATURE_EH_FUNCLETS) #undef USE_FEF // Turn off the FEF use on x86. #define ELIMINATE_FEF #else #if defined(ELIMINATE_FEF) #undef ELIMINATE_FEF #endif -#endif // TARGET_X86 && !TARGET_UNIX +#endif // TARGET_X86 && !FEATURE_EH_FUNCLETS #if defined(FEATURE_EH_FUNCLETS) #define RECORD_RESUMABLE_FRAME_SP diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index a21064d019b663..5838d0454ebc6a 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -7756,27 +7756,30 @@ void Thread::InitializeSpecialUserModeApc() #endif // FEATURE_SPECIAL_USER_MODE_APC -#if !(defined(TARGET_WINDOWS) && defined(TARGET_X86)) #if defined(TARGET_AMD64) EXTERN_C void STDCALL ClrRestoreNonvolatileContextWorker(PCONTEXT ContextRecord, DWORD64 ssp); #endif void ClrRestoreNonvolatileContext(PCONTEXT ContextRecord, size_t targetSSP) { + __asan_handle_no_return(); #if defined(TARGET_AMD64) if (targetSSP == 0) { targetSSP = GetSSP(ContextRecord); } - __asan_handle_no_return(); ClrRestoreNonvolatileContextWorker(ContextRecord, targetSSP); +#elif defined(TARGET_X86) && defined(TARGET_WINDOWS) + // need to pop the SEH records before write over the stack + LPVOID oldSP = (LPVOID)GetSP(ContextRecord); + PopSEHRecords(oldSP); + ResumeAtJit(ContextRecord, oldSP); #else - __asan_handle_no_return(); // Falling back to RtlRestoreContext() for now, though it should be possible to have simpler variants for these cases RtlRestoreContext(ContextRecord, NULL); #endif } -#endif // !(TARGET_WINDOWS && TARGET_X86) + #endif // #ifndef DACCESS_COMPILE #ifdef DACCESS_COMPILE From 76a7c96e65a76e1b2303689b455319ca849ecf92 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Thu, 3 Apr 2025 16:50:13 +0200 Subject: [PATCH 2/2] Update comments --- src/coreclr/vm/exceptionhandling.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index fb57a8cd54ef63..82d486c5ca7fcb 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -648,7 +648,7 @@ ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord, } #ifndef HOST_UNIX - // First pass (filtering) + // First pass (searching) if (!(pExceptionRecord->ExceptionFlags & EXCEPTION_UNWINDING)) { // If the exception is a breakpoint, let it go. The managed exception handling @@ -676,7 +676,7 @@ ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord, #endif } - // Second pass (handling) + // Second pass (unwinding) GCX_COOP(); ThreadExceptionState* pExState = pThread->GetExceptionState(); ExInfo *pPrevExInfo = (ExInfo*)pExState->GetCurrentExceptionTracker();