Bug 1280477 - Write the stack traces extracted from a crash dump into the crash.main event file r=ted

This commit is contained in:
Gabriele Svelto 2016-06-28 15:30:29 +02:00
parent da2f825015
commit 54fdfadaf7
9 changed files with 624 additions and 5 deletions

View File

@ -492,6 +492,17 @@ bool CheckEndOfLifed(string version)
return UIFileExists(reportPath);
}
static string
GetMinidumpAnalyzerPath()
{
string path = gArgv[0];
size_t pos = path.rfind(UI_CRASH_REPORTER_FILENAME BIN_SUFFIX);
path.erase(pos);
path.append(UI_MINIDUMP_ANALYZER_FILENAME BIN_SUFFIX);
return path;
}
int main(int argc, char** argv)
{
gArgc = argc;
@ -513,6 +524,10 @@ int main(int argc, char** argv)
// no dump file specified, run the default UI
UIShowDefaultUI();
} else {
// start by running minidump analyzer
UIRunMinidumpAnalyzer(GetMinidumpAnalyzerPath(), gReporterDumpFile);
// go ahead with the crash reporter
gExtraFile = GetAdditionalFilename(gReporterDumpFile, kExtraDataExtension);
if (gExtraFile.empty()) {
UIError(gStrings[ST_ERROR_BADARGUMENTS]);

View File

@ -37,6 +37,9 @@ std::string WideToUTF8(const std::wstring& wide, bool* success = 0);
#endif
#define UI_CRASH_REPORTER_FILENAME "crashreporter"
#define UI_MINIDUMP_ANALYZER_FILENAME "minidump-analyzer"
typedef std::map<std::string, std::string> StringTable;
#define ST_CRASHREPORTERTITLE "CrashReporterTitle"
@ -145,6 +148,8 @@ std::ofstream* UIOpenWrite(const std::string& filename,
bool append=false,
bool binary=false);
void UIPruneSavedDumps(const std::string& directory);
void UIRunMinidumpAnalyzer(const std::string& exename,
const std::string& filename);
#ifdef _MSC_VER
# pragma warning( pop )

View File

@ -4,11 +4,13 @@
#include "crashreporter.h"
#include <dirent.h>
#include <sys/stat.h>
#include <errno.h>
#include <algorithm>
#include <dirent.h>
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
using namespace CrashReporter;
using std::string;
using std::vector;
@ -67,3 +69,17 @@ void UIPruneSavedDumps(const std::string& directory)
dumpfiles.pop_back();
}
}
void UIRunMinidumpAnalyzer(const string& exename, const string& filename)
{
// Run the minidump analyzer and wait for it to finish
pid_t pid = fork();
if (pid == -1) {
return; // Nothing to do upon failure
} else if (pid == 0) {
execl(exename.c_str(), exename.c_str(), filename.c_str(), nullptr);
} else {
waitpid(pid, nullptr, 0);
}
}

View File

@ -1544,3 +1544,25 @@ void UIPruneSavedDumps(const std::string& directory)
dumpfiles.pop_back();
}
}
void UIRunMinidumpAnalyzer(const string& exename, const string& filename)
{
wstring cmdLine;
cmdLine += L"\"" + UTF8ToWide(exename) + L"\" ";
cmdLine += L"\"" + UTF8ToWide(filename) + L"\" ";
STARTUPINFO si = {};
PROCESS_INFORMATION pi = {};
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOWNORMAL;
if (CreateProcess(nullptr, (LPWSTR)cmdLine.c_str(), nullptr, nullptr, FALSE,
0, nullptr, nullptr, &si, &pi)) {
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
}

View File

@ -67,6 +67,8 @@ if CONFIG['OS_ARCH'] == 'Linux' or CONFIG['OS_ARCH'] == 'SunOS':
'/toolkit/themes/windows/global/throbber/Throbber-small.gif',
]
DEFINES['BIN_SUFFIX'] = '"%s"' % CONFIG['BIN_SUFFIX']
RCINCLUDE = 'crashreporter.rc'
# Don't use the STL wrappers in the crashreporter clients; they don't

View File

