diff options
Diffstat (limited to 'toolkit/crashreporter/minidump-analyzer')
-rw-r--r-- | toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp | 437 | ||||
-rw-r--r-- | toolkit/crashreporter/minidump-analyzer/moz.build | 34 |
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') |