summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/breakpad-client/windows/common/minidump_callback.cc
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/crashreporter/breakpad-client/windows/common/minidump_callback.cc')
-rw-r--r--toolkit/crashreporter/breakpad-client/windows/common/minidump_callback.cc319
1 files changed, 319 insertions, 0 deletions
diff --git a/toolkit/crashreporter/breakpad-client/windows/common/minidump_callback.cc b/toolkit/crashreporter/breakpad-client/windows/common/minidump_callback.cc
new file mode 100644
index 0000000000..54d8e25274
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/windows/common/minidump_callback.cc
@@ -0,0 +1,319 @@
+/* -*- 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;
+
+using NtQueryInformationThreadFunc = decltype(::NtQueryInformationThread);
+static NtQueryInformationThreadFunc* sNtQueryInformationThread = nullptr;
+
+
+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;
+};
+}
+
+void InitAppMemoryInternal()
+{
+ if (!sPageSize) {
+ SYSTEM_INFO systemInfo;
+ GetSystemInfo(&systemInfo);
+ sPageSize = systemInfo.dwPageSize;
+ }
+
+ if (!sNtQueryInformationThread) {
+ sNtQueryInformationThread = (NtQueryInformationThreadFunc*)
+ (::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"),
+ "NtQueryInformationThread"));
+ }
+}
+
+bool GetAppMemoryFromRegister(HANDLE aProcess,
+ const NT_TIB* aTib,
+ RegisterValueType aRegister,
+ AppMemory* aResult)
+{
+ static_assert(sizeof(RegisterValueType) == sizeof(void*),
+ "Size mismatch between DWORD/DWORD64 and void*");
+
+ if (!sPageSize) {
+ // GetSystemInfo() should not fail, but bail out just in case it fails.
+ return false;
+ }
+
+ 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.
+ return false;
+ }
+
+ // Check protection and type of the memory region. Include the region if it's
+ // 1. read-write: heap, or
+ // 2. read-executable and private: likely to be JIT code.
+ if (memInfo.Protect != PAGE_READWRITE &&
+ memInfo.Protect != PAGE_EXECUTE_READ) {
+ return false;
+ }
+
+ // Try to include a region of size sHeapRegionSize around aRegister, bounded
+ // by the [BaseAddress, BaseAddress + RegionSize].
+ RegisterValueType lower =
+ std::max(aRegister - sHeapRegionSize / 2,
+ reinterpret_cast<RegisterValueType>(memInfo.BaseAddress));
+
+ RegisterValueType upper =
+ std::min(lower + sHeapRegionSize,
+ reinterpret_cast<RegisterValueType>(memInfo.BaseAddress) +
+ memInfo.RegionSize);
+
+ aResult->ptr = lower;
+ aResult->length = upper - lower;
+
+ return true;
+}
+
+static AppMemoryList::iterator
+FindNextPreallocated(AppMemoryList& aList, AppMemoryList::iterator aBegin) {
+ auto it = aBegin;
+ for (auto it = aBegin; it != aList.end(); it++) {
+ if (it->preallocated) {
+ 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;
+ }
+
+ if (!sNtQueryInformationThread) {
+ return false;
+ }
+
+ THREAD_BASIC_INFORMATION threadInfo;
+ auto status = (*sNtQueryInformationThread)(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;
+ heapAddrCandidates[numElements++] = aExceptionContext->Ecx;
+ heapAddrCandidates[numElements++] = aExceptionContext->Edx;
+ heapAddrCandidates[numElements++] = aExceptionContext->Esi;
+ heapAddrCandidates[numElements++] = aExceptionContext->Edi;
+ }
+ heapAddrCandidates[numElements++] = aExceptionContext->Eip;
+#elif defined(_M_AMD64)
+ if (!aInstructionPointerOnly) {
+ heapAddrCandidates[numElements++] = aExceptionContext->Rax;
+ heapAddrCandidates[numElements++] = aExceptionContext->Rbx;
+ heapAddrCandidates[numElements++] = aExceptionContext->Rcx;
+ heapAddrCandidates[numElements++] = aExceptionContext->Rdx;
+ heapAddrCandidates[numElements++] = aExceptionContext->Rsi;
+ heapAddrCandidates[numElements++] = aExceptionContext->Rdi;
+ heapAddrCandidates[numElements++] = aExceptionContext->R8;
+ heapAddrCandidates[numElements++] = aExceptionContext->R9;
+ heapAddrCandidates[numElements++] = aExceptionContext->R10;
+ heapAddrCandidates[numElements++] = aExceptionContext->R11;
+ heapAddrCandidates[numElements++] = aExceptionContext->R12;
+ heapAddrCandidates[numElements++] = aExceptionContext->R13;
+ heapAddrCandidates[numElements++] = aExceptionContext->R14;
+ heapAddrCandidates[numElements++] = aExceptionContext->R15;
+ }
+ heapAddrCandidates[numElements++] = aExceptionContext->Rip;
+#elif defined(_M_ARM64)
+ if (!aInstructionPointerOnly) {
+ for (auto reg : aExceptionContext->X) {
+ heapAddrCandidates[numElements++] = reg;
+ }
+ heapAddrCandidates[numElements++] = aExceptionContext->Sp;
+ }
+ heapAddrCandidates[numElements++] = aExceptionContext->Pc;
+#endif
+
+ // 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++) {
+ if (appMemory == aList.end()) {
+ break;
+ }
+
+ AppMemory tmp{};
+ if (!GetAppMemoryFromRegister(aProcess,
+ aInstructionPointerOnly ? nullptr : &tib,
+ heapAddrCandidates[i],
+ &tmp)) {
+ continue;
+ }
+
+ if (!(tmp.ptr && tmp.length)) {
+ // Something unexpected happens. Skip this candidate.
+ continue;
+ }
+
+ 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) {
+ case MemoryCallback: {
+ MinidumpCallbackContext* callback_context =
+ reinterpret_cast<MinidumpCallbackContext*>(context);
+
+ // Skip unused preallocated AppMemory elements.
+ while (callback_context->iter != callback_context->end &&
+ callback_context->iter->preallocated &&
+ !callback_context->iter->ptr) {
+ callback_context->iter++;
+ }
+
+ if (callback_context->iter == callback_context->end)
+ return FALSE;
+
+ // Include the specified memory region.
+ callback_output->MemoryBase = callback_context->iter->ptr;
+ callback_output->MemorySize = callback_context->iter->length;
+ callback_context->iter++;
+ return TRUE;
+ }
+
+ // Include all modules.
+ case IncludeModuleCallback:
+ case ModuleCallback:
+ return TRUE;
+
+ // Include all threads.
+ case IncludeThreadCallback:
+ case ThreadCallback:
+ return TRUE;
+
+ // Stop receiving cancel callbacks.
+ case CancelCallback:
+ callback_output->CheckCancel = FALSE;
+ callback_output->Cancel = FALSE;
+ return TRUE;
+ }
+ // Ignore other callback types.
+ return FALSE;
+}
+
+} // namespace google_breakpad
+