diff options
Diffstat (limited to '')
-rw-r--r-- | toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp | 604 |
1 files changed, 604 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 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); +} |