Bug 1286802 - Part 7: Don't generate overlapping app memory regions in the crash dump. r?ted draft
authorCervantes Yu <cyu@mozilla.com>
Thu, 18 May 2017 17:07:38 +0800
changeset 580340 cafc5ef54d121bc666930c887dfc8125394468f3
parent 580339 b6c23fbfda5d6f91c7cf8ac1d3744a4301bb4013
child 629252 4e81b47730edac55d14c47049db6aef25f273c4a
push id59519
push usercyu@mozilla.com
push dateThu, 18 May 2017 11:09:20 +0000
reviewersted
bugs1286802
milestone55.0a1
Bug 1286802 - Part 7: Don't generate overlapping app memory regions in the crash dump. r?ted This deduplicates/merges app memory regions so that we don't generate overlapping memory regions in the minidump. We need to do this because older version of dbghelp.dll (e.g. that on Windows 7) doesn't handle overlaps in its memory list, which causes xpcshell bustages. The test bustages are fixed by sorting and merging app memory regions before adding to the input to the MinidumpWriteDump callback. Memory regions on the thread stack is also excluded because thread stack memory is included in the minidump by default. MozReview-Commit-ID: 541vzG0b0kC
toolkit/crashreporter/breakpad-client/windows/common/minidump_callback.cc
--- a/toolkit/crashreporter/breakpad-client/windows/common/minidump_callback.cc
+++ b/toolkit/crashreporter/breakpad-client/windows/common/minidump_callback.cc
@@ -1,40 +1,71 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "minidump_callback.h"
 
+#include <winternl.h>
+
 #include <algorithm>
 #include <cassert>
 
 namespace google_breakpad {
 
 static const DWORD sHeapRegionSize= 1024;
 static DWORD sPageSize = 0;
 
+namespace {
+enum {
+  ThreadBasicInformation,
+};
+
+struct CLIENT_ID {
+  PVOID UniqueProcess;
+  PVOID UniqueThread;
+};
+
+struct THREAD_BASIC_INFORMATION {
+  NTSTATUS ExitStatus;
+  PVOID TebBaseAddress;
+  CLIENT_ID ClientId;
+  KAFFINITY AffMask;
+  DWORD Priority;
+  DWORD BasePriority;
+};
+}
+
 bool GetAppMemoryFromRegister(HANDLE aProcess,
+                              const NT_TIB* aTib,
                               RegisterValueType aRegister,
-                              AppMemoryList::iterator aResult)
+                              AppMemory* aResult)
 {
   static_assert(sizeof(RegisterValueType) == sizeof(void*),
                 "Size mismatch between DWORD/DWORD64 and void*");
 
   if (!sPageSize) {
     SYSTEM_INFO systemInfo;
     GetSystemInfo(&systemInfo);
     sPageSize = systemInfo.dwPageSize;
   }
 
   RegisterValueType addr = aRegister;
   addr &= ~(static_cast<RegisterValueType>(sPageSize) - 1);
 
+  if (aTib) {
+    if (aRegister >= (RegisterValueType)aTib->StackLimit &&
+        aRegister <= (RegisterValueType)aTib->StackBase) {
+      // aRegister points to the stack.
+      return false;
+    }
+  }
+
   MEMORY_BASIC_INFORMATION memInfo;
   memset(&memInfo, 0, sizeof(memInfo));
   SIZE_T rv = ::VirtualQueryEx(aProcess,
                                reinterpret_cast<void*>(addr),
                                &memInfo,
                                sizeof(memInfo));
   if (!rv) {
     // VirtualQuery fails: aAddr is not on heap.
@@ -74,24 +105,72 @@ FindNextPreallocated(AppMemoryList& aLis
       return it;
     }
   }
 
   assert(it == aList.end());
   return it;
 }
 
