gecko-dev/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp

438 строки
11 KiB
C++

/* -*- 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);
}