Bug 1452204 part 2 - Use RtlCaptureContext to capture context for current thread and remove walker thread. r?glandium draft
authorXidorn Quan <me@upsuper.org>
Mon, 04 Jun 2018 19:23:27 +1000
changeset 805210 26a01187a3350f7df02b22fc0712cbb7765d8bb9
parent 804501 658805be640e59249a6421c1e0a8e6ef18f3e199
push id112595
push userxquan@mozilla.com
push dateThu, 07 Jun 2018 13:50:52 +0000
reviewersglandium
bugs1452204
milestone62.0a1
Bug 1452204 part 2 - Use RtlCaptureContext to capture context for current thread and remove walker thread. r?glandium GetThreadContext() returns a context pointing to its own frame when it gets called with the current thread handle. That frame can go away after it returns. This patch instead uses RtlCaptureContext(), which captures the context of its caller, when walking the current thread. In the past, we also used a walker thread when nullptr is passed in for aThread, but the check doesn't cover all the cases, and having another thread is apparently more complicated than this approach. MozReview-Commit-ID: 3TAatDc9BLh
mozglue/misc/StackWalk.cpp
--- a/mozglue/misc/StackWalk.cpp
+++ b/mozglue/misc/StackWalk.cpp
@@ -90,17 +90,16 @@ struct WalkStackData
   uint32_t pc_count;
   uint32_t pc_max;
   void** sps;
   uint32_t sp_size;
   uint32_t sp_count;
   CONTEXT* context;
 };
 
-DWORD gStackWalkThread;
 CRITICAL_SECTION gDbgHelpCS;
 
 #ifdef _M_AMD64
 // Because various Win64 APIs acquire function-table locks, we need a way of
 // preventing stack walking while those APIs are being called. Otherwise, the
 // stack walker may suspend a thread holding such a lock, and deadlock when the
 // stack unwind code attempts to wait for that lock.
 //
@@ -184,85 +183,29 @@ InitializeDbgHelpCriticalSection()
   static bool initialized = false;
   if (initialized) {
     return;
   }
   ::InitializeCriticalSection(&gDbgHelpCS);
   initialized = true;
 }
 
