From 379f7b1fc2fb154c64e8b89a8635f3855ae5687d Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Fri, 17 May 2024 00:24:50 +0200 Subject: [PATCH] Workaround broken special APC when running x64 on arm64 windows (#102333) * Workaround broken special APC when running x64 on arm64 windows In ARM64 windows older than 24H2, the special APC is broken when running x64 emulation. The callback that gets invoked doesn't get an argument with correct CONTEXT of the interrupted location. This change disables using the special APC for runtime suspension when running on the affected Windows versions. Close #100425 * Make the same fix for NativeAOT --- .../Runtime/windows/PalRedhawkMinWin.cpp | 26 ++++++++++++++++++- src/coreclr/vm/threads.cpp | 16 ++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp b/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp index 0de949935d97a..d61badb1536bf 100644 --- a/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp +++ b/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp @@ -30,6 +30,10 @@ #include "thread.h" #include "threadstore.h" +#ifdef FEATURE_SPECIAL_USER_MODE_APC +#include +#endif + #define REDHAWK_PALEXPORT extern "C" #define REDHAWK_PALAPI __stdcall @@ -630,12 +634,32 @@ REDHAWK_PALEXPORT void REDHAWK_PALAPI PalHijack(HANDLE hThread, _In_opt_ void* p _ASSERTE(hThread != INVALID_HANDLE_VALUE); #ifdef FEATURE_SPECIAL_USER_MODE_APC + // initialize g_pfnQueueUserAPC2Proc on demand. // Note that only one thread at a time may perform suspension (guaranteed by the thread store lock) // so simple conditional assignment is ok. if (g_pfnQueueUserAPC2Proc == QUEUE_USER_APC2_UNINITIALIZED) { - g_pfnQueueUserAPC2Proc = (QueueUserAPC2Proc)GetProcAddress(LoadKernel32dll(), "QueueUserAPC2"); +#ifdef HOST_AMD64 + HMODULE hKernel32 = LoadKernel32dll(); + + typedef BOOL (WINAPI *IsWow64Process2Proc)(HANDLE hProcess, USHORT *pProcessMachine, USHORT *pNativeMachine); + + IsWow64Process2Proc pfnIsWow64Process2Proc = (IsWow64Process2Proc)GetProcAddress(hKernel32, "IsWow64Process2"); + USHORT processMachine, hostMachine; + if (pfnIsWow64Process2Proc != nullptr && + (*pfnIsWow64Process2Proc)(GetCurrentProcess(), &processMachine, &hostMachine) && + (hostMachine == IMAGE_FILE_MACHINE_ARM64) && + !IsWindowsVersionOrGreater(10, 0, 26100)) + { + // Special user-mode APCs are broken on WOW64 processes (x64 running on Arm64 machine) with Windows older than 11.0.26100 (24H2) + g_pfnQueueUserAPC2Proc = NULL; + } + else +#endif // HOST_AMD64 + { + g_pfnQueueUserAPC2Proc = (QueueUserAPC2Proc)GetProcAddress(hKernel32, "QueueUserAPC2"); + } } if (g_pfnQueueUserAPC2Proc) diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index 7915c96b002df..7411d62a285f8 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -45,6 +45,7 @@ #ifdef FEATURE_SPECIAL_USER_MODE_APC #include "asmconstants.h" +#include #endif static const PortableTailCallFrame g_sentinelTailCallFrame = { NULL, NULL }; @@ -8130,6 +8131,21 @@ void Thread::InitializeSpecialUserModeApc() HMODULE hKernel32 = WszLoadLibrary(WINDOWS_KERNEL32_DLLNAME_W, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); +#ifdef HOST_AMD64 + typedef BOOL (WINAPI *IsWow64Process2Proc)(HANDLE hProcess, USHORT *pProcessMachine, USHORT *pNativeMachine); + + IsWow64Process2Proc pfnIsWow64Process2Proc = (IsWow64Process2Proc)GetProcAddress(hKernel32, "IsWow64Process2"); + USHORT processMachine, hostMachine; + if (pfnIsWow64Process2Proc != nullptr && + (*pfnIsWow64Process2Proc)(GetCurrentProcess(), &processMachine, &hostMachine) && + (hostMachine == IMAGE_FILE_MACHINE_ARM64) && + !IsWindowsVersionOrGreater(10, 0, 26100)) + { + // Special user-mode APCs are broken on WOW64 processes (x64 running on Arm64 machine) with Windows older than 11.0.26100 (24H2) + return; + } +#endif // HOST_AMD64 + // See if QueueUserAPC2 exists QueueUserAPC2Proc pfnQueueUserAPC2Proc = (QueueUserAPC2Proc)GetProcAddress(hKernel32, "QueueUserAPC2"); if (pfnQueueUserAPC2Proc == nullptr)