Fix GetThreadContext stale register values use if WoW64 (Win32)
authorHamayama <hamay1010@gmail.com>
Thu, 7 Feb 2019 21:51:25 +0000 (00:51 +0300)
committerIvan Maidanski <ivmai@mail.ru>
Thu, 7 Feb 2019 21:51:25 +0000 (00:51 +0300)
Issue #262 (bdwgc).

* misc.c [MSWIN32 && !MSWINRT_FLAVOR && !MSWIN_XBOX1]
(GC_win32_MessageBoxA): Do not define unless SMALL_CONFIG.
* misc.c [MSWIN32 && !_WIN64 && GC_WIN32_THREADS && CHECK_NOT_WOW64]
(GC_init): Do not call IsWow64Process() and GC_win32_MessageBoxA().
* win32_threads.c [I386] (isWow64): New static variable.
* win32_threads.c [I386] (GC_push_stack_for): If isWow64 then set also
CONTEXT_EXCEPTION_REQUEST and CONTEXT_SEGMENTS bits in ContextFlags;
if isWow64, and CONTEXT_EXCEPTION_REPORTING and
CONTEXT_EXCEPTION_ACTIVE are set on return from GetThreadContext then
call GetThreadSelectorEntry and use StackLimit of FS selector to set
sp local variable (instead of context.Esp); add comment.
* win32_threads.c [I386 && DEBUG_THREADS] (GC_push_stack_for): Call
GC_log_printf() to report TIB stack limit/base and the case when
CONTEXT_EXCEPTION_REQUEST is not supported.
* win32_threads.c [I386] (GC_thr_init): Set isWow64 by IsWow64Process()
if the later is available.

misc.c
win32_threads.c

diff --git a/misc.c b/misc.c
index 7fb3abd..4f3284b 100644 (file)
--- a/misc.c
+++ b/misc.c
@@ -821,9 +821,7 @@ GC_API int GC_CALL GC_is_init_called(void)
 #endif
 
 #if defined(MSWIN32) && !defined(MSWINRT_FLAVOR) && !defined(MSWIN_XBOX1) \
-    && (!defined(SMALL_CONFIG) \
-        || (!defined(_WIN64) && defined(GC_WIN32_THREADS) \
-            && defined(CHECK_NOT_WOW64)))
+    && !defined(SMALL_CONFIG)
   STATIC void GC_win32_MessageBoxA(const char *msg, const char *caption,
                                    unsigned flags)
   {
@@ -929,30 +927,6 @@ GC_API void GC_CALL GC_init(void)
       initial_heap_sz = MINHINCR * HBLKSIZE;
 #   endif
 
-#   if defined(MSWIN32) && !defined(_WIN64) && defined(GC_WIN32_THREADS) \
-       && defined(CHECK_NOT_WOW64)
-      {
-        /* Windows: running 32-bit GC on 64-bit system is broken!       */
-        /* WoW64 bug affects SuspendThread, no workaround exists.       */
-        HMODULE hK32 = GetModuleHandle(TEXT("kernel32.dll"));
-        if (hK32) {
-          FARPROC pfn = GetProcAddress(hK32, "IsWow64Process");
-          BOOL bIsWow64 = FALSE;
-          if (pfn
-              && (*(BOOL (WINAPI*)(HANDLE, BOOL*))pfn)(GetCurrentProcess(),
-                                                       &bIsWow64)
-              && bIsWow64) {
-            GC_win32_MessageBoxA("This program uses BDWGC garbage collector"
-                " compiled for 32-bit but running on 64-bit Windows.\n"
-                "This is known to be broken due to a design flaw"
-                " in Windows itself! Expect erratic behavior...",
-                "32-bit program running on 64-bit system",
-                MB_ICONWARNING | MB_OK);
-          }
-        }
-      }
-#   endif
-
     DISABLE_CANCEL(cancel_state);
     /* Note that although we are nominally called with the */
     /* allocation lock held, the allocation lock is now    */
index 2c64da9..cbf874c 100644 (file)
@@ -1389,6 +1389,10 @@ static GC_bool may_be_in_stack(ptr_t s)
           && !(last_info.Protect & PAGE_GUARD);
 }
 
+#if defined(I386)
+  static BOOL isWow64;  /* Is running 32-bit code on Win64?     */
+#endif
+
 STATIC word GC_push_stack_for(GC_thread thread, DWORD me)
 {
   ptr_t sp, stack_min;
@@ -1402,7 +1406,22 @@ STATIC word GC_push_stack_for(GC_thread thread, DWORD me)
               /* Use saved sp value for blocked threads. */
     /* For unblocked threads call GetThreadContext().   */
     CONTEXT context;
-    context.ContextFlags = CONTEXT_INTEGER|CONTEXT_CONTROL;
+#   if defined(I386)
+#     ifndef CONTEXT_EXCEPTION_ACTIVE
+#       define CONTEXT_EXCEPTION_ACTIVE    0x08000000
+#       define CONTEXT_EXCEPTION_REQUEST   0x40000000
+#       define CONTEXT_EXCEPTION_REPORTING 0x80000000
+#     endif
+
+      if (isWow64) {
+        context.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL
+                                | CONTEXT_EXCEPTION_REQUEST
+                                | CONTEXT_SEGMENTS;
+      } else
+#   endif
+    /* else */ {
+      context.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
+    }
     if (!GetThreadContext(THREAD_HANDLE(thread), &context))
       ABORT("GetThreadContext failed");
 
@@ -1414,7 +1433,45 @@ STATIC word GC_push_stack_for(GC_thread thread, DWORD me)
 #   define PUSH4(r1,r2,r3,r4) (PUSH2(r1,r2), PUSH2(r3,r4))
 #   if defined(I386)
       PUSH4(Edi,Esi,Ebx,Edx), PUSH2(Ecx,Eax), PUSH1(Ebp);
-      sp = (ptr_t)context.Esp;
+      /* WoW64 workaround. */
+      if (isWow64
+          && (context.ContextFlags & CONTEXT_EXCEPTION_REPORTING) != 0
+          && (context.ContextFlags & (CONTEXT_EXCEPTION_ACTIVE
+                                      /* | CONTEXT_SERVICE_ACTIVE */)) != 0) {
+        LDT_ENTRY selector;
+        PNT_TIB tib;
+
+        if (!GetThreadSelectorEntry(THREAD_HANDLE(thread), context.SegFs,
+                                    &selector))
+          ABORT("GetThreadSelectorEntry failed");
+        tib = (PNT_TIB)(selector.BaseLow
+                        | (selector.HighWord.Bits.BaseMid << 16)
+                        | (selector.HighWord.Bits.BaseHi << 24));
+        /* GetThreadContext() might return stale register values, so    */
+        /* we scan the entire stack region (down to the stack limit).   */
+        /* There is no 100% guarantee that all the registers are pushed */
+        /* but we do our best (the proper solution would be to fix it   */
+        /* inside Windows OS).                                          */
+        sp = (ptr_t)tib->StackLimit;
+#       ifdef DEBUG_THREADS
+          GC_log_printf("TIB stack limit/base: %p .. %p\n",
+                        (void *)tib->StackLimit, (void *)tib->StackBase);
+#       endif
+        GC_ASSERT(!((word)thread->stack_base
+                    COOLER_THAN (word)tib->StackBase));
+      } else {
+#       ifdef DEBUG_THREADS
+          {
+            static GC_bool logged;
+            if (isWow64 && !logged
+                && (context.ContextFlags & CONTEXT_EXCEPTION_REPORTING) == 0) {
+              GC_log_printf("CONTEXT_EXCEPTION_REQUEST not supported\n");
+              logged = TRUE;
+            }
+          }
+#       endif
+        sp = (ptr_t)context.Esp;
+      }
 #   elif defined(X86_64)
       PUSH4(Rax,Rcx,Rdx,Rbx); PUSH2(Rbp, Rsi); PUSH1(Rdi);
       PUSH4(R8, R9, R10, R11); PUSH4(R12, R13, R14, R15);
@@ -2451,6 +2508,20 @@ GC_INNER void GC_thr_init(void)
     }
 # endif
 
+# if defined(I386)
+    /* Set isWow64 flag. */
+    {
+      HMODULE hK32 = GetModuleHandle(TEXT("kernel32.dll"));
+      if (hK32) {
+        FARPROC pfn = GetProcAddress(hK32, "IsWow64Process");
+        if (pfn
+            && !(*(BOOL (WINAPI*)(HANDLE, BOOL*))pfn)(GetCurrentProcess(),
+                                                      &isWow64))
+          isWow64 = FALSE; /* IsWow64Process failed */
+      }
+    }
+# endif
+
   /* Add the initial thread, so we can stop it. */
 # ifdef GC_ASSERTIONS
     sb_result =