-static unsigned int WINAPI WalkStackThread(void* aData);
-
-static bool
-EnsureWalkThreadReady()
-{
-  static bool walkThreadReady = false;
-  static HANDLE stackWalkThread = nullptr;
-  static HANDLE readyEvent = nullptr;
-
-  if (walkThreadReady) {
-    return walkThreadReady;
-  }
-
-  if (!stackWalkThread) {
-    readyEvent = ::CreateEvent(nullptr, FALSE /* auto-reset*/,
-                               FALSE /* initially non-signaled */,
-                               nullptr);
-    if (!readyEvent) {
-      PrintError("CreateEvent");
-      return false;
-    }
-
-    unsigned int threadID;
-    stackWalkThread = (HANDLE)_beginthreadex(nullptr, 0, WalkStackThread,
-                                             (void*)readyEvent, 0, &threadID);
-    if (!stackWalkThread) {
-      PrintError("CreateThread");
-      ::CloseHandle(readyEvent);
-      readyEvent = nullptr;
-      return false;
-    }
-    gStackWalkThread = threadID;
-    ::CloseHandle(stackWalkThread);
-  }
-
-  MOZ_ASSERT((stackWalkThread && readyEvent) ||
-             (!stackWalkThread && !readyEvent));
-
-  // The thread was created. Try to wait an arbitrary amount of time (1 second
-  // should be enough) for its event loop to start before posting events to it.
-  DWORD waitRet = ::WaitForSingleObject(readyEvent, 1000);
-  if (waitRet == WAIT_TIMEOUT) {
-    // We get a timeout if we're called during static initialization because
-    // the thread will only start executing after we return so it couldn't
-    // have signalled the event. If that is the case, give up for now and
-    // try again next time we're called.
-    return false;
-  }
-  ::CloseHandle(readyEvent);
-  stackWalkThread = nullptr;
-  readyEvent = nullptr;
-
-  return walkThreadReady = true;
-}
-
 static void
 WalkStackMain64(struct WalkStackData* aData)
 {
   // Get a context for the specified thread.
   CONTEXT context_buf;
   CONTEXT* context;
   if (!aData->context) {
     context = &context_buf;
     memset(context, 0, sizeof(CONTEXT));
     context->ContextFlags = CONTEXT_FULL;
-    if (!GetThreadContext(aData->thread, context)) {
-      if (aData->walkCallingThread) {
-        PrintError("GetThreadContext");
-      }
+    if (aData->walkCallingThread) {
+      ::RtlCaptureContext(context);
+    } else if (!GetThreadContext(aData->thread, context)) {
       return;
     }
   } else {
     context = aData->context;
   }
 
 #if defined(_M_IX86) || defined(_M_IA64)
   // Setup initial stack frame to walk from.
@@ -423,95 +366,35 @@ WalkStackMain64(struct WalkStackData* aD
 #if defined(_M_IX86) || defined(_M_IA64)
     if (frame64.AddrReturn.Offset == 0) {
       break;
     }
 #endif
   }
 }
 
-static unsigned int WINAPI
-WalkStackThread(void* aData)
-{
-  BOOL msgRet;
-  MSG msg;
-
-  // Call PeekMessage to force creation of a message queue so that
-  // other threads can safely post events to us.
-  ::PeekMessage(&msg, nullptr, WM_USER, WM_USER, PM_NOREMOVE);
-
-  // and tell the thread that created us that we're ready.
-  HANDLE readyEvent = (HANDLE)aData;
-  ::SetEvent(readyEvent);
-
-  while ((msgRet = ::GetMessage(&msg, (HWND)-1, 0, 0)) != 0) {
-    if (msgRet == -1) {
-      PrintError("GetMessage");
-    } else {
-      DWORD ret;
-
-      struct WalkStackData* data = (WalkStackData*)msg.lParam;
-      if (!data) {
-        continue;
-      }
-
-      // Don't suspend the calling thread until it's waiting for
-      // us; otherwise the number of frames on the stack could vary.
-      ret = ::WaitForSingleObject(data->eventStart, INFINITE);
-      if (ret != WAIT_OBJECT_0) {
-        PrintError("WaitForSingleObject");
-      }
-
-      // Suspend the calling thread, dump his stack, and then resume him.
-      // He's currently waiting for us to finish so now should be a good time.
-      ret = ::SuspendThread(data->thread);
-      if (ret == (DWORD)-1) {
-        PrintError("ThreadSuspend");
-      } else {
-        WalkStackMain64(data);
-
-        ret = ::ResumeThread(data->thread);
-        if (ret == (DWORD)-1) {
-          PrintError("ThreadResume");
-        }
-      }
-
-      ::SetEvent(data->eventEnd);
-    }
-  }
-
-  return 0;
-}
-
 /**
  * Walk the stack, translating PC's found into strings and recording the
  * chain in aBuffer. For this to work properly, the DLLs must be rebased
  * so that the address in the file agrees with the address in memory.
  * Otherwise StackWalk will return FALSE when it hits a frame in a DLL
  * whose in memory address doesn't match its in-file address.
  */
 
 MFBT_API void
 MozStackWalkThread(MozWalkStackCallback aCallback, uint32_t aSkipFrames,
                    uint32_t aMaxFrames, void* aClosure,
                    HANDLE aThread, CONTEXT* aContext)
 {
   static HANDLE myProcess = nullptr;
   HANDLE myThread;
-  DWORD walkerReturn;
   struct WalkStackData data;
 
   InitializeDbgHelpCriticalSection();
 
-  // EnsureWalkThreadReady's _beginthreadex takes a heap lock and must be
-  // avoided if we're walking another (i.e. suspended) thread.
-  if (!aThread && !EnsureWalkThreadReady()) {
-    return;
-  }
-
   HANDLE targetThread = aThread;
   if (!aThread) {
     targetThread = ::GetCurrentThread();
     data.walkCallingThread = true;
   } else {
     DWORD threadId = ::GetThreadId(aThread);
     DWORD currentThreadId = ::GetCurrentThreadId();
     data.walkCallingThread = (threadId == currentThreadId);
@@ -550,60 +433,26 @@ MozStackWalkThread(MozWalkStackCallback 
   data.pc_size = ArrayLength(local_pcs);
   data.pc_max = aMaxFrames;
   void* local_sps[1024];
   data.sps = local_sps;
   data.sp_count = 0;
   data.sp_size = ArrayLength(local_sps);
   data.context = aContext;
 
-  if (aThread) {
-    // If we're walking the stack of another thread, we don't need to
-    // use a separate walker thread.
-    WalkStackMain64(&data);
-
-    if (data.pc_count > data.pc_size) {
-      data.pcs = (void**)_alloca(data.pc_count * sizeof(void*));
-      data.pc_size = data.pc_count;
-      data.pc_count = 0;
-      data.sps = (void**)_alloca(data.sp_count * sizeof(void*));
-      data.sp_size = data.sp_count;
-      data.sp_count = 0;
-      WalkStackMain64(&data);
-    }
-  } else {
-    data.eventStart = ::CreateEvent(nullptr, FALSE /* auto-reset*/,
-                                    FALSE /* initially non-signaled */, nullptr);
-    data.eventEnd = ::CreateEvent(nullptr, FALSE /* auto-reset*/,
-                                  FALSE /* initially non-signaled */, nullptr);
-
-    ::PostThreadMessage(gStackWalkThread, WM_USER, 0, (LPARAM)&data);
+  WalkStackMain64(&data);
 
-    walkerReturn = ::SignalObjectAndWait(data.eventStart,
-                                         data.eventEnd, INFINITE, FALSE);
-    if (walkerReturn != WAIT_OBJECT_0 && data.walkCallingThread) {
-      PrintError("SignalObjectAndWait (1)");
-    }
-    if (data.pc_count > data.pc_size) {
-      data.pcs = (void**)_alloca(data.pc_count * sizeof(void*));
-      data.pc_size = data.pc_count;
-      data.pc_count = 0;
-      data.sps = (void**)_alloca(data.sp_count * sizeof(void*));
-      data.sp_size = data.sp_count;
-      data.sp_count = 0;
-      ::PostThreadMessage(gStackWalkThread, WM_USER, 0, (LPARAM)&data);
-      walkerReturn = ::SignalObjectAndWait(data.eventStart,
-                                           data.eventEnd, INFINITE, FALSE);
-      if (walkerReturn != WAIT_OBJECT_0 && data.walkCallingThread) {
-        PrintError("SignalObjectAndWait (2)");
-      }
-    }
-
-    ::CloseHandle(data.eventStart);
-    ::CloseHandle(data.eventEnd);
+  if (data.pc_count > data.pc_size) {
+    data.pcs = (void**)_alloca(data.pc_count * sizeof(void*));
+    data.pc_size = data.pc_count;
+    data.pc_count = 0;
+    data.sps = (void**)_alloca(data.sp_count * sizeof(void*));
+    data.sp_size = data.sp_count;
+    data.sp_count = 0;
+    WalkStackMain64(&data);
   }
 
   ::CloseHandle(myThread);
 
   for (uint32_t i = 0; i < data.pc_count; ++i) {
     (*aCallback)(i + 1, data.pcs[i], data.sps[i], aClosure);
   }
 }