summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/minidump-analyzer
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/crashreporter/minidump-analyzer')
-rw-r--r--toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp437
-rw-r--r--toolkit/crashreporter/minidump-analyzer/moz.build34
2 files changed, 471 insertions, 0 deletions
diff --git a/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp b/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp
new file mode 100644
index 000000000..b523826c9
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp
@@ -0,0 +1,437 @@
+/* -*- 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 <fstream>
+#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"
+
+#if defined(XP_WIN32)
+
+#include <windows.h>
+
+#elif defined(XP_UNIX) || defined(XP_MACOSX)
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#endif
+
+namespace CrashReporter {
+
+using std::ios;
+using std::ios_base;
+using std::hex;
+using std::ofstream;
+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;
+
+#ifdef XP_WIN
+
+static wstring UTF8ToWide(const string& aUtf8Str, bool *aSuccess = nullptr)
+{
+ wchar_t* buffer = nullptr;
+ int buffer_size = MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
+ -1, nullptr, 0);
+ if (buffer_size == 0) {
+ if (aSuccess) {
+ *aSuccess = false;
+ }
+
+ return L"";
+ }
+
+ buffer = new wchar_t[buffer_size];
+
+ if (buffer == nullptr) {
+ if (aSuccess) {
+ *aSuccess = false;
+ }
+
+ return L"";
+ }
+
+ MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
+ -1, buffer, buffer_size);
+ wstring str = buffer;
+ delete [] buffer;
+
+ if (aSuccess) {
+ *aSuccess = true;
+ }
+
+ return str;
+}
+
+#endif
+
+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 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();
+ unsigned int moduleIndex = 0;
+
+ for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
+ const StackFrame *frame = aStack->frames()->at(frameIndex);
+ Json::Value frameNode;
+
+ if (frame->module) {
+ auto itr = aOrderedModules.find(frame->module);
+
+ if (itr != aOrderedModules.end()) {
+ moduleIndex = (*itr).second;
+ frameNode["module_index"] = moduleIndex;
+ }
+ }
+
+ frameNode["trust"] = FrameTrust(frame->trust);
+ // The 'ip' field is equivalent to socorro's 'offset' field
+ frameNode["ip"] = ToHex(frame->instruction);
+
+ aNode.append(frameNode);
+ }
+}
+
+// Convert the list of modules to JSON and append them to the array specified
+// in the |aNode| parameter.
+
+static int
+ConvertModulesToJSON(const ProcessState& aProcessState,
+ OrderedModulesMap& aOrderedModules,
+ Json::Value& aNode)
+{
+ const CodeModules* modules = aProcessState.modules();
+
+ if (!modules) {
+ return -1;
+ }
+
+ // Create a sorted set of modules so that we'll be able to lookup the index
+ // of a particular module.
+ for (unsigned int i = 0; i < modules->module_count(); ++i) {
+ aOrderedModules.insert(
+ std::pair<const CodeModule*, unsigned int>(
+ modules->GetModuleAtSequence(i), i
+ )
+ );
+ }
+
+ uint64_t mainAddress = 0;
+ const CodeModule *mainModule = modules->GetMainModule();
+
+ if (mainModule) {
+ mainAddress = mainModule->base_address();
+ }
+
+ unsigned int moduleCount = modules->module_count();
+ int mainModuleIndex = -1;
+
+ for (unsigned int moduleSequence = 0;
+ moduleSequence < moduleCount;
+ ++moduleSequence)
+ {
+ const CodeModule *module = modules->GetModuleAtSequence(moduleSequence);
+
+ if (module->base_address() == mainAddress) {
+ mainModuleIndex = moduleSequence;
+ }
+
+ 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 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& aRoot)
+{
+ // We use this map to get the index of a module when listed by address
+ OrderedModulesMap orderedModules;
+
+ // 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) {
+ crashInfo["crashing_thread"] = requestingThread;
+ }
+ } else {
+ crashInfo["type"] = Json::Value(Json::nullValue);
+ // Add assertion info, if available
+ string assertion = aProcessState.assertion();
+
+ if (!assertion.empty()) {
+ crashInfo["assertion"] = assertion;
+ }
+ }
+
+ aRoot["crash_info"] = crashInfo;
+
+ // Modules
+ Json::Value modules(Json::arrayValue);
+ int mainModule = ConvertModulesToJSON(aProcessState, orderedModules, modules);
+
+ if (mainModule != -1) {
+ aRoot["main_module"] = mainModule;
+ }
+
+ aRoot["modules"] = modules;
+
+ // Threads
+ Json::Value threads(Json::arrayValue);
+ int threadCount = aProcessState.threads()->size();
+
+ 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);
+ }
+
+ aRoot["threads"] = threads;
+}
+
+// Process the minidump file and append the JSON-formatted stack traces to
+// the node specified in |aRoot|
+
+static bool
+ProcessMinidump(Json::Value& aRoot, const string& aDumpFile) {
+ BasicSourceLineResolver resolver;
+ // We don't have a valid symbol resolver so we pass nullptr instead.
+ MinidumpProcessor minidumpProcessor(nullptr, &resolver);
+
+ // Process the minidump.
+ Minidump dump(aDumpFile);
+ if (!dump.Read()) {
+ return false;
+ }
+
+ ProcessResult rv;
+ ProcessState processState;
+ rv = minidumpProcessor.Process(&dump, &processState);
+ aRoot["status"] = ResultString(rv);
+
+ ConvertProcessStateToJSON(processState, aRoot);
+
+ return true;
+}
+
+// Open the specified file in append mode
+
+static ofstream*
+OpenAppend(const string& aFilename)
+{
+ ios_base::openmode mode = ios::out | ios::app;
+
+#if defined(XP_WIN)
+#if defined(_MSC_VER)
+ ofstream* file = new ofstream();
+ file->open(UTF8ToWide(aFilename).c_str(), mode);
+#else // GCC
+ ofstream* file =
+ new ofstream(WideToMBCP(UTF8ToWide(aFilename), CP_ACP).c_str(), mode);
+#endif // _MSC_VER
+#else // Non-Windows
+ ofstream* file = new ofstream(aFilename.c_str(), mode);
+#endif // XP_WIN
+ return file;
+}
+
+// Check if a file exists at the specified path
+
+static bool
+FileExists(const string& aPath)
+{
+#if defined(XP_WIN)
+ DWORD attrs = GetFileAttributes(UTF8ToWide(aPath).c_str());
+ return (attrs != INVALID_FILE_ATTRIBUTES);
+#else // Non-Windows
+ struct stat sb;
+ int ret = stat(aPath.c_str(), &sb);
+ if (ret == -1 || !(sb.st_mode & S_IFREG)) {
+ return false;
+ }
+
+ return true;
+#endif // XP_WIN
+}
+
+// Update the extra data file by adding the StackTraces field holding the
+// JSON output of this program.
+
+static void
+UpdateExtraDataFile(const string &aDumpPath, const Json::Value& aRoot)
+{
+ string extraDataPath(aDumpPath);
+ int dot = extraDataPath.rfind('.');
+
+ if (dot < 0) {
+ return; // Not a valid dump path
+ }
+
+ extraDataPath.replace(dot, extraDataPath.length() - dot, kExtraDataExtension);
+ ofstream* f = OpenAppend(extraDataPath.c_str());
+
+ if (f->is_open()) {
+ Json::FastWriter writer;
+
+ *f << "StackTraces=" << writer.write(aRoot);
+
+ f->close();
+ }
+
+ delete f;
+}
+
+} // namespace CrashReporter
+
+using namespace CrashReporter;
+
+int main(int argc, char** argv)
+{
+ string dumpPath;
+
+ if (argc > 1) {
+ dumpPath = argv[1];
+ }
+
+ if (dumpPath.empty()) {
+ exit(EXIT_FAILURE);
+ }
+
+ if (!FileExists(dumpPath)) {
+ // The dump file does not exist
+ return 1;
+ }
+
+ // Try processing the minidump
+ Json::Value root;
+ if (ProcessMinidump(root, dumpPath)) {
+ UpdateExtraDataFile(dumpPath, root);
+ }
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/toolkit/crashreporter/minidump-analyzer/moz.build b/toolkit/crashreporter/minidump-analyzer/moz.build
new file mode 100644
index 000000000..bd4aa762f
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/moz.build
@@ -0,0 +1,34 @@
+# -*- 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/.
+
+if CONFIG['OS_TARGET'] != 'Android':
+ Program('minidump-analyzer')
+
+ DEFINES['UNICODE'] = True
+ DEFINES['_UNICODE'] = True
+
+ UNIFIED_SOURCES += [
+ 'minidump-analyzer.cpp',
+ ]
+
+ USE_LIBS += [
+ 'breakpad_processor',
+ 'jsoncpp',
+ ]
+
+ LOCAL_INCLUDES += [
+ '/toolkit/crashreporter/jsoncpp/include',
+ ]
+
+
+ if CONFIG['OS_TARGET'] == 'Darwin':
+ DIST_SUBDIR = 'crashreporter.app/Contents/MacOS'
+
+# Don't use the STL wrappers in the crashreporter clients; they don't
+# link with -lmozalloc, and it really doesn't matter here anyway.
+DISABLE_STL_WRAPPING = True
+
+include('/toolkit/crashreporter/crashreporter.mozbuild')