@ -32,6 +32,11 @@ Crash Reporter Client
to handle dump files. This application optionally submits crashes to
Mozilla (or the configured server).
Minidump Analyzer
The minidump analyzer is a standalone executable that is launched by the
crash reporter client or by the browser itself to extract stack traces from
the dump files generated during a crash.
How Main-Process Crash Handling Works
=====================================
@ -83,8 +88,11 @@ argument.
The *crash reporter client* performs a number of roles. There's a lot going
on, so you may want to look at ``main()`` in ``crashreporter.cpp``. First,
it verifies the dump data is sane. If it isn't (e.g. required metadata is
missing), the dump data is ignored. If dump data looks sane, the dump data
stack traces are extracted from the dump via the *minidump analyzer* tool.
The resulting traces are appended to the main crash event file. Then, the
*crash reporter client* verifies that the dump data is sane. If it isn't
(e.g. required metadata is missing), the dump data is ignored. If dump data
looks sane, the dump data
is moved into the *pending* directory for the configured data directory
(defined via the ``MOZ_CRASHREPORTER_DATA_DIRECTORY`` environment variable
or from the UI). Once this is done, the main crash reporter UI is displayed

View File

@ -0,0 +1,516 @@
/* -*- 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>
#define DIR_SEPARATOR "\\"
#elif defined(XP_UNIX) || defined(XP_MACOSX)
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#define DIR_SEPARATOR "/"
#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 string
WideToUTF8(const wstring& aWideStr, bool* aSuccess = nullptr)
{
char* buffer = nullptr;
int buffer_size = WideCharToMultiByte(CP_UTF8, 0, aWideStr.c_str(),
-1, nullptr, 0, nullptr, nullptr);
if (buffer_size == 0) {
if (aSuccess) {
*aSuccess = false;
}
return "";
}
buffer = new char[buffer_size];
if (buffer == nullptr) {
if (aSuccess) {
*aSuccess = false;
}
return "";
}
WideCharToMultiByte(CP_UTF8, 0, aWideStr.c_str(),
-1, buffer, buffer_size, nullptr, nullptr);
string mb = buffer;
delete [] buffer;
if (aSuccess) {
*aSuccess = true;
}
return mb;
}
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
static string gReporterDumpFile;
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 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) {
BasicSourceLineResolver resolver;
// We don't have a valid symbol resolver so we pass nullptr instead.
MinidumpProcessor minidumpProcessor(nullptr, &resolver);
// Process the minidump.
Minidump dump(gReporterDumpFile);
if (!dump.Read()) {
return false;
}
ProcessResult rv;
ProcessState processState;
rv = minidumpProcessor.Process(&dump, &processState);
aRoot["status"] = ResultString(rv);
ConvertProcessStateToJSON(processState, aRoot);
return true;
}
static string
Basename(const string& file)
{
string::size_type slashIndex = file.rfind(DIR_SEPARATOR);
if (slashIndex != string::npos)
return file.substr(slashIndex + 1);
else
return file;
}
// Return the ID of the crash report being processed
static string
GetDumpLocalID()
{
string localId = Basename(gReporterDumpFile);
string::size_type dot = localId.rfind('.');
if (dot == string::npos)
return "";
return localId.substr(0, dot);
}
// 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 crash event file by adding the StackTraces field holding the
// JSON output of this program.
static void
UpdateCrashEvent(string& aEventsPath, const Json::Value& aRoot)
{
if (aEventsPath.empty()) {
// If there is no path to find the crash event in, skip it.
return;
}
string localId = GetDumpLocalID();
string fpath = aEventsPath + DIR_SEPARATOR + localId;
ofstream* f = OpenAppend(fpath.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)
{
if (argc > 1) {
gReporterDumpFile = argv[1];
}
if (gReporterDumpFile.empty()) {
exit(EXIT_FAILURE);
}
string eventsPath;
#ifdef XP_WIN32
static const wchar_t kEventsDirKey[] = L"MOZ_CRASHREPORTER_EVENTS_DIRECTORY";
const wchar_t *eventsPathEnv = _wgetenv(kEventsDirKey);
if (eventsPathEnv && *eventsPathEnv) {
eventsPath = WideToUTF8(eventsPathEnv);
}
#else
static const char kEventsDirKey[] = "MOZ_CRASHREPORTER_EVENTS_DIRECTORY";
const char *eventsPathEnv = getenv(kEventsDirKey);
if (eventsPathEnv && *eventsPathEnv) {
eventsPath = eventsPathEnv;
}
#endif
else {
eventsPath.clear();
}
if (!FileExists(gReporterDumpFile)) {
// The dump file does not exist
return 1;
}
// Try processing the minidump
Json::Value root;
if (ProcessMinidump(root)) {
UpdateCrashEvent(eventsPath, root);
}
exit(EXIT_SUCCESS);
}

View File

@ -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')

View File

@ -56,6 +56,7 @@ DIRS += [
if CONFIG['NIGHTLY_BUILD']:
DIRS += [
'jsoncpp/src/lib_json',
'minidump-analyzer',
]
if CONFIG['MOZ_CRASHREPORTER_INJECTOR']: