summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/minidump-analyzer
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /toolkit/crashreporter/minidump-analyzer
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/crashreporter/minidump-analyzer')
-rw-r--r--toolkit/crashreporter/minidump-analyzer/MinidumpAnalyzerUtils.h115
-rw-r--r--toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.cpp117
-rw-r--r--toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.h48
-rw-r--r--toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp227
-rw-r--r--toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.h56
-rw-r--r--toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp604
-rw-r--r--toolkit/crashreporter/minidump-analyzer/minidump-analyzer.exe.manifest19
-rw-r--r--toolkit/crashreporter/minidump-analyzer/moz.build43
8 files changed, 1229 insertions, 0 deletions
diff --git a/toolkit/crashreporter/minidump-analyzer/MinidumpAnalyzerUtils.h b/toolkit/crashreporter/minidump-analyzer/MinidumpAnalyzerUtils.h
new file mode 100644
index 0000000000..2fb8319be8
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/MinidumpAnalyzerUtils.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef MinidumpAnalyzerUtils_h
+#define MinidumpAnalyzerUtils_h
+
+#ifdef XP_WIN
+# include <windows.h>
+#endif
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <string>
+
+namespace CrashReporter {
+
+struct MinidumpAnalyzerOptions {
+ bool fullMinidump;
+ std::string forceUseModule;
+};
+
+extern MinidumpAnalyzerOptions gMinidumpAnalyzerOptions;
+
+#ifdef XP_WIN
+
+static inline std::string WideToMBCP(const std::wstring& wide, unsigned int cp,
+ bool* success = nullptr) {
+ int bufferCharLen = WideCharToMultiByte(cp, 0, wide.c_str(), wide.length(),
+ nullptr, 0, nullptr, nullptr);
+ if (!bufferCharLen) {
+ if (success) {
+ *success = false;
+ }
+
+ return "";
+ }
+
+ auto buffer = std::make_unique<char[]>(bufferCharLen);
+ if (!buffer) {
+ if (success) {
+ *success = false;
+ }
+
+ return "";
+ }
+
+ int result =
+ WideCharToMultiByte(cp, 0, wide.c_str(), wide.length(), buffer.get(),
+ bufferCharLen, nullptr, nullptr);
+ if (success) {
+ *success = result > 0;
+ }
+
+ return std::string(buffer.get(), result);
+}
+
+static inline std::wstring MBCPToWide(const std::string& aMbStr,
+ unsigned int aCodepage,
+ bool* aSuccess = nullptr) {
+ int bufferCharLen = MultiByteToWideChar(aCodepage, 0, aMbStr.c_str(),
+ aMbStr.length(), nullptr, 0);
+ if (!bufferCharLen) {
+ if (aSuccess) {
+ *aSuccess = false;
+ }
+
+ return L"";
+ }
+
+ auto buffer = std::make_unique<wchar_t[]>(bufferCharLen);
+ if (!buffer) {
+ if (aSuccess) {
+ *aSuccess = false;
+ }
+
+ return L"";
+ }
+
+ int result =
+ MultiByteToWideChar(aCodepage, 0, aMbStr.c_str(), aMbStr.length(),
+ buffer.get(), bufferCharLen);
+ if (aSuccess) {
+ *aSuccess = result > 0;
+ }
+
+ return std::wstring(buffer.get(), result);
+}
+
+static inline std::string WideToUTF8(const std::wstring& aWide,
+ bool* aSuccess = nullptr) {
+ return WideToMBCP(aWide, CP_UTF8, aSuccess);
+}
+
+static inline std::wstring UTF8ToWide(const std::string& aUtf8Str,
+ bool* aSuccess = nullptr) {
+ return MBCPToWide(aUtf8Str, CP_UTF8, aSuccess);
+}
+
+static inline std::string WideToMBCS(const std::wstring& aWide,
+ bool* aSuccess = nullptr) {
+ return WideToMBCP(aWide, CP_ACP, aSuccess);
+}
+
+static inline std::string UTF8ToMBCS(const std::string& aUtf8) {
+ return WideToMBCS(UTF8ToWide(aUtf8));
+}
+
+#endif // XP_WIN
+
+} // namespace CrashReporter
+
+#endif // MinidumpAnalyzerUtils_h
diff --git a/toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.cpp b/toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.cpp
new file mode 100644
index 0000000000..dbc4183429
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.cpp
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#if XP_WIN && HAVE_64BIT_BUILD
+
+# include "MozStackFrameSymbolizer.h"
+
+# include "MinidumpAnalyzerUtils.h"
+
+# include "processor/cfi_frame_info.h"
+
+# include <iostream>
+# include <sstream>
+# include <fstream>
+
+namespace CrashReporter {
+
+extern MinidumpAnalyzerOptions gMinidumpAnalyzerOptions;
+
+using google_breakpad::CFIFrameInfo;
+
+MozStackFrameSymbolizer::MozStackFrameSymbolizer()
+ : StackFrameSymbolizer(nullptr, nullptr) {}
+
+MozStackFrameSymbolizer::SymbolizerResult
+MozStackFrameSymbolizer::FillSourceLineInfo(const CodeModules* modules,
+ const CodeModules* unloaded_modules,
+ const SystemInfo* system_info,
+ StackFrame* stack_frame) {
+ SymbolizerResult ret = StackFrameSymbolizer::FillSourceLineInfo(
+ modules, unloaded_modules, system_info, stack_frame);
+
+ if (ret == kNoError && this->HasImplementation() &&
+ stack_frame->function_name.empty()) {
+ // Breakpad's Stackwalker::InstructionAddressSeemsValid only considers an
+ // address valid if it has associated symbols.
+ //
+ // This makes sense for complete & accurate symbols, but ours may be
+ // incomplete or wrong. Returning a function name tells Breakpad we
+ // recognize this address as code, so it's OK to use in stack scanning.
+ // This function is only called with addresses that land in this module.
+ //
+ // This allows us to fall back to stack scanning in the case where we were
+ // unable to provide CFI.
+ stack_frame->function_name = "<unknown code>";
+ }
+ return ret;
+}
+
+CFIFrameInfo* MozStackFrameSymbolizer::FindCFIFrameInfo(
+ const StackFrame* frame) {
+ std::string modulePath;
+
+ // For unit testing, support loading a specified module instead of
+ // the real one.
+ bool moduleHasBeenReplaced = false;
+ if (gMinidumpAnalyzerOptions.forceUseModule.size() > 0) {
+ modulePath = gMinidumpAnalyzerOptions.forceUseModule;
+ moduleHasBeenReplaced = true;
+ } else {
+ if (!frame->module) {
+ return nullptr;
+ }
+ modulePath = frame->module->code_file();
+ }
+
+ // Get/create the unwind parser.
+ auto itMod = mModuleMap.find(modulePath);
+ std::shared_ptr<ModuleUnwindParser> unwindParser;
+ if (itMod != mModuleMap.end()) {
+ unwindParser = itMod->second;
+ } else {
+ unwindParser.reset(new ModuleUnwindParser(modulePath));
+ mModuleMap[modulePath] = unwindParser;
+ }
+
+ UnwindCFI cfi;
+ DWORD offsetAddr;
+
+ if (moduleHasBeenReplaced) {
+ // If we are replacing a module, addresses will never line up.
+ // So just act like the 1st entry is correct.
+ offsetAddr = unwindParser->GetAnyOffsetAddr();
+ } else {
+ offsetAddr = frame->instruction - frame->module->base_address();
+ }
+
+ if (!unwindParser->GetCFI(offsetAddr, cfi)) {
+ return nullptr;
+ }
+
+ std::unique_ptr<CFIFrameInfo> rules(new CFIFrameInfo());
+
+ static const size_t exprSize = 50;
+ char expr[exprSize];
+ if (cfi.stackSize == 0) {
+ snprintf(expr, exprSize, "$rsp");
+ } else {
+ snprintf(expr, exprSize, "$rsp %d +", cfi.stackSize);
+ }
+ rules->SetCFARule(expr);
+
+ if (cfi.ripOffset == 0) {
+ snprintf(expr, exprSize, ".cfa ^");
+ } else {
+ snprintf(expr, exprSize, ".cfa %d - ^", cfi.ripOffset);
+ }
+ rules->SetRARule(expr);
+
+ return rules.release();
+}
+
+} // namespace CrashReporter
+
+#endif // XP_WIN && HAVE_64BIT_BUILD
diff --git a/toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.h b/toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.h
new file mode 100644
index 0000000000..dfe4299d85
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef MozStackFrameSymbolizer_h
+#define MozStackFrameSymbolizer_h
+
+#if XP_WIN && HAVE_64BIT_BUILD
+
+# include "Win64ModuleUnwindMetadata.h"
+
+# include "google_breakpad/processor/stack_frame_symbolizer.h"
+# include "google_breakpad/processor/stack_frame.h"
+
+# include <memory>
+
+namespace CrashReporter {
+
+using google_breakpad::CodeModule;
+using google_breakpad::CodeModules;
+using google_breakpad::SourceLineResolverInterface;
+using google_breakpad::StackFrame;
+using google_breakpad::StackFrameSymbolizer;
+using google_breakpad::SymbolSupplier;
+using google_breakpad::SystemInfo;
+
+class MozStackFrameSymbolizer : public StackFrameSymbolizer {
+ using google_breakpad::StackFrameSymbolizer::SymbolizerResult;
+
+ std::map<std::string, std::shared_ptr<ModuleUnwindParser>> mModuleMap;
+
+ public:
+ MozStackFrameSymbolizer();
+
+ virtual SymbolizerResult FillSourceLineInfo(
+ const CodeModules* modules, const CodeModules* unloaded_modules,
+ const SystemInfo* system_info, StackFrame* stack_frame);
+
+ virtual class google_breakpad::CFIFrameInfo* FindCFIFrameInfo(
+ const StackFrame* frame);
+};
+
+} // namespace CrashReporter
+
+#endif // XP_WIN && HAVE_64BIT_BUILD
+
+#endif // MozStackFrameSymbolizer_h
diff --git a/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp b/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp
new file mode 100644
index 0000000000..4978415e86
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp
@@ -0,0 +1,227 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#if XP_WIN && HAVE_64BIT_BUILD
+
+# include "Win64ModuleUnwindMetadata.h"
+
+# include "MinidumpAnalyzerUtils.h"
+
+# include <windows.h>
+# include <winnt.h>
+# include <imagehlp.h>
+# include <iostream>
+# include <set>
+# include <sstream>
+# include <string>
+
+# include "mozilla/WindowsUnwindInfo.h"
+
+using namespace mozilla;
+
+namespace CrashReporter {
+
+ModuleUnwindParser::~ModuleUnwindParser() {
+ if (mImg) {
+ ImageUnload(mImg);
+ }
+}
+
+void* ModuleUnwindParser::RvaToVa(ULONG aRva) {
+ return ImageRvaToVa(mImg->FileHeader, mImg->MappedAddress, aRva,
+ &mImg->LastRvaSection);
+}
+
+ModuleUnwindParser::ModuleUnwindParser(const std::string& aPath)
+ : mPath(aPath) {
+ // Convert wchar to native charset because ImageLoad only takes
+ // a PSTR as input.
+ std::string code_file = UTF8ToMBCS(aPath);
+
+ mImg = ImageLoad((PSTR)code_file.c_str(), NULL);
+ if (!mImg || !mImg->FileHeader) {
+ return;
+ }
+
+ PIMAGE_OPTIONAL_HEADER64 optional_header = &mImg->FileHeader->OptionalHeader;
+ if (optional_header->Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
+ return;
+ }
+
+ DWORD exception_rva =
+ optional_header->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION]
+ .VirtualAddress;
+
+ DWORD exception_size =
+ optional_header->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size;
+
+ auto funcs = (PIMAGE_RUNTIME_FUNCTION_ENTRY)RvaToVa(exception_rva);
+ if (!funcs) {
+ return;
+ }
+
+ for (DWORD i = 0; i < exception_size / sizeof(*funcs); i++) {
+ mUnwindMap[funcs[i].BeginAddress] = &funcs[i];
+ }
+}
+
+bool ModuleUnwindParser::GenerateCFIForFunction(
+ IMAGE_RUNTIME_FUNCTION_ENTRY& aFunc, UnwindCFI& aRet) {
+ DWORD unwind_rva = aFunc.UnwindInfoAddress;
+ // Holds RVA to all visited IMAGE_RUNTIME_FUNCTION_ENTRY, to avoid
+ // circular references.
+ std::set<DWORD> visited;
+
+ // Follow chained function entries
+ while (unwind_rva & 0x1) {
+ unwind_rva ^= 0x1;
+
+ if (visited.end() != visited.find(unwind_rva)) {
+ return false;
+ }
+ visited.insert(unwind_rva);
+
+ auto chained_func = (PIMAGE_RUNTIME_FUNCTION_ENTRY)RvaToVa(unwind_rva);
+ if (!chained_func) {
+ return false;
+ }
+ unwind_rva = chained_func->UnwindInfoAddress;
+ }
+
+ visited.insert(unwind_rva);
+
+ auto unwind_info = (UnwindInfo*)RvaToVa(unwind_rva);
+ if (!unwind_info) {
+ return false;
+ }
+
+ DWORD stack_size = 8; // minimal stack size is 8 for RIP
+ DWORD rip_offset = 8;
+ do {
+ for (uint8_t c = 0; c < unwind_info->count_of_codes; c++) {
+ UnwindCode* unwind_code = &unwind_info->unwind_code[c];
+ switch (unwind_code->unwind_operation_code) {
+ case UWOP_PUSH_NONVOL: {
+ stack_size += 8;
+ break;
+ }
+ case UWOP_ALLOC_LARGE: {
+ if (unwind_code->operation_info == 0) {
+ c++;
+ if (c < unwind_info->count_of_codes) {
+ stack_size += (unwind_code + 1)->frame_offset * 8;
+ }
+ } else {
+ c += 2;
+ if (c < unwind_info->count_of_codes) {
+ stack_size += (unwind_code + 1)->frame_offset |
+ ((unwind_code + 2)->frame_offset << 16);
+ }
+ }
+ break;
+ }
+ case UWOP_ALLOC_SMALL: {
+ stack_size += unwind_code->operation_info * 8 + 8;
+ break;
+ }
+ case UWOP_SET_FPREG:
+ // To correctly track RSP when it's been transferred to another
+ // register, we would need to emit CFI records for every unwind op.
+ // For simplicity, don't emit CFI records for this function as
+ // we know it will be incorrect after this point.
+ return false;
+ case UWOP_SAVE_NONVOL:
+ case UWOP_SAVE_XMM: // also v2 UWOP_EPILOG
+ case UWOP_SAVE_XMM128: {
+ c++; // skip slot with offset
+ break;
+ }
+ case UWOP_SAVE_NONVOL_FAR:
+ case UWOP_SAVE_XMM_FAR: // also v2 UWOP_SPARE
+ case UWOP_SAVE_XMM128_FAR: {
+ c += 2; // skip 2 slots with offset
+ break;
+ }
+ case UWOP_PUSH_MACHFRAME: {
+ if (unwind_code->operation_info) {
+ stack_size += 88;
+ } else {
+ stack_size += 80;
+ }
+ rip_offset += 80;
+ break;
+ }
+ default: {
+ return false;
+ }
+ }
+ }
+
+ if (unwind_info->flags & UNW_FLAG_CHAININFO) {
+ auto chained_func = (PIMAGE_RUNTIME_FUNCTION_ENTRY)((
+ unwind_info->unwind_code + ((unwind_info->count_of_codes + 1) & ~1)));
+
+ if (visited.end() != visited.find(chained_func->UnwindInfoAddress)) {
+ return false; // Circular reference
+ }
+
+ visited.insert(chained_func->UnwindInfoAddress);
+
+ unwind_info = (UnwindInfo*)RvaToVa(chained_func->UnwindInfoAddress);
+ } else {
+ unwind_info = nullptr;
+ }
+ } while (unwind_info);
+
+ aRet.beginAddress = aFunc.BeginAddress;
+ aRet.size = aFunc.EndAddress - aFunc.BeginAddress;
+ aRet.stackSize = stack_size;
+ aRet.ripOffset = rip_offset;
+ return true;
+}
+
+// For unit testing we sometimes need any address that's valid in this module.
+// Just return the first address we know of.
+DWORD
+ModuleUnwindParser::GetAnyOffsetAddr() const {
+ if (mUnwindMap.size() < 1) {
+ return 0;
+ }
+ return mUnwindMap.begin()->first;
+}
+
+bool ModuleUnwindParser::GetCFI(DWORD aAddress, UnwindCFI& aRet) {
+ // Figure out the begin address of the requested address.
+ auto itUW = mUnwindMap.lower_bound(aAddress + 1);
+ if (itUW == mUnwindMap.begin()) {
+ return false; // address before this module.
+ }
+ --itUW;
+
+ // Ensure that the function entry is big enough to contain this address.
+ IMAGE_RUNTIME_FUNCTION_ENTRY& func = *itUW->second;
+ if (aAddress > func.EndAddress) {
+ return false;
+ }
+
+ // Do we have CFI for this function already?
+ auto itCFI = mCFIMap.find(aAddress);
+ if (itCFI != mCFIMap.end()) {
+ aRet = itCFI->second;
+ return true;
+ }
+
+ // No, generate it.
+ if (!GenerateCFIForFunction(func, aRet)) {
+ return false;
+ }
+
+ mCFIMap[func.BeginAddress] = aRet;
+ return true;
+}
+
+} // namespace CrashReporter
+
+#endif // XP_WIN && HAVE_64BIT_BUILD
diff --git a/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.h b/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.h
new file mode 100644
index 0000000000..3ce9dd7bb4
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef Win64ModuleUnwindMetadata_h
+#define Win64ModuleUnwindMetadata_h
+
+#if XP_WIN && HAVE_64BIT_BUILD
+
+# include <functional>
+# include <map>
+# include <string>
+
+# include <windows.h>
+# include <winnt.h>
+# include <imagehlp.h>
+
+namespace CrashReporter {
+
+struct UnwindCFI {
+ uint32_t beginAddress;
+ uint32_t size;
+ uint32_t stackSize;
+ uint32_t ripOffset;
+};
+
+// Does lazy-parsing of unwind info.
+class ModuleUnwindParser {
+ PLOADED_IMAGE mImg;
+ std::string mPath;
+
+ // Maps begin address to exception record.
+ // Populated upon construction.
+ std::map<DWORD, PIMAGE_RUNTIME_FUNCTION_ENTRY> mUnwindMap;
+
+ // Maps begin address to CFI.
+ // Populated as needed.
+ std::map<DWORD, UnwindCFI> mCFIMap;
+
+ bool GenerateCFIForFunction(IMAGE_RUNTIME_FUNCTION_ENTRY& aFunc,
+ UnwindCFI& aRet);
+ void* RvaToVa(ULONG aRva);
+
+ public:
+ explicit ModuleUnwindParser(const std::string& aPath);
+ ~ModuleUnwindParser();
+ bool GetCFI(DWORD aAddress, UnwindCFI& aRet);
+ DWORD GetAnyOffsetAddr() const;
+};
+
+} // namespace CrashReporter
+
+#endif // XP_WIN && HAVE_64BIT_BUILD
+
+#endif // Win64ModuleUnwindMetadata_h
diff --git a/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp b/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp
new file mode 100644
index 0000000000..a90b7edcbb
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp
@@ -0,0 +1,604 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 <cstdio>
+#include <cstring>
+#include <string>
+#include <sstream>
+
+#include "json/json.h"
+#include "google_breakpad/processor/basic_source_line_resolver.h"
+#include "google_breakpad/processor/call_stack.h"
+#include "google_breakpad/processor/code_module.h"
+#include "google_breakpad/processor/code_modules.h"
+#include "google_breakpad/processor/minidump.h"
+#include "google_breakpad/processor/minidump_processor.h"
+#include "google_breakpad/processor/process_state.h"
+#include "google_breakpad/processor/stack_frame.h"
+#include "processor/pathname_stripper.h"
+
+#include "mozilla/FStream.h"
+#include "mozilla/Unused.h"
+
+#if defined(XP_WIN)
+
+# include <windows.h>
+# include "mozilla/glue/WindowsDllServices.h"
+
+#elif defined(XP_UNIX) || defined(XP_MACOSX)
+
+# include <sys/types.h>
+# include <sys/stat.h>
+# include <unistd.h>
+
+#endif
+
+#include "MinidumpAnalyzerUtils.h"
+
+#if XP_WIN && HAVE_64BIT_BUILD && defined(_M_X64)
+# include "MozStackFrameSymbolizer.h"
+#endif
+
+namespace CrashReporter {
+
+#if defined(XP_WIN)
+
+static mozilla::glue::BasicDllServices gDllServices;
+
+#endif
+
+using std::hex;
+using std::ios;
+using std::ios_base;
+using std::map;
+using std::showbase;
+using std::string;
+using std::stringstream;
+using std::wstring;
+
+using google_breakpad::BasicSourceLineResolver;
+using google_breakpad::CallStack;
+using google_breakpad::CodeModule;
+using google_breakpad::CodeModules;
+using google_breakpad::Minidump;
+using google_breakpad::MinidumpProcessor;
+using google_breakpad::PathnameStripper;
+using google_breakpad::ProcessResult;
+using google_breakpad::ProcessState;
+using google_breakpad::StackFrame;
+
+using mozilla::IFStream;
+using mozilla::OFStream;
+using mozilla::Unused;
+
+MinidumpAnalyzerOptions gMinidumpAnalyzerOptions;
+
+// Path of the minidump to be analyzed.
+static string gMinidumpPath;
+
+struct ModuleCompare {
+ bool operator()(const CodeModule* aLhs, const CodeModule* aRhs) const {
+ return aLhs->base_address() < aRhs->base_address();
+ }
+};
+
+typedef map<const CodeModule*, unsigned int, ModuleCompare> OrderedModulesMap;
+
+static void AddModulesFromCallStack(OrderedModulesMap& aOrderedModules,
+ const CallStack* aStack) {
+ int frameCount = aStack->frames()->size();
+
+ for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
+ const StackFrame* frame = aStack->frames()->at(frameIndex);
+
+ if (frame->module) {
+ aOrderedModules.insert(
+ std::pair<const CodeModule*, unsigned int>(frame->module, 0));
+ }
+ }
+}
+
+static void PopulateModuleList(const ProcessState& aProcessState,
+ OrderedModulesMap& aOrderedModules,
+ bool aFullStacks) {
+ int threadCount = aProcessState.threads()->size();
+ int requestingThread = aProcessState.requesting_thread();
+
+ if (!aFullStacks && (requestingThread != -1)) {
+ AddModulesFromCallStack(aOrderedModules,
+ aProcessState.threads()->at(requestingThread));
+ } else {
+ for (int threadIndex = 0; threadIndex < threadCount; ++threadIndex) {
+ AddModulesFromCallStack(aOrderedModules,
+ aProcessState.threads()->at(threadIndex));
+ }
+ }
+
+ int moduleCount = 0;
+ for (auto& itr : aOrderedModules) {
+ itr.second = moduleCount++;
+ }
+}
+
+static const char kExtraDataExtension[] = ".extra";
+
+static string ToHex(uint64_t aValue) {
+ stringstream output;
+
+ output << hex << showbase << aValue;
+
+ return output.str();
+}
+
+// Convert the stack frame trust value into a readable string.
+
+static string FrameTrust(const StackFrame::FrameTrust aTrust) {
+ switch (aTrust) {
+ case StackFrame::FRAME_TRUST_NONE:
+ return "none";
+ case StackFrame::FRAME_TRUST_SCAN:
+ return "scan";
+ case StackFrame::FRAME_TRUST_CFI_SCAN:
+ return "cfi_scan";
+ case StackFrame::FRAME_TRUST_FP:
+ return "frame_pointer";
+ case StackFrame::FRAME_TRUST_CFI:
+ return "cfi";
+ case StackFrame::FRAME_TRUST_PREWALKED:
+ return "prewalked";
+ case StackFrame::FRAME_TRUST_CONTEXT:
+ return "context";
+ }
+
+ return "none";
+}
+
+// Convert the result value of the minidump processing step into a readable
+// string.
+
+static string ResultString(ProcessResult aResult) {
+ switch (aResult) {
+ case google_breakpad::PROCESS_OK:
+ return "OK";
+ case google_breakpad::PROCESS_ERROR_MINIDUMP_NOT_FOUND:
+ return "ERROR_MINIDUMP_NOT_FOUND";
+ case google_breakpad::PROCESS_ERROR_NO_MINIDUMP_HEADER:
+ return "ERROR_NO_MINIDUMP_HEADER";
+ case google_breakpad::PROCESS_ERROR_NO_THREAD_LIST:
+ return "ERROR_NO_THREAD_LIST";
+ case google_breakpad::PROCESS_ERROR_GETTING_THREAD:
+ return "ERROR_GETTING_THREAD";
+ case google_breakpad::PROCESS_ERROR_GETTING_THREAD_ID:
+ return "ERROR_GETTING_THREAD_ID";
+ case google_breakpad::PROCESS_ERROR_DUPLICATE_REQUESTING_THREADS:
+ return "ERROR_DUPLICATE_REQUESTING_THREADS";
+ case google_breakpad::PROCESS_SYMBOL_SUPPLIER_INTERRUPTED:
+ return "SYMBOL_SUPPLIER_INTERRUPTED";
+ default:
+ return "";
+ }
+}
+
+// Convert the list of stack frames to JSON and append them to the array
+// specified in the |aNode| parameter.
+
+static void ConvertStackToJSON(const ProcessState& aProcessState,
+ const OrderedModulesMap& aOrderedModules,
+ const CallStack* aStack, Json::Value& aNode) {
+ int frameCount = aStack->frames()->size();
+
+ for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
+ const StackFrame* frame = aStack->frames()->at(frameIndex);
+ Json::Value frameNode;
+
+ if (frame->module) {
+ const auto& itr = aOrderedModules.find(frame->module);
+
+ if (itr != aOrderedModules.end()) {
+ frameNode["module_index"] = (*itr).second;
+ }
+ }
+
+ frameNode["trust"] = FrameTrust(frame->trust);
+ // The 'ip' field is equivalent to socorro's 'offset' field
+ frameNode["ip"] = ToHex(frame->instruction);
+
+ aNode.append(frameNode);
+ }
+}
+
+// Extract the list of certifications subjects from the list of modules and
+// store it in the |aCertSubjects| parameter
+
+static void RetrieveCertSubjects(const CodeModules* modules,
+ Json::Value& aCertSubjects) {
+#if defined(XP_WIN)
+ if (modules) {
+ for (size_t i = 0; i < modules->module_count(); i++) {
+ const CodeModule* module = modules->GetModuleAtIndex(i);
+ auto certSubject = gDllServices.GetBinaryOrgName(
+ UTF8ToWide(module->code_file()).c_str());
+ if (certSubject) {
+ string strSubject(WideToUTF8(certSubject.get()));
+ // Json::Value::operator[] creates and returns a null member if the key
+ // does not exist.
+ Json::Value& subjectNode = aCertSubjects[strSubject];
+ if (!subjectNode) {
+ // If the member is null, we want to convert that to an array.
+ subjectNode = Json::Value(Json::arrayValue);
+ }
+
+ // Now we're guaranteed that subjectNode is an array. Add the new entry.
+ subjectNode.append(PathnameStripper::File(module->code_file()));
+ }
+ }
+ }
+#endif // defined(XP_WIN)
+}
+
+// Convert the list of modules to JSON and append them to the array specified
+// in the |aNode| parameter.
+
+static int ConvertModulesToJSON(const ProcessState& aProcessState,
+ const OrderedModulesMap& aOrderedModules,
+ Json::Value& aNode) {
+ const CodeModules* modules = aProcessState.modules();
+
+ if (!modules) {
+ return -1;
+ }
+
+ uint64_t mainAddress = 0;
+ const CodeModule* mainModule = modules->GetMainModule();
+
+ if (mainModule) {
+ mainAddress = mainModule->base_address();
+ }
+
+ int mainModuleIndex = -1;
+
+ for (const auto& itr : aOrderedModules) {
+ const CodeModule* module = itr.first;
+
+ if ((module->base_address() == mainAddress) && mainModule) {
+ mainModuleIndex = itr.second;
+ }
+
+ Json::Value moduleNode;
+ moduleNode["filename"] = PathnameStripper::File(module->code_file());
+ moduleNode["code_id"] = PathnameStripper::File(module->code_identifier());
+ moduleNode["version"] = module->version();
+ moduleNode["debug_file"] = PathnameStripper::File(module->debug_file());
+ moduleNode["debug_id"] = module->debug_identifier();
+ moduleNode["base_addr"] = ToHex(module->base_address());
+ moduleNode["end_addr"] = ToHex(module->base_address() + module->size());
+
+ aNode.append(moduleNode);
+ }
+
+ return mainModuleIndex;
+}
+
+// Convert the list of unloaded modules to JSON and append them to the array
+// specified in the |aNode| parameter. Return the number of unloaded modules
+// that were found.
+
+static size_t ConvertUnloadedModulesToJSON(const ProcessState& aProcessState,
+ Json::Value& aNode) {
+ const CodeModules* unloadedModules = aProcessState.unloaded_modules();
+ if (!unloadedModules) {
+ return 0;
+ }
+
+ const size_t unloadedModulesLen = unloadedModules->module_count();
+ for (size_t i = 0; i < unloadedModulesLen; i++) {
+ const CodeModule* unloadedModule = unloadedModules->GetModuleAtIndex(i);
+
+ Json::Value unloadedModuleNode;
+ unloadedModuleNode["filename"] =
+ PathnameStripper::File(unloadedModule->code_file());
+ unloadedModuleNode["code_id"] =
+ PathnameStripper::File(unloadedModule->code_identifier());
+ unloadedModuleNode["base_addr"] = ToHex(unloadedModule->base_address());
+ unloadedModuleNode["end_addr"] =
+ ToHex(unloadedModule->base_address() + unloadedModule->size());
+
+ aNode.append(unloadedModuleNode);
+ }
+
+ return unloadedModulesLen;
+}
+
+// Convert the process state to JSON, this includes information about the
+// crash, the module list and stack traces for every thread
+
+static void ConvertProcessStateToJSON(const ProcessState& aProcessState,
+ Json::Value& aStackTraces,
+ const bool aFullStacks,
+ Json::Value& aCertSubjects) {
+ // Crash info
+ Json::Value crashInfo;
+ int requestingThread = aProcessState.requesting_thread();
+
+ if (aProcessState.crashed()) {
+ crashInfo["type"] = aProcessState.crash_reason();
+ crashInfo["address"] = ToHex(aProcessState.crash_address());
+
+ if (requestingThread != -1) {
+ // Record the crashing thread index only if this is a full minidump
+ // and all threads' stacks are present, otherwise only the crashing
+ // thread stack is written out and this field is set to 0.
+ crashInfo["crashing_thread"] = aFullStacks ? requestingThread : 0;
+ }
+ } else {
+ crashInfo["type"] = Json::Value(Json::nullValue);
+ // Add assertion info, if available
+ string assertion = aProcessState.assertion();
+
+ if (!assertion.empty()) {
+ crashInfo["assertion"] = assertion;
+ }
+ }
+
+ aStackTraces["crash_info"] = crashInfo;
+
+ // Modules
+ OrderedModulesMap orderedModules;
+ PopulateModuleList(aProcessState, orderedModules, aFullStacks);
+
+ Json::Value modules(Json::arrayValue);
+ int mainModule = ConvertModulesToJSON(aProcessState, orderedModules, modules);
+
+ if (mainModule != -1) {
+ aStackTraces["main_module"] = mainModule;
+ }
+
+ aStackTraces["modules"] = modules;
+
+ Json::Value unloadedModules(Json::arrayValue);
+ size_t unloadedModulesLen =
+ ConvertUnloadedModulesToJSON(aProcessState, unloadedModules);
+
+ if (unloadedModulesLen > 0) {
+ aStackTraces["unloaded_modules"] = unloadedModules;
+ }
+
+ RetrieveCertSubjects(aProcessState.modules(), aCertSubjects);
+ RetrieveCertSubjects(aProcessState.unloaded_modules(), aCertSubjects);
+
+ // Threads
+ Json::Value threads(Json::arrayValue);
+ int threadCount = aProcessState.threads()->size();
+
+ if (!aFullStacks && (requestingThread != -1)) {
+ // Only add the crashing thread
+ Json::Value thread;
+ Json::Value stack(Json::arrayValue);
+ const CallStack* rawStack = aProcessState.threads()->at(requestingThread);
+
+ ConvertStackToJSON(aProcessState, orderedModules, rawStack, stack);
+ thread["frames"] = stack;
+ threads.append(thread);
+ } else {
+ for (int threadIndex = 0; threadIndex < threadCount; ++threadIndex) {
+ Json::Value thread;
+ Json::Value stack(Json::arrayValue);
+ const CallStack* rawStack = aProcessState.threads()->at(threadIndex);
+
+ ConvertStackToJSON(aProcessState, orderedModules, rawStack, stack);
+ thread["frames"] = stack;
+ threads.append(thread);
+ }
+ }
+
+ aStackTraces["threads"] = threads;
+}
+
+// Process the minidump file and append the JSON-formatted stack traces to
+// the node specified in |aStackTraces|. We also populate |aCertSubjects| with
+// information about the certificates used to sign modules, when present and
+// supported by the underlying OS.
+static bool ProcessMinidump(Json::Value& aStackTraces,
+ Json::Value& aCertSubjects, const string& aDumpFile,
+ const bool aFullStacks) {
+#if XP_WIN && HAVE_64BIT_BUILD && defined(_M_X64)
+ MozStackFrameSymbolizer symbolizer;
+ MinidumpProcessor minidumpProcessor(&symbolizer, false);
+#else
+ BasicSourceLineResolver resolver;
+ // We don't have a valid symbol resolver so we pass nullptr instead.
+ MinidumpProcessor minidumpProcessor(nullptr, &resolver);
+#endif
+
+ // Process the minidump.
+#if defined(XP_WIN)
+ // Breakpad invokes std::ifstream directly, so this path needs to be ANSI
+ Minidump dump(UTF8ToMBCS(aDumpFile));
+#else
+ Minidump dump(aDumpFile);
+#endif // defined(XP_WIN)
+ if (!dump.Read()) {
+ return false;
+ }
+
+ ProcessResult rv;
+ ProcessState processState;
+ rv = minidumpProcessor.Process(&dump, &processState);
+ aStackTraces["status"] = ResultString(rv);
+
+ ConvertProcessStateToJSON(processState, aStackTraces, aFullStacks,
+ aCertSubjects);
+
+ return true;
+}
+
+static bool ReadExtraFile(const string& aExtraDataPath, Json::Value& aExtra) {
+ IFStream f(
+#if defined(XP_WIN)
+ UTF8ToWide(aExtraDataPath).c_str(),
+#else
+ aExtraDataPath.c_str(),
+#endif // defined(XP_WIN)
+ ios::in);
+ if (!f.is_open()) {
+ return false;
+ }
+
+ Json::CharReaderBuilder builder;
+ return parseFromStream(builder, f, &aExtra, nullptr);
+}
+
+// Update the extra data file by adding the StackTraces and ModuleSignatureInfo
+// fields that contain the JSON outputs of this program.
+static bool UpdateExtraDataFile(const string& aDumpPath,
+ const Json::Value& aStackTraces,
+ const Json::Value& aCertSubjects) {
+ string extraDataPath(aDumpPath);
+ int dot = extraDataPath.rfind('.');
+
+ if (dot < 0) {
+ return false; // Not a valid dump path
+ }
+
+ extraDataPath.replace(dot, extraDataPath.length() - dot, kExtraDataExtension);
+
+ Json::Value extra;
+ if (!ReadExtraFile(extraDataPath, extra)) {
+ return false;
+ }
+
+ OFStream f(
+#if defined(XP_WIN)
+ UTF8ToWide(extraDataPath).c_str(),
+#else
+ extraDataPath.c_str(),
+#endif // defined(XP_WIN)
+ ios::out | ios::trunc);
+
+ bool res = false;
+ if (f.is_open()) {
+ Json::StreamWriterBuilder builder;
+ builder["indentation"] = "";
+
+ // The StackTraces field is not stored as a string because it's not a
+ // crash annotation. It's only used by the crash reporter client which
+ // strips it before submitting the other annotations to Socorro.
+ extra["StackTraces"] = aStackTraces;
+
+ if (!!aCertSubjects) {
+ extra["ModuleSignatureInfo"] = Json::writeString(builder, aCertSubjects);
+ }
+
+ std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
+ writer->write(extra, &f);
+ f << "\n";
+ res = !f.fail();
+ f.close();
+ }
+
+ return res;
+}
+
+static bool GenerateStacks(const string& aDumpPath, const bool aFullStacks) {
+ Json::Value stackTraces;
+ Json::Value certSubjects;
+
+ if (!ProcessMinidump(stackTraces, certSubjects, aDumpPath, aFullStacks)) {
+ return false;
+ }
+
+ return UpdateExtraDataFile(aDumpPath, stackTraces, certSubjects);
+}
+
+} // namespace CrashReporter
+
+using namespace CrashReporter;
+
+#if defined(XP_WIN)
+# define XP_LITERAL(s) L##s
+#else
+# define XP_LITERAL(s) s
+#endif
+
+template <typename CharT>
+struct CharTraits;
+
+template <>
+struct CharTraits<char> {
+ static int compare(const char* left, const char* right) {
+ return strcmp(left, right);
+ }
+
+ static string& assign(string& left, const char* right) {
+ left = right;
+ return left;
+ }
+};
+
+#if defined(XP_WIN)
+
+template <>
+struct CharTraits<wchar_t> {
+ static int compare(const wchar_t* left, const wchar_t* right) {
+ return wcscmp(left, right);
+ }
+
+ static string& assign(string& left, const wchar_t* right) {
+ left = WideToUTF8(right);
+ return left;
+ }
+};
+
+#endif // defined(XP_WIN)
+
+static void LowerPriority() {
+#if defined(XP_WIN)
+ Unused << SetPriorityClass(GetCurrentProcess(),
+ PROCESS_MODE_BACKGROUND_BEGIN);
+#else // Linux, MacOS X, etc...
+ Unused << nice(20);
+#endif
+}
+
+template <typename CharT, typename Traits = CharTraits<CharT>>
+static void ParseArguments(int argc, CharT** argv) {
+ if (argc <= 1) {
+ exit(EXIT_FAILURE);
+ }
+
+ for (int i = 1; i < argc - 1; i++) {
+ if (!Traits::compare(argv[i], XP_LITERAL("--full"))) {
+ gMinidumpAnalyzerOptions.fullMinidump = true;
+ } else if (!Traits::compare(argv[i], XP_LITERAL("--force-use-module")) &&
+ (i < argc - 2)) {
+ Traits::assign(gMinidumpAnalyzerOptions.forceUseModule, argv[i + 1]);
+ ++i;
+ } else {
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ Traits::assign(gMinidumpPath, argv[argc - 1]);
+}
+
+#if defined(XP_WIN)
+// WARNING: Windows does *NOT* use UTF8 for char strings off the command line!
+// Using wmain here so that the CRT doesn't need to perform a wasteful and
+// lossy UTF-16 to MBCS conversion; ParseArguments will convert to UTF8
+// directly.
+extern "C" int wmain(int argc, wchar_t** argv)
+#else
+int main(int argc, char** argv)
+#endif
+{
+ LowerPriority();
+ ParseArguments(argc, argv);
+
+ if (!GenerateStacks(gMinidumpPath, gMinidumpAnalyzerOptions.fullMinidump)) {
+ exit(EXIT_FAILURE);
+ }
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.exe.manifest b/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.exe.manifest
new file mode 100644
index 0000000000..731502e805
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.exe.manifest
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+<assemblyIdentity
+ version="1.0.0.0"
+ processorArchitecture="*"
+ name="minidump-analyzer"
+ type="win32"
+/>
+<dependency>
+ <dependentAssembly>
+ <assemblyIdentity
+ type="win32"
+ name="mozglue"
+ version="1.0.0.0"
+ language="*"
+ />
+ </dependentAssembly>
+</dependency>
+</assembly>
diff --git a/toolkit/crashreporter/minidump-analyzer/moz.build b/toolkit/crashreporter/minidump-analyzer/moz.build
new file mode 100644
index 0000000000..b6b3cebfea
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/moz.build
@@ -0,0 +1,43 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+GeckoProgram("minidump-analyzer", linkage=None)
+
+if CONFIG["OS_TARGET"] == "WINNT":
+ DEFINES["UNICODE"] = True
+ DEFINES["_UNICODE"] = True
+
+ if CONFIG["CPU_ARCH"] == "x86_64":
+ UNIFIED_SOURCES += [
+ "MozStackFrameSymbolizer.cpp",
+ "Win64ModuleUnwindMetadata.cpp",
+ ]
+
+ OS_LIBS += ["dbghelp", "imagehlp"]
+
+if CONFIG["OS_TARGET"] == "WINNT" and CONFIG["CC_TYPE"] in ("gcc", "clang"):
+ # This allows us to use wmain as the entry point on mingw
+ LDFLAGS += [
+ "-municode",
+ ]
+
+UNIFIED_SOURCES += [
+ "minidump-analyzer.cpp",
+]
+
+USE_LIBS += [
+ "breakpad_processor",
+ "jsoncpp",
+]
+
+LOCAL_INCLUDES += [
+ "/toolkit/components/jsoncpp/include",
+]
+
+if CONFIG["OS_TARGET"] != "WINNT":
+ DisableStlWrapping()
+
+include("/toolkit/crashreporter/crashreporter.mozbuild")