+static bool
+GetThreadTib(HANDLE aProcess, DWORD aThreadId, NT_TIB* aTib) {
+  HANDLE threadHandle = ::OpenThread(THREAD_QUERY_INFORMATION,
+                                     FALSE,
+                                     aThreadId);
+  if (!threadHandle) {
+    return false;
+  }
+
+  using FuncPtr = decltype(::NtQueryInformationThread);
+  FuncPtr* pNtQueryInformationThread =
+    (FuncPtr*)(::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"),
+                                "NtQueryInformationThread"));
+  if (!pNtQueryInformationThread) {
+    return false;
+  }
+
+  THREAD_BASIC_INFORMATION threadInfo;
+  auto status = (*pNtQueryInformationThread)(threadHandle,
+                                             (THREADINFOCLASS)ThreadBasicInformation,
+                                             &threadInfo,
+                                             sizeof(threadInfo),
+                                             NULL);
+  if (!NT_SUCCESS(status)) {
+    return false;
+  }
+
+  auto readSuccess = ::ReadProcessMemory(aProcess,
+                                         threadInfo.TebBaseAddress,
+                                         aTib,
+                                         sizeof(*aTib),
+                                         NULL);
+  if (!readSuccess) {
+    return false;
+  }
+
+  ::CloseHandle(threadHandle);
+  return true;
+}
+
 void IncludeAppMemoryFromExceptionContext(HANDLE aProcess,
                                           DWORD aThreadId,
                                           AppMemoryList& aList,
                                           PCONTEXT aExceptionContext,
                                           bool aInstructionPointerOnly) {
   RegisterValueType heapAddrCandidates[kExceptionAppMemoryRegions];
   size_t numElements = 0;
 
+  NT_TIB tib;
+  memset(&tib, 0, sizeof(tib));
+  if (!GetThreadTib(aProcess, aThreadId, &tib)) {
+    // Fail to query thread stack range: only safe to include the region around
+    // the instruction pointer.
+    aInstructionPointerOnly = true;
+  }
+
   // Add registers that might have a heap address to heapAddrCandidates.
   // Note that older versions of DbgHelp.dll don't correctly put the memory
   // around the faulting instruction pointer into the minidump. Include Rip/Eip
   // unconditionally ensures it gets included.
 #if defined(_M_IX86)
   if (!aInstructionPointerOnly) {
     heapAddrCandidates[numElements++] = aExceptionContext->Eax;
     heapAddrCandidates[numElements++] = aExceptionContext->Ebx;
@@ -116,26 +195,57 @@ void IncludeAppMemoryFromExceptionContex
     heapAddrCandidates[numElements++] = aExceptionContext->R12;
     heapAddrCandidates[numElements++] = aExceptionContext->R13;
     heapAddrCandidates[numElements++] = aExceptionContext->R14;
     heapAddrCandidates[numElements++] = aExceptionContext->R15;
   }
   heapAddrCandidates[numElements++] = aExceptionContext->Rip;
 #endif
 
-  auto appMemory = aList.begin();
+  // Inplace sort the candidates for excluding or merging memory regions.
+  auto begin = &heapAddrCandidates[0], end = &heapAddrCandidates[numElements];
+  std::make_heap(begin, end);
+  std::sort_heap(begin, end);
+
+  auto appMemory = FindNextPreallocated(aList, aList.begin());
   for (size_t i = 0; i < numElements; i++) {
-    appMemory = FindNextPreallocated(aList, appMemory);
     if (appMemory == aList.end()) {
       break;
     }
 
-    if (GetAppMemoryFromRegister(aProcess, heapAddrCandidates[i], appMemory)) {
-      appMemory++;
+    AppMemory tmp{};
+    if (!GetAppMemoryFromRegister(aProcess,
+                                  aInstructionPointerOnly ? nullptr : &tib,
+                                  heapAddrCandidates[i],
+                                  &tmp)) {
+      continue;
+    }
+
+    assert(tmp.ptr && tmp.length);
+
+    if (!appMemory->ptr) {
+      *appMemory = tmp;
+      continue;
     }
+
+    if (appMemory->ptr + appMemory->length > tmp.ptr) {
+      // The beginning of the next region fall within the range of the previous
+      // region: merge into one. Note that we don't merge adjacent regions like
+      // [0, 99] and [100, 199] in case we cross the border of memory allocation
+      // regions.
+      appMemory->length = tmp.ptr + tmp.length - appMemory->ptr;
+      continue;
+    }
+
+    appMemory = FindNextPreallocated(aList, ++appMemory);
+    if (appMemory == aList.end()) {
+      break;
+    }
+
+    *appMemory = tmp;
   }
 }
 
 BOOL CALLBACK MinidumpWriteDumpCallback(
     PVOID context,
     const PMINIDUMP_CALLBACK_INPUT callback_input,
     PMINIDUMP_CALLBACK_OUTPUT callback_output) {
   switch (callback_input->CallbackType) {