From 663013d579d5eb3e29b2e0f88aeaed0694e74f39 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Thu, 15 Jul 2021 09:34:36 -0700 Subject: [PATCH] Use alternatives to redirecting threads via IP changes in the context, for a few cases on Windows (#55649) * Use alternatives to redirecting threads via IP changes in the context, for a few cases on Windows - For GC/debugger suspension, a new variant of an APC is used to interrupt the thread - For hardware exceptions, an exception is raised from the vectored exception handler instead of redirecting and then raising an exception - Made changes to enable /guard:ehcont and /cetcompat for binaries, but commented out the enablement for now since it looks like we will first need .pgd files generated with /guard:ehcont and published for release builds to succeed --- eng/native/configurecompiler.cmake | 17 +- src/coreclr/clrdefinitions.cmake | 4 + src/coreclr/debug/ee/debugger.cpp | 28 +- src/coreclr/debug/ee/debugger.h | 10 +- src/coreclr/dlls/clretwrc/CMakeLists.txt | 6 +- src/coreclr/dlls/mscorrc/CMakeLists.txt | 6 +- .../vm/amd64/RedirectedHandledJITCase.asm | 33 +- src/coreclr/vm/amd64/asmconstants.h | 4 + src/coreclr/vm/ceemain.cpp | 1 + src/coreclr/vm/dbginterface.h | 2 +- src/coreclr/vm/excep.cpp | 28 +- src/coreclr/vm/threads.cpp | 55 +++ src/coreclr/vm/threads.h | 120 ++++++- src/coreclr/vm/threadsuspend.cpp | 330 ++++++++++++------ src/libraries/Native/Windows/CMakeLists.txt | 11 +- src/tests/Interop/IJW/IJW.cmake | 5 + 16 files changed, 527 insertions(+), 133 deletions(-) diff --git a/eng/native/configurecompiler.cmake b/eng/native/configurecompiler.cmake index 685949ca33dea..92a715f38d932 100644 --- a/eng/native/configurecompiler.cmake +++ b/eng/native/configurecompiler.cmake @@ -48,7 +48,10 @@ add_compile_definitions("$<$:DEBUG;_DEBUG;_DBG;URTBLDENV_FRIENDL add_compile_definitions("$<$,$>:NDEBUG;URTBLDENV_FRIENDLY=Retail>") if (MSVC) - add_linker_flag(/GUARD:CF) + add_linker_flag(/guard:cf) + #if (CLR_CMAKE_HOST_ARCH_AMD64) + # add_linker_flag(/guard:ehcont) + #endif (CLR_CMAKE_HOST_ARCH_AMD64) # Linker flags # @@ -71,6 +74,10 @@ if (MSVC) set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /IGNORE:4197,4013,4254,4070,4221") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /SUBSYSTEM:WINDOWS,${WINDOWS_SUBSYSTEM_VERSION}") + #if (CLR_CMAKE_HOST_ARCH_AMD64) + # set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /CETCOMPAT") + #endif (CLR_CMAKE_HOST_ARCH_AMD64) + set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} /IGNORE:4221") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG") @@ -565,6 +572,14 @@ if (MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /guard:cf") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /guard:cf") + # Enable EH-continuation table for native components for amd64 builds + # Added using variables instead of add_compile_options to let individual projects override it + #if (CLR_CMAKE_HOST_ARCH_AMD64) + # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /guard:ehcont") + # set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /guard:ehcont") + # set(CMAKE_ASM_MASM_FLAGS "${CMAKE_C_FLAGS} /guard:ehcont") + #endif (CLR_CMAKE_HOST_ARCH_AMD64) + # Statically linked CRT (libcmt[d].lib, libvcruntime[d].lib and libucrt[d].lib) by default. This is done to avoid # linking in VCRUNTIME140.DLL for a simplified xcopy experience by reducing the dependency on VC REDIST. # diff --git a/src/coreclr/clrdefinitions.cmake b/src/coreclr/clrdefinitions.cmake index 0485ff99a99eb..257a9c377e3ba 100644 --- a/src/coreclr/clrdefinitions.cmake +++ b/src/coreclr/clrdefinitions.cmake @@ -228,6 +228,10 @@ if (NOT CLR_CMAKE_TARGET_ARCH_I386 OR NOT CLR_CMAKE_TARGET_WIN32) add_compile_definitions($<$>>:FEATURE_EH_FUNCLETS>) endif (NOT CLR_CMAKE_TARGET_ARCH_I386 OR NOT CLR_CMAKE_TARGET_WIN32) +if (CLR_CMAKE_TARGET_WIN32 AND CLR_CMAKE_TARGET_ARCH_AMD64) + add_definitions(-DFEATURE_SPECIAL_USER_MODE_APC) +endif() + # Use this function to enable building with a specific target OS and architecture set of defines # This is known to work for the set of defines used by the JIT and gcinfo, it is not likely correct for diff --git a/src/coreclr/debug/ee/debugger.cpp b/src/coreclr/debug/ee/debugger.cpp index e4563a31757f4..9f7e286059475 100644 --- a/src/coreclr/debug/ee/debugger.cpp +++ b/src/coreclr/debug/ee/debugger.cpp @@ -992,10 +992,9 @@ Debugger::~Debugger() } #if defined(FEATURE_HIJACK) && !defined(TARGET_UNIX) -typedef void (*PFN_HIJACK_FUNCTION) (void); // Given the start address and the end address of a function, return a MemoryRange for the function. -inline MemoryRange GetMemoryRangeForFunction(PFN_HIJACK_FUNCTION pfnStart, PFN_HIJACK_FUNCTION pfnEnd) +inline MemoryRange GetMemoryRangeForFunction(void *pfnStart, void *pfnEnd) { PCODE pfnStartAddress = (PCODE)GetEEFuncEntryPoint(pfnStart); PCODE pfnEndAddress = (PCODE)GetEEFuncEntryPoint(pfnEnd); @@ -1016,6 +1015,11 @@ MemoryRange Debugger::s_hijackFunction[kMaxHijackFunctions] = GetMemoryRangeForFunction(RedirectedHandledJITCaseForGCStress_Stub, RedirectedHandledJITCaseForGCStress_StubEnd) #endif // HAVE_GCCOVER && TARGET_AMD64 +#ifdef FEATURE_SPECIAL_USER_MODE_APC + , + GetMemoryRangeForFunction(ApcActivationCallbackStub, + ApcActivationCallbackStubEnd) +#endif // FEATURE_SPECIAL_USER_MODE_APC }; #endif // FEATURE_HIJACK && !TARGET_UNIX @@ -15890,7 +15894,7 @@ HRESULT Debugger::UpdateSpecialThreadList(DWORD cThreadArrayLength, // // 3) If the IP is in the prolog or epilog of a managed function. // -BOOL Debugger::IsThreadContextInvalid(Thread *pThread) +BOOL Debugger::IsThreadContextInvalid(Thread *pThread, CONTEXT *pCtx) { CONTRACTL { @@ -15902,14 +15906,22 @@ BOOL Debugger::IsThreadContextInvalid(Thread *pThread) BOOL invalid = FALSE; // Get the thread context. + BOOL success = pCtx != NULL; CONTEXT ctx; - ctx.ContextFlags = CONTEXT_CONTROL; - BOOL success = pThread->GetThreadContext(&ctx); + if (!success) + { + ctx.ContextFlags = CONTEXT_CONTROL; + BOOL success = pThread->GetThreadContext(&ctx); + if (success) + { + pCtx = &ctx; + } + } if (success) { // Check single-step flag - if (IsSSFlagEnabled(reinterpret_cast(&ctx) ARM_ARG(pThread) ARM64_ARG(pThread))) + if (IsSSFlagEnabled(reinterpret_cast(pCtx) ARM_ARG(pThread) ARM64_ARG(pThread))) { // Can't hijack a thread whose SS-flag is set. This could lead to races // with the thread taking the SS-exception. @@ -15923,7 +15935,7 @@ BOOL Debugger::IsThreadContextInvalid(Thread *pThread) { #ifdef TARGET_X86 // Grab Eip - 1 - LPVOID address = (((BYTE*)GetIP(&ctx)) - 1); + LPVOID address = (((BYTE*)GetIP(pCtx)) - 1); EX_TRY { @@ -15934,7 +15946,7 @@ BOOL Debugger::IsThreadContextInvalid(Thread *pThread) if (AddressIsBreakpoint((CORDB_ADDRESS_TYPE*)address)) { size_t prologSize; // Unused... - if (g_pEEInterface->IsInPrologOrEpilog((BYTE*)GetIP(&ctx), &prologSize)) + if (g_pEEInterface->IsInPrologOrEpilog((BYTE*)GetIP(pCtx), &prologSize)) { LOG((LF_CORDB, LL_INFO1000, "D::ITCI: thread is after a BP and in prolog or epilog.\n")); invalid = TRUE; diff --git a/src/coreclr/debug/ee/debugger.h b/src/coreclr/debug/ee/debugger.h index 5503de2459099..4f8b13cf66831 100644 --- a/src/coreclr/debug/ee/debugger.h +++ b/src/coreclr/debug/ee/debugger.h @@ -2505,7 +2505,7 @@ class Debugger : public DebugInterface SIZE_T *rgVal2, BYTE **rgpVCs); - BOOL IsThreadContextInvalid(Thread *pThread); + BOOL IsThreadContextInvalid(Thread *pThread, T_CONTEXT *pCtx); // notification for SQL fiber debugging support void CreateConnection(CONNID dwConnectionId, __in_z WCHAR *wzName); @@ -2875,6 +2875,9 @@ class Debugger : public DebugInterface #if defined(HAVE_GCCOVER) && defined(TARGET_AMD64) kRedirectedForGCStress, #endif // HAVE_GCCOVER && TARGET_AMD64 +#ifdef FEATURE_SPECIAL_USER_MODE_APC + kRedirectedForApcActivation, +#endif // FEATURE_SPECIAL_USER_MODE_APC kMaxHijackFunctions, }; @@ -2976,6 +2979,11 @@ void RedirectedHandledJITCaseForUserSuspend_StubEnd(); void RedirectedHandledJITCaseForGCStress_Stub(); void RedirectedHandledJITCaseForGCStress_StubEnd(); #endif // HAVE_GCCOVER && TARGET_AMD64 + +#ifdef FEATURE_SPECIAL_USER_MODE_APC +void NTAPI ApcActivationCallbackStub(ULONG_PTR Parameter); +void ApcActivationCallbackStubEnd(); +#endif // FEATURE_SPECIAL_USER_MODE_APC }; diff --git a/src/coreclr/dlls/clretwrc/CMakeLists.txt b/src/coreclr/dlls/clretwrc/CMakeLists.txt index cc427efcde488..2b749fd7b3736 100644 --- a/src/coreclr/dlls/clretwrc/CMakeLists.txt +++ b/src/coreclr/dlls/clretwrc/CMakeLists.txt @@ -7,8 +7,12 @@ if(CLR_CMAKE_HOST_WIN32) string(REPLACE "/LTCG" "" CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO ${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO}) string(REPLACE "/LTCG" "" CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO ${CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO}) - # remove /guard:cf from resource-only libraries + # remove /guard:cf, /guard:ehcont, and /CETCOMPAT from resource-only libraries string(REPLACE "/guard:cf" "" CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS}) + if (CLR_CMAKE_HOST_ARCH_AMD64) + string(REPLACE "/guard:ehcont" "" CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS}) + string(REPLACE "/CETCOMPAT" "" CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS}) + endif (CLR_CMAKE_HOST_ARCH_AMD64) set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /NOENTRY") endif(CLR_CMAKE_HOST_WIN32) diff --git a/src/coreclr/dlls/mscorrc/CMakeLists.txt b/src/coreclr/dlls/mscorrc/CMakeLists.txt index c6618f0b77b23..8011997c1e55f 100644 --- a/src/coreclr/dlls/mscorrc/CMakeLists.txt +++ b/src/coreclr/dlls/mscorrc/CMakeLists.txt @@ -8,8 +8,12 @@ if(CLR_CMAKE_HOST_WIN32) string(REPLACE "/LTCG" "" CMAKE_SHARED_LINKER_FLAGS_RELEASE ${CMAKE_SHARED_LINKER_FLAGS_RELEASE}) string(REPLACE "/LTCG" "" CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO ${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO}) - # remove /guard:cf from resource-only libraries + # remove /guard:cf, /guard:ehcont, and /CETCOMPAT from resource-only libraries string(REPLACE "/guard:cf" "" CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS}) + if (CLR_CMAKE_HOST_ARCH_AMD64) + string(REPLACE "/guard:ehcont" "" CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS}) + string(REPLACE "/CETCOMPAT" "" CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS}) + endif (CLR_CMAKE_HOST_ARCH_AMD64) add_library_clr(mscorrc SHARED include.rc diff --git a/src/coreclr/vm/amd64/RedirectedHandledJITCase.asm b/src/coreclr/vm/amd64/RedirectedHandledJITCase.asm index 5a3efffe358c6..0168dddb615c2 100644 --- a/src/coreclr/vm/amd64/RedirectedHandledJITCase.asm +++ b/src/coreclr/vm/amd64/RedirectedHandledJITCase.asm @@ -232,5 +232,36 @@ NESTED_END NakedThrowHelper2, _TEXT GenerateRedirectedStubWithFrame NakedThrowHelper, FixContextHandler, NakedThrowHelper2 - end +ifdef FEATURE_SPECIAL_USER_MODE_APC + +extern ?ApcActivationCallback@Thread@@CAX_K@Z:proc + +; extern "C" void NTAPI ApcActivationCallbackStub(ULONG_PTR Parameter); +NESTED_ENTRY ApcActivationCallbackStub, _TEXT, FixRedirectContextHandler + + push_nonvol_reg rbp + alloc_stack 30h ; padding for alignment, CONTEXT *, callee scratch area + set_frame rbp, 0 + .errnz REDIRECTSTUB_ESTABLISHER_OFFSET_RBP, REDIRECTSTUB_ESTABLISHER_OFFSET_RBP has changed - update asm stubs + END_PROLOGUE + + ; Save the pointer to the interrupted context on the stack for the stack walker + mov rax, [rcx + OFFSETOF__APC_CALLBACK_DATA__ContextRecord] + mov [rbp + 20h], rax + .errnz REDIRECTSTUB_RBP_OFFSET_CONTEXT - 20h, REDIRECTSTUB_RBP_OFFSET_CONTEXT has changed - update asm stubs + call ?ApcActivationCallback@Thread@@CAX_K@Z + + add rsp, 30h + pop rbp + ret + + ; Put a label here to tell the debugger where the end of this function is. + PATCH_LABEL ApcActivationCallbackStubEnd + +NESTED_END ApcActivationCallbackStub, _TEXT + +endif ; FEATURE_SPECIAL_USER_MODE_APC + + + end diff --git a/src/coreclr/vm/amd64/asmconstants.h b/src/coreclr/vm/amd64/asmconstants.h index 4cc6ab1de2aeb..64ff4ebea6c06 100644 --- a/src/coreclr/vm/amd64/asmconstants.h +++ b/src/coreclr/vm/amd64/asmconstants.h @@ -493,6 +493,10 @@ ASMCONSTANTS_C_ASSERT(OFFSET__TEB__ThreadLocalStoragePointer == offsetof(TEB, Th #define THROWSTUB_ESTABLISHER_OFFSET_FaultingExceptionFrame 0x30 +#ifdef FEATURE_SPECIAL_USER_MODE_APC +#define OFFSETOF__APC_CALLBACK_DATA__ContextRecord 0x8 +#endif + #define Thread__ObjectRefFlush ?ObjectRefFlush@Thread@@SAXPEAV1@@Z diff --git a/src/coreclr/vm/ceemain.cpp b/src/coreclr/vm/ceemain.cpp index b60aac924d2e2..87106bbff4b41 100644 --- a/src/coreclr/vm/ceemain.cpp +++ b/src/coreclr/vm/ceemain.cpp @@ -677,6 +677,7 @@ void EEStartupHelper() IfFailGo(ExecutableAllocator::StaticInitialize(FatalErrorHandler)); + Thread::StaticInitialize(); ThreadpoolMgr::StaticInitialize(); MethodDescBackpatchInfoTracker::StaticInitialize(); diff --git a/src/coreclr/vm/dbginterface.h b/src/coreclr/vm/dbginterface.h index ac09c52311a05..4875df7c2070c 100644 --- a/src/coreclr/vm/dbginterface.h +++ b/src/coreclr/vm/dbginterface.h @@ -363,7 +363,7 @@ class DebugInterface SIZE_T *rgVal2, BYTE **rgpVCs) = 0; - virtual BOOL IsThreadContextInvalid(Thread *pThread) = 0; + virtual BOOL IsThreadContextInvalid(Thread *pThread, CONTEXT *pCtx) = 0; // For Just-My-Code (aka Just-User-Code). // The jit inserts probes that look like. diff --git a/src/coreclr/vm/excep.cpp b/src/coreclr/vm/excep.cpp index 6bf5efcc8028c..7ee6baad9a4a6 100644 --- a/src/coreclr/vm/excep.cpp +++ b/src/coreclr/vm/excep.cpp @@ -6971,13 +6971,31 @@ void HandleManagedFault(EXCEPTION_RECORD* pExceptionRecord, WRAPPER_NO_CONTRACT; // Ok. Now we have a brand new fault in jitted code. - g_SavedExceptionInfo.Enter(); - g_SavedExceptionInfo.SaveExceptionRecord(pExceptionRecord); - g_SavedExceptionInfo.SaveContext(pContext); + if (!Thread::UseContextBasedThreadRedirection()) + { + // Once this code path gets enough bake time, perhaps this path could always be used instead of the alternative path to + // redirect the thread + FrameWithCookie frameWithCookie; + FaultingExceptionFrame *frame = &frameWithCookie; + #if defined(FEATURE_EH_FUNCLETS) + *frame->GetGSCookiePtr() = GetProcessGSCookie(); + #endif // FEATURE_EH_FUNCLETS + frame->InitAndLink(pContext); + + SEHException exception(pExceptionRecord); + OBJECTREF managedException = CLRException::GetThrowableFromException(&exception); + RaiseTheExceptionInternalOnly(managedException, FALSE); + } + else + { + g_SavedExceptionInfo.Enter(); + g_SavedExceptionInfo.SaveExceptionRecord(pExceptionRecord); + g_SavedExceptionInfo.SaveContext(pContext); - SetNakedThrowHelperArgRegistersInContext(pContext); + SetNakedThrowHelperArgRegistersInContext(pContext); - SetIP(pContext, GetEEFuncEntryPoint(NakedThrowHelper)); + SetIP(pContext, GetEEFuncEntryPoint(NakedThrowHelper)); + } } #else // USE_FEF && !TARGET_UNIX diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index c6485b86d59c7..b38dc33185afb 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -50,6 +50,10 @@ #include "roapi.h" #endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT +#ifdef FEATURE_SPECIAL_USER_MODE_APC +#include "asmconstants.h" +#endif + static const PortableTailCallFrame g_sentinelTailCallFrame = { NULL, NULL }; TailCallTls::TailCallTls() @@ -1641,6 +1645,7 @@ Thread::Thread() m_currentPrepareCodeConfig = nullptr; m_isInForbidSuspendForDebuggerRegion = false; + m_hasPendingActivation = false; #ifdef _DEBUG memset(dangerousObjRefs, 0, sizeof(dangerousObjRefs)); @@ -8304,7 +8309,57 @@ BOOL dbgOnly_IsSpecialEEThread() #endif // _DEBUG +void Thread::StaticInitialize() +{ + WRAPPER_NO_CONTRACT; + +#ifdef FEATURE_SPECIAL_USER_MODE_APC + InitializeSpecialUserModeApc(); + + // When CET shadow stacks are enabled, support for special user-mode APCs with the necessary functionality is required + _ASSERTE_ALL_BUILDS(__FILE__, !AreCetShadowStacksEnabled() || UseSpecialUserModeApc()); +#endif +} + +#ifdef FEATURE_SPECIAL_USER_MODE_APC + +QueueUserAPC2Proc Thread::s_pfnQueueUserAPC2Proc; + +static void NTAPI EmptyApcCallback(ULONG_PTR Parameter) +{ + LIMITED_METHOD_CONTRACT; +} + +void Thread::InitializeSpecialUserModeApc() +{ + WRAPPER_NO_CONTRACT; + static_assert_no_msg(OFFSETOF__APC_CALLBACK_DATA__ContextRecord == offsetof(CLONE_APC_CALLBACK_DATA, ContextRecord)); + + HMODULE hKernel32 = WszLoadLibraryEx(WINDOWS_KERNEL32_DLLNAME_W, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + + // See if QueueUserAPC2 exists + QueueUserAPC2Proc pfnQueueUserAPC2Proc = (QueueUserAPC2Proc)GetProcAddress(hKernel32, "QueueUserAPC2"); + if (pfnQueueUserAPC2Proc == nullptr) + { + return; + } + + // See if QueueUserAPC2 supports the special user-mode APC with a callback that includes the interrupted CONTEXT. A special + // user-mode APC can interrupt a thread that is in user mode and not in a non-alertable wait. + if (!(*pfnQueueUserAPC2Proc)(EmptyApcCallback, GetCurrentThread(), 0, SpecialUserModeApcWithContextFlags)) + { + return; + } + + // In the future, once code paths using the special user-mode APC get some bake time, it should be used regardless of + // whether CET shadow stacks are enabled + if (AreCetShadowStacksEnabled()) + { + s_pfnQueueUserAPC2Proc = pfnQueueUserAPC2Proc; + } +} +#endif // FEATURE_SPECIAL_USER_MODE_APC #endif // #ifndef DACCESS_COMPILE #ifdef DACCESS_COMPILE diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h index 7d600dab5edac..f579984f7ba21 100644 --- a/src/coreclr/vm/threads.h +++ b/src/coreclr/vm/threads.h @@ -484,6 +484,10 @@ typedef Thread::ForbidSuspendThreadHolder ForbidSuspendThreadHolder; #define DISABLE_THREADSUSPEND #endif +#if defined(FEATURE_HIJACK) && (defined(TARGET_UNIX) || defined(FEATURE_SPECIAL_USER_MODE_APC)) +#define FEATURE_THREAD_ACTIVATION +#endif + // NT thread priorities range from -15 to +15. #define INVALID_THREAD_PRIORITY ((DWORD)0x80000000) @@ -530,6 +534,41 @@ struct HijackArgs; // manifest constant for waiting in the exposed classlibs const INT32 INFINITE_TIMEOUT = -1; +/***************************************************************************/ +#ifdef FEATURE_SPECIAL_USER_MODE_APC + +// These declarations are for a new special user-mode APC feature introduced in Windows. These are not yet available in Windows +// SDK headers, so some names below are prefixed with "CLONE_" to avoid conflicts in the future. Once the prefixed declarations +// become available in the Windows SDK headers, the prefixed declarations below can be removed in favor of the SDK ones. + +enum CLONE_QUEUE_USER_APC_FLAGS +{ + CLONE_QUEUE_USER_APC_FLAGS_NONE = 0x0, + CLONE_QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC = 0x1, + + CLONE_QUEUE_USER_APC_CALLBACK_DATA_CONTEXT = 0x10000 +}; + +struct CLONE_APC_CALLBACK_DATA +{ + ULONG_PTR Parameter; + PCONTEXT ContextRecord; + ULONG_PTR Reserved0; + ULONG_PTR Reserved1; +}; +typedef CLONE_APC_CALLBACK_DATA *CLONE_PAPC_CALLBACK_DATA; + +typedef BOOL (WINAPI *QueueUserAPC2Proc)(PAPCFUNC ApcRoutine, HANDLE Thread, ULONG_PTR Data, CLONE_QUEUE_USER_APC_FLAGS Flags); + +const CLONE_QUEUE_USER_APC_FLAGS SpecialUserModeApcWithContextFlags = + (CLONE_QUEUE_USER_APC_FLAGS) + ( + CLONE_QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC | // this will be a special user-mode APC + CLONE_QUEUE_USER_APC_CALLBACK_DATA_CONTEXT // the callback's parameter will be a PAPC_CALLBACK_DATA + ); + +#endif // FEATURE_SPECIAL_USER_MODE_APC + /***************************************************************************/ // Public enum shared between thread and threadpool // These are two kinds of threadpool thread that the threadpool mgr needs @@ -1022,9 +1061,9 @@ class Thread // MapWin32FaultToCOMPlusException needs access to Thread::IsAddrOfRedirectFunc() friend DWORD MapWin32FaultToCOMPlusException(EXCEPTION_RECORD *pExceptionRecord); friend void STDCALL OnHijackWorker(HijackArgs * pArgs); -#ifdef TARGET_UNIX - friend void HandleGCSuspensionForInterruptedThread(CONTEXT *interruptedContext); -#endif // TARGET_UNIX +#ifdef FEATURE_THREAD_ACTIVATION + friend void HandleSuspensionForInterruptedThread(CONTEXT *interruptedContext); +#endif // FEATURE_THREAD_ACTIVATION #endif // FEATURE_HIJACK @@ -2388,9 +2427,15 @@ class Thread STR_NoStressLog, }; -#if defined(FEATURE_HIJACK) && defined(TARGET_UNIX) - bool InjectGcSuspension(); -#endif // FEATURE_HIJACK && TARGET_UNIX +#ifdef FEATURE_THREAD_ACTIVATION + enum class ActivationReason + { + SuspendForGC, + SuspendForDebugger, + }; + + bool InjectActivation(ActivationReason reason); +#endif // FEATURE_THREAD_ACTIVATION #ifndef DISABLE_THREADSUSPEND // SuspendThread @@ -4000,9 +4045,12 @@ class Thread { LIMITED_METHOD_CONTRACT; #ifdef _DEBUG - _ASSERTE(m_RedirectContextInUse); - _ASSERTE(pCtx == m_pSavedRedirectContext); - m_RedirectContextInUse = false; + _ASSERTE(!UseContextBasedThreadRedirection() || m_RedirectContextInUse); + if (m_RedirectContextInUse) + { + _ASSERTE(pCtx == m_pSavedRedirectContext); + m_RedirectContextInUse = false; + } #endif } #endif //DACCESS_COMPILE @@ -4610,6 +4658,60 @@ class Thread private: bool m_isInForbidSuspendForDebuggerRegion; + +#ifndef DACCESS_COMPILE +public: + static void StaticInitialize(); + +#if defined(TARGET_AMD64) && defined(TARGET_WINDOWS) + static bool AreCetShadowStacksEnabled() + { + LIMITED_METHOD_CONTRACT; + + // The SSP is null when CET shadow stacks are not enabled. On processors that don't support shadow stacks, this is a + // no-op and the intrinsic returns 0. CET shadow stacks are enabled or disabled for all threads, so the result is the + // same from any thread. + return _rdsspq() != 0; + } +#endif + +#ifdef FEATURE_SPECIAL_USER_MODE_APC +private: + static void InitializeSpecialUserModeApc(); + static void ApcActivationCallback(ULONG_PTR Parameter); +#endif + +public: + static bool UseSpecialUserModeApc() + { + LIMITED_METHOD_CONTRACT; + + #ifdef FEATURE_SPECIAL_USER_MODE_APC + return s_pfnQueueUserAPC2Proc != nullptr; + #else + return false; + #endif + } + + static bool UseContextBasedThreadRedirection() + { + LIMITED_METHOD_CONTRACT; + + #ifndef DISABLE_THREADSUSPEND + return !UseSpecialUserModeApc(); + #else + return false; + #endif + } + +#ifdef FEATURE_SPECIAL_USER_MODE_APC +private: + static QueueUserAPC2Proc s_pfnQueueUserAPC2Proc; +#endif +#endif // !DACCESS_COMPILE + +private: + bool m_hasPendingActivation; }; // End of class Thread diff --git a/src/coreclr/vm/threadsuspend.cpp b/src/coreclr/vm/threadsuspend.cpp index cf09d505b1c46..02e4747fceaf1 100644 --- a/src/coreclr/vm/threadsuspend.cpp +++ b/src/coreclr/vm/threadsuspend.cpp @@ -34,6 +34,9 @@ ThreadSuspend::SUSPEND_REASON ThreadSuspend::m_suspendReason; extern "C" void RedirectedHandledJITCaseForGCThreadControl_Stub(void); extern "C" void RedirectedHandledJITCaseForDbgThreadControl_Stub(void); extern "C" void RedirectedHandledJITCaseForUserSuspend_Stub(void); +#ifdef FEATURE_SPECIAL_USER_MODE_APC +extern "C" void NTAPI ApcActivationCallbackStub(ULONG_PTR Parameter); +#endif #define GetRedirectHandlerForGCThreadControl() \ ((PFN_REDIRECTTARGET) GetEEFuncEntryPoint(RedirectedHandledJITCaseForGCThreadControl_Stub)) @@ -41,6 +44,10 @@ extern "C" void RedirectedHandledJITCaseForUserSuspend_Stub(void); ((PFN_REDIRECTTARGET) GetEEFuncEntryPoint(RedirectedHandledJITCaseForDbgThreadControl_Stub)) #define GetRedirectHandlerForUserSuspend() \ ((PFN_REDIRECTTARGET) GetEEFuncEntryPoint(RedirectedHandledJITCaseForUserSuspend_Stub)) +#ifdef FEATURE_SPECIAL_USER_MODE_APC +#define GetRedirectHandlerForApcActivation() \ + ((PAPCFUNC) GetEEFuncEntryPoint(ApcActivationCallbackStub)) +#endif #if defined(TARGET_AMD64) || defined(TARGET_ARM) || defined(TARGET_ARM64) #if defined(HAVE_GCCOVER) && defined(USE_REDIRECT_FOR_GCSTRESS) // GCCOVER @@ -50,7 +57,6 @@ extern "C" void RedirectedHandledJITCaseForGCStress_Stub(void); #endif // HAVE_GCCOVER && USE_REDIRECT_FOR_GCSTRESS #endif // TARGET_AMD64 || TARGET_ARM - // Every PING_JIT_TIMEOUT ms, check to see if a thread in JITted code has wandered // into some fully interruptible code (or should have a different hijack to improve // our chances of snagging it at a safe spot). @@ -2402,7 +2408,7 @@ void Thread::RareEnablePreemptiveGC() } // Called when we are passing through a safe point in CommonTripThread or -// HandleGCSuspensionForInterruptedThread. Do the right thing with this thread, +// HandleSuspensionForInterruptedThread. Do the right thing with this thread, // which can either mean waiting for the GC to complete, or performing a // pending suspension. void Thread::PulseGCMode() @@ -3046,10 +3052,21 @@ BOOL Thread::IsAddrOfRedirectFunc(void * pFuncAddr) return TRUE; #endif // HAVE_GCCOVER && USE_REDIRECT_FOR_GCSTRESS - return - (pFuncAddr == GetRedirectHandlerForGCThreadControl()) || + if ((pFuncAddr == GetRedirectHandlerForGCThreadControl()) || (pFuncAddr == GetRedirectHandlerForDbgThreadControl()) || - (pFuncAddr == GetRedirectHandlerForUserSuspend()); + (pFuncAddr == GetRedirectHandlerForUserSuspend())) + { + return TRUE; + } + +#ifdef FEATURE_SPECIAL_USER_MODE_APC + if (pFuncAddr == GetRedirectHandlerForApcActivation()) + { + return TRUE; + } +#endif + + return FALSE; } //************************************************************************ @@ -3345,25 +3362,32 @@ void ThreadSuspend::SuspendRuntime(ThreadSuspend::SUSPEND_REASON reason) // this is an interesting thread in cooperative mode, let's guide it to preemptive -#ifdef DISABLE_THREADSUSPEND - // On platforms that do not support safe thread suspension, we do one of two things: - // - // - If we're on a Unix platform where hijacking is enabled, we attempt - // to inject a GC suspension which will try to redirect or hijack the - // thread to get it to a safe point. - // - // - Otherwise, we rely on the GCPOLL mechanism enabled by - // TrapReturningThreads. - -#if defined(FEATURE_HIJACK) && defined(TARGET_UNIX) - bool gcSuspensionSignalSuccess = thread->InjectGcSuspension(); - if (!gcSuspensionSignalSuccess) + if (!Thread::UseContextBasedThreadRedirection()) { - STRESS_LOG1(LF_SYNC, LL_INFO1000, "Thread::SuspendRuntime() - Failed to raise GC suspension signal for thread %p.\n", thread); + // On platforms that do not support safe thread suspension, we do one of the following: + // + // - If we're on a Unix platform where hijacking is enabled, we attempt + // to inject an activation which will try to redirect or hijack the + // thread to get it to a safe point. + // + // - Similarly to above, if we're on a Windows platform where the special + // user-mode APC is available, that is used if redirection is necessary. + // + // - Otherwise, we rely on the GCPOLL mechanism enabled by + // TrapReturningThreads. + +#ifdef FEATURE_THREAD_ACTIVATION + bool success = thread->InjectActivation(Thread::ActivationReason::SuspendForGC); + if (!success) + { + STRESS_LOG1(LF_SYNC, LL_INFO1000, "Thread::SuspendRuntime() - Failed to inject an activation for thread %p.\n", thread); + } +#endif // FEATURE_THREAD_ACTIVATION + + continue; } -#endif // FEATURE_HIJACK && TARGET_UNIX -#else // DISABLE_THREADSUSPEND +#ifndef DISABLE_THREADSUSPEND if (thread->HasThreadStateOpportunistic(Thread::TS_GCSuspendRedirected)) { @@ -3469,7 +3493,7 @@ void ThreadSuspend::SuspendRuntime(ThreadSuspend::SUSPEND_REASON reason) thread->ResumeThread(); STRESS_LOG1(LF_SYNC, LL_INFO1000, " Thread 0x%x is in cooperative needs to rendezvous\n", thread); -#endif // DISABLE_THREADSUSPEND +#endif // !DISABLE_THREADSUSPEND } if (countThreads == 0) @@ -4122,50 +4146,56 @@ bool Thread::SysStartSuspendForDebug(AppDomain *pAppDomain) // switch back and forth during a debug suspension -- until we // can get their Pending bit set. -#if defined(FEATURE_HIJACK) && !defined(TARGET_UNIX) +#if !defined(DISABLE_THREADSUSPEND) && defined(FEATURE_HIJACK) && !defined(TARGET_UNIX) DWORD dwSwitchCount = 0; RetrySuspension: -#endif // FEATURE_HIJACK && !TARGET_UNIX +#endif // !DISABLE_THREADSUSPEND && FEATURE_HIJACK && !TARGET_UNIX -#ifdef DISABLE_THREADSUSPEND - // On platforms that do not support safe thread suspension we have - // to rely on the GCPOLL mechanism. - - // When we do not suspend the target thread we rely on the GCPOLL - // mechanism enabled by TrapReturningThreads. However when reading - // shared state we need to erect appropriate memory barriers. So - // the interlocked operation below ensures that any future reads on - // this thread will happen after any earlier writes on a different - // thread. SuspendThreadResult str = STR_Success; - FastInterlockOr(&thread->m_fPreemptiveGCDisabled, 0); -#else - // We can not allocate memory after we suspend a thread. - // Otherwise, we may deadlock if suspended thread holds allocator locks. - ThreadStore::AllocateOSContext(); - SuspendThreadResult str = thread->SuspendThread(); -#endif // DISABLE_THREADSUSPEND + if (!UseContextBasedThreadRedirection()) + { + // On platforms that do not support safe thread suspension we either + // rely on the GCPOLL mechanism mechanism enabled by TrapReturningThreads, + // or we try to hijack/redirect the thread using a thread activation. + + // When we do not suspend the target thread, when reading shared state + // we need to erect appropriate memory barriers. So the interlocked + // operation below ensures that any future reads on this thread will + // happen after any earlier writes on a different thread. + FastInterlockOr(&thread->m_fPreemptiveGCDisabled, 0); + } + else + { +#ifndef DISABLE_THREADSUSPEND + // We can not allocate memory after we suspend a thread. + // Otherwise, we may deadlock if suspended thread holds allocator locks. + ThreadStore::AllocateOSContext(); + str = thread->SuspendThread(); +#endif // !DISABLE_THREADSUSPEND + } if (thread->m_fPreemptiveGCDisabled && str == STR_Success) { - -#if defined(FEATURE_HIJACK) && !defined(TARGET_UNIX) - WorkingOnThreadContextHolder workingOnThreadContext(thread); - if (workingOnThreadContext.Acquired() && thread->HandledJITCase()) +#if !defined(DISABLE_THREADSUSPEND) && defined(FEATURE_HIJACK) && !defined(TARGET_UNIX) + if (UseContextBasedThreadRedirection()) { - // Redirect thread so we can capture a good thread context - // (GetThreadContext is not sufficient, due to an OS bug). - // If we don't succeed (should only happen on Win9X, due to - // a different OS bug), we must resume the thread and try - // again. - if (!thread->CheckForAndDoRedirectForDbg()) + WorkingOnThreadContextHolder workingOnThreadContext(thread); + if (workingOnThreadContext.Acquired() && thread->HandledJITCase()) { - thread->ResumeThread(); - __SwitchToThread(0, ++dwSwitchCount); - goto RetrySuspension; + // Redirect thread so we can capture a good thread context + // (GetThreadContext is not sufficient, due to an OS bug). + // If we don't succeed (should only happen on Win9X, due to + // a different OS bug), we must resume the thread and try + // again. + if (!thread->CheckForAndDoRedirectForDbg()) + { + thread->ResumeThread(); + __SwitchToThread(0, ++dwSwitchCount); + goto RetrySuspension; + } } } -#endif // FEATURE_HIJACK && !TARGET_UNIX +#endif // !DISABLE_THREADSUSPEND && FEATURE_HIJACK && !TARGET_UNIX // Remember that this thread will be running to a safe point FastInterlockIncrement(&m_DebugWillSyncCount); @@ -4177,20 +4207,30 @@ bool Thread::SysStartSuspendForDebug(AppDomain *pAppDomain) TS_DebugWillSync ); -#ifdef DISABLE_THREADSUSPEND - // There'a a race above between the moment we first check m_fPreemptiveGCDisabled - // and the moment we enable TrapReturningThreads in MarkForSuspension. However, - // nothing bad happens if the thread has transitioned to preemptive before marking - // the thread for suspension; the thread will later be identified as Synced in - // SysSweepThreadsForDebug. - // - // If the thread transitions to preemptive mode and into a forbid-suspend-for-debugger - // region, SysSweepThreadsForDebug would similarly identify the thread as synced - // after it leaves the forbid region. -#else // DISABLE_THREADSUSPEND - // Resume the thread and let it run to a safe point - thread->ResumeThread(); -#endif // DISABLE_THREADSUSPEND + if (!UseContextBasedThreadRedirection()) + { + // There'a a race above between the moment we first check m_fPreemptiveGCDisabled + // and the moment we enable TrapReturningThreads in MarkForSuspension. However, + // nothing bad happens if the thread has transitioned to preemptive before marking + // the thread for suspension; the thread will later be identified as Synced in + // SysSweepThreadsForDebug. + // + // If the thread transitions to preemptive mode and into a forbid-suspend-for-debugger + // region, SysSweepThreadsForDebug would similarly identify the thread as synced + // after it leaves the forbid region. + +#if defined(FEATURE_THREAD_ACTIVATION) && defined(TARGET_WINDOWS) + // Inject an activation that will interrupt the thread and try to bring it to a safe point + thread->InjectActivation(Thread::ActivationReason::SuspendForDebugger); +#endif // FEATURE_THREAD_ACTIVATION && TARGET_WINDOWS + } + else + { +#ifndef DISABLE_THREADSUSPEND + // Resume the thread and let it run to a safe point + thread->ResumeThread(); +#endif // !DISABLE_THREADSUSPEND + } LOG((LF_CORDB, LL_INFO1000, "[0x%x] SUSPEND: gc disabled - will sync.\n", @@ -4203,14 +4243,13 @@ bool Thread::SysStartSuspendForDebug(AppDomain *pAppDomain) thread->MarkForSuspension(TS_DebugSuspendPending); if ( -#ifdef DISABLE_THREADSUSPEND // There'a a race above between the moment we first check m_fPreemptiveGCDisabled // and the moment we enable TrapReturningThreads in MarkForSuspension. To account // for that we check whether the thread moved into cooperative mode, and if it had // we mark it as a DebugWillSync thread, that will be handled later in // SysSweepThreadsForDebug. - thread->m_fPreemptiveGCDisabled || -#endif // DISABLE_THREADSUSPEND + (!UseContextBasedThreadRedirection() && thread->m_fPreemptiveGCDisabled) || + // The thread may have been suspended in a forbid-suspend-for-debugger region, or // before the state change to set TS_DebugSuspendPending is made visible to other // threads, the thread may have transitioned into a forbid region. In either case, @@ -4224,7 +4263,7 @@ bool Thread::SysStartSuspendForDebug(AppDomain *pAppDomain) } #ifndef DISABLE_THREADSUSPEND - if (str == STR_Success) { + if (str == STR_Success && UseContextBasedThreadRedirection()) { thread->ResumeThread(); } #endif // !DISABLE_THREADSUSPEND @@ -4294,30 +4333,38 @@ bool Thread::SysSweepThreadsForDebug(bool forceSync) if ((thread->m_State & TS_DebugWillSync) == 0) continue; -#ifdef DISABLE_THREADSUSPEND - - // On platforms that do not support safe thread suspension we have - // to rely on the GCPOLL mechanism. - - // When we do not suspend the target thread we rely on the GCPOLL - // mechanism enabled by TrapReturningThreads. However when reading - // shared state we need to erect appropriate memory barriers. So - // the interlocked operation below ensures that any future reads on - // this thread will happen after any earlier writes on a different - // thread. - FastInterlockOr(&thread->m_fPreemptiveGCDisabled, 0); - if (!thread->m_fPreemptiveGCDisabled && !thread->IsInForbidSuspendForDebuggerRegion()) - { - // If the thread toggled to preemptive mode and is not in a - // forbid-suspend-for-debugger region, then it's synced. - goto Label_MarkThreadAsSynced; - } - else + if (!UseContextBasedThreadRedirection()) { + // On platforms that do not support safe thread suspension we either + // rely on the GCPOLL mechanism mechanism enabled by TrapReturningThreads, + // or we try to hijack/redirect the thread using a thread activation. + + // When we do not suspend the target thread, when reading shared state + // we need to erect appropriate memory barriers. So the interlocked + // operation below ensures that any future reads on this thread will + // happen after any earlier writes on a different thread. + FastInterlockOr(&thread->m_fPreemptiveGCDisabled, 0); + if (!thread->m_fPreemptiveGCDisabled) + { + if (thread->IsInForbidSuspendForDebuggerRegion()) + { + continue; + } + + // If the thread toggled to preemptive mode and is not in a + // forbid-suspend-for-debugger region, then it's synced. + goto Label_MarkThreadAsSynced; + } + +#if defined(FEATURE_THREAD_ACTIVATION) && defined(TARGET_WINDOWS) + // Inject an activation that will interrupt the thread and try to bring it to a safe point + thread->InjectActivation(Thread::ActivationReason::SuspendForDebugger); +#endif // FEATURE_THREAD_ACTIVATION && TARGET_WINDOWS + continue; } -#else // DISABLE_THREADSUSPEND +#ifndef DISABLE_THREADSUSPEND // Suspend the thread #if defined(FEATURE_HIJACK) && !defined(TARGET_UNIX) @@ -4402,7 +4449,7 @@ bool Thread::SysSweepThreadsForDebug(bool forceSync) thread->ResumeThread(); continue; -#endif // DISABLE_THREADSUSPEND +#endif // !DISABLE_THREADSUSPEND // The thread is synced. Remove the sync bits and dec the sync count. Label_MarkThreadAsSynced: @@ -5215,7 +5262,7 @@ BOOL Thread::GetSafelyRedirectableThreadContext(DWORD dwOptions, CONTEXT * pCtx, { // If we are running under the control of a managed debugger that may have placed breakpoints in the code stream, // then there is a special case that we need to check. See the comments in debugger.cpp for more information. - if (CORDebuggerAttached() && (g_pDebugInterface->IsThreadContextInvalid(this))) + if (CORDebuggerAttached() && (g_pDebugInterface->IsThreadContextInvalid(this, NULL))) return FALSE; } #endif // DEBUGGING_SUPPORTED @@ -5709,10 +5756,10 @@ void ThreadSuspend::SuspendEE(SUSPEND_REASON reason) #endif //TARGET_ARM || TARGET_ARM64 } -#if defined(FEATURE_HIJACK) && defined(TARGET_UNIX) +#ifdef FEATURE_THREAD_ACTIVATION -// This function is called by PAL to check if the specified instruction pointer -// is in a function where we can safely inject activation. +// This function is called by a thread activation to check if the specified instruction pointer +// is in a function where we can safely handle an activation. BOOL CheckActivationSafePoint(SIZE_T ip, BOOL checkingCurrentThread) { Thread *pThread = GetThreadNULLOk(); @@ -5725,7 +5772,7 @@ BOOL CheckActivationSafePoint(SIZE_T ip, BOOL checkingCurrentThread) return checkForManagedCode && ExecutionManager::IsManagedCode(ip); } -// This function is called when a GC is pending. It tries to ensure that the current +// This function is called when thread suspension is pending. It tries to ensure that the current // thread is taken to a GC-safe place as quickly as possible. It does this by doing // one of the following: // @@ -5740,7 +5787,7 @@ BOOL CheckActivationSafePoint(SIZE_T ip, BOOL checkingCurrentThread) // address to take the thread to the appropriate stub (based on the return // type of the method) which will then handle preparing the thread for GC. // -void HandleGCSuspensionForInterruptedThread(CONTEXT *interruptedContext) +void HandleSuspensionForInterruptedThread(CONTEXT *interruptedContext) { Thread *pThread = GetThread(); @@ -5762,6 +5809,13 @@ void HandleGCSuspensionForInterruptedThread(CONTEXT *interruptedContext) if (!workingOnThreadContext.Acquired()) return; +#if defined(DEBUGGING_SUPPORTED) && defined(TARGET_WINDOWS) + // If we are running under the control of a managed debugger that may have placed breakpoints in the code stream, + // then there is a special case that we need to check. See the comments in debugger.cpp for more information. + if (CORDebuggerAttached() && g_pDebugInterface->IsThreadContextInvalid(pThread, interruptedContext)) + return; +#endif // DEBUGGING_SUPPORTED && TARGET_WINDOWS + EECodeInfo codeInfo(ip); if (!codeInfo.IsValid()) return; @@ -5783,6 +5837,8 @@ void HandleGCSuspensionForInterruptedThread(CONTEXT *interruptedContext) pThread->PulseGCMode(); frame.Pop(pThread); + + // TODO: Windows - Raise thread abort exception if ready for abort } else { @@ -5833,8 +5889,73 @@ void HandleGCSuspensionForInterruptedThread(CONTEXT *interruptedContext) } } -bool Thread::InjectGcSuspension() +#ifdef FEATURE_SPECIAL_USER_MODE_APC +void Thread::ApcActivationCallback(ULONG_PTR Parameter) { + // Cannot use contracts here because the thread may be interrupted at any point + + _ASSERTE(UseSpecialUserModeApc()); + _ASSERTE(Parameter != 0); + + CLONE_PAPC_CALLBACK_DATA pData = (CLONE_PAPC_CALLBACK_DATA)Parameter; + ActivationReason reason = (ActivationReason)pData->Parameter; + PCONTEXT pContext = pData->ContextRecord; + + struct AutoClearPendingThreadActivation + { + ~AutoClearPendingThreadActivation() + { + GetThread()->m_hasPendingActivation = false; + } + } autoClearPendingThreadActivation; + + if (!CheckActivationSafePoint(GetIP(pContext), true /* checkingCurrentThread */)) + { + return; + } + + switch (reason) + { + case ActivationReason::SuspendForGC: + case ActivationReason::SuspendForDebugger: + HandleSuspensionForInterruptedThread(pContext); + break; + + default: + UNREACHABLE_MSG("Unexpected ActivationReason"); + } +} +#endif // FEATURE_SPECIAL_USER_MODE_APC + +bool Thread::InjectActivation(ActivationReason reason) +{ +#ifdef FEATURE_SPECIAL_USER_MODE_APC + _ASSERTE(UseSpecialUserModeApc()); + + if (m_hasPendingActivation) + { + // Try to avoid nesting special user-mode APCs, as they can interrupt one another + return true; + } + + HANDLE hThread = GetThreadHandle(); + if (hThread == INVALID_HANDLE_VALUE) + { + return false; + } + + m_hasPendingActivation = true; + BOOL success = + (*s_pfnQueueUserAPC2Proc)( + GetRedirectHandlerForApcActivation(), + hThread, + (ULONG_PTR)reason, + SpecialUserModeApcWithContextFlags); + _ASSERTE(success); + return true; +#elif defined(TARGET_UNIX) + _ASSERTE(reason == ActivationReason::SuspendForGC); + static ConfigDWORD injectionEnabled; if (injectionEnabled.val(CLRConfig::INTERNAL_ThreadSuspendInjection) == 0) return false; @@ -5844,15 +5965,18 @@ bool Thread::InjectGcSuspension() return ::PAL_InjectActivation(hThread); return false; +#else +#error Unknown platform. +#endif // FEATURE_SPECIAL_USER_MODE_APC || TARGET_UNIX } -#endif // FEATURE_HIJACK && TARGET_UNIX +#endif // FEATURE_THREAD_ACTIVATION // Initialize thread suspension support void ThreadSuspend::Initialize() { #if defined(FEATURE_HIJACK) && defined(TARGET_UNIX) - ::PAL_SetActivationFunction(HandleGCSuspensionForInterruptedThread, CheckActivationSafePoint); + ::PAL_SetActivationFunction(HandleSuspensionForInterruptedThread, CheckActivationSafePoint); #endif } diff --git a/src/libraries/Native/Windows/CMakeLists.txt b/src/libraries/Native/Windows/CMakeLists.txt index b9b6c823d0b49..e96ec87ddb1c4 100644 --- a/src/libraries/Native/Windows/CMakeLists.txt +++ b/src/libraries/Native/Windows/CMakeLists.txt @@ -63,7 +63,14 @@ endif () # enable control-flow-guard support for native components add_compile_options(/guard:cf) -list(APPEND __SharedLinkArgs /GUARD:CF) +list(APPEND __SharedLinkArgs /guard:cf) + +#if (${CLR_CMAKE_HOST_ARCH} STREQUAL "x86_64" OR ${CLR_CMAKE_HOST_ARCH} STREQUAL "amd64" OR ${CLR_CMAKE_HOST_ARCH} STREQUAL "x64") +# # Enable EH continuation table and CETCOMPAT for native components +# add_compile_options(/guard:ehcont) +# list(APPEND __SharedLinkArgs /guard:ehcont) +# list(APPEND __SharedLinkArgs /CETCOMPAT) +#endif () # Statically linked CRT (libcmt[d].lib, libvcruntime[d].lib and libucrt[d].lib) by default. This is done to avoid # linking in VCRUNTIME140.DLL for a simplified xcopy experience by reducing the dependency on VC REDIST. @@ -109,7 +116,7 @@ endif() # Debug build specific flags list(INSERT __SharedLinkArgs 0 $<$,$>:/NOVCFEATURE>) -if (${CLR_CMAKE_HOST_ARCH} STREQUAL "x86_64" OR ${CLR_CMAKE_HOST_ARCH} STREQUAL "amd64") +if (${CLR_CMAKE_HOST_ARCH} STREQUAL "x86_64" OR ${CLR_CMAKE_HOST_ARCH} STREQUAL "amd64" OR ${CLR_CMAKE_HOST_ARCH} STREQUAL "x64") add_definitions(-DTARGET_64BIT=1) endif () diff --git a/src/tests/Interop/IJW/IJW.cmake b/src/tests/Interop/IJW/IJW.cmake index b78fcb2bb169a..b41a83794d99b 100644 --- a/src/tests/Interop/IJW/IJW.cmake +++ b/src/tests/Interop/IJW/IJW.cmake @@ -22,6 +22,11 @@ if (CLR_CMAKE_HOST_WIN32) string(REPLACE "/guard:cf" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") endif() + # IJW isn't compatible with EHCONT, which requires CFG + if(CMAKE_CXX_FLAGS MATCHES "/guard:ehcont") + string(REPLACE "/guard:ehcont" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + endif() + # IJW isn't compatible with GR- if(CMAKE_CXX_FLAGS MATCHES "/GR-") string(REPLACE "/GR-" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")