Bug 1293656 - Send crash pings for content crashes complete with stack traces r=bsmedberg

* * *
Bug 1293656 - Fix the test_process_error.xul test
This commit is contained in:
Gabriele Svelto 2016-10-19 12:51:29 +02:00
parent d55bb9094c
commit 1b8ffd2807
12 changed files with 442 additions and 100 deletions

View File

@ -38,14 +38,18 @@
ok(dumpID, "dumpID is present and not an empty string");
}
let p = Promise.resolve();
if (dumpID) {
var minidumpDirectory = getMinidumpDirectory();
removeFile(minidumpDirectory, dumpID + '.dmp');
removeFile(minidumpDirectory, dumpID + '.extra');
p = Services.crashmanager.ensureCrashIsPresent(dumpID).then(() => {
removeFile(minidumpDirectory, dumpID + '.dmp');
removeFile(minidumpDirectory, dumpID + '.extra');
});
}
Services.obs.removeObserver(crashObserver, 'ipc:content-shutdown');
done();
p.then(done);
}
Services.obs.addObserver(crashObserver, 'ipc:content-shutdown', false);

View File

@ -6,10 +6,12 @@
#include "CrashReporterHost.h"
#include "CrashReporterMetadataShmem.h"
#include "mozilla/LazyIdleThread.h"
#include "mozilla/Sprintf.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/Telemetry.h"
#ifdef MOZ_CRASHREPORTER
# include "nsIAsyncShutdown.h"
# include "nsICrashService.h"
#endif
@ -63,6 +65,147 @@ CrashReporterHost::GenerateCrashReport(RefPtr<nsIFile> aCrashDump)
NotifyCrashService(mProcessType, dumpID, &notes);
}
/**
* Runnable used to execute the minidump analyzer program asynchronously after
* a crash. This should run on a background thread not to block the main thread
* over the potentially long minidump analysis. Once analysis is over, the
* crash information will be relayed to the crash manager via another runnable
* sent to the main thread. Shutdown will be blocked for the duration of the
* entire process to ensure this information is sent.
*/
class AsyncMinidumpAnalyzer final : public nsIRunnable
, public nsIAsyncShutdownBlocker
{
public:
/**
* Create a new minidump analyzer runnable, this will also block shutdown
* until the associated crash has been added to the crash manager.
*/
AsyncMinidumpAnalyzer(int32_t aProcessType,
int32_t aCrashType,
const nsString& aChildDumpID)
: mProcessType(aProcessType)
, mCrashType(aCrashType)
, mChildDumpID(aChildDumpID)
, mName(NS_LITERAL_STRING("Crash reporter: blocking on minidump analysis"))
{
MOZ_ASSERT(NS_IsMainThread());
nsresult rv = GetShutdownBarrier()->AddBlocker(
this, NS_LITERAL_STRING(__FILE__), __LINE__,
NS_LITERAL_STRING("Minidump analysis"));
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
}
NS_IMETHOD Run() override
{
MOZ_ASSERT(!NS_IsMainThread());
if (mProcessType == nsICrashService::PROCESS_TYPE_CONTENT) {
CrashReporter::RunMinidumpAnalyzer(mChildDumpID);
}
// Make a copy of these so we can copy them into the runnable lambda
int32_t processType = mProcessType;
int32_t crashType = mCrashType;
nsString childDumpID(mChildDumpID);
NS_DispatchToMainThread(NS_NewRunnableFunction([=] () -> void {
nsCOMPtr<nsICrashService> crashService =
do_GetService("@mozilla.org/crashservice;1");
if (crashService) {
crashService->AddCrash(processType, crashType, childDumpID);
}
nsCOMPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier();
if (barrier) {
barrier->RemoveBlocker(this);
}
}));
return NS_OK;
}
NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient* aBarrierClient) override
{
return NS_OK;
}
NS_IMETHOD GetName(nsAString& aName) override
{
aName = mName;
return NS_OK;
}
NS_IMETHOD GetState(nsIPropertyBag**) override
{
return NS_OK;
}
NS_DECL_THREADSAFE_ISUPPORTS
private:
~AsyncMinidumpAnalyzer() {}
// Returns the profile-before-change shutdown barrier
static nsCOMPtr<nsIAsyncShutdownClient> GetShutdownBarrier()
{
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdown();
nsCOMPtr<nsIAsyncShutdownClient> barrier;
nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(barrier));
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
return barrier.forget();
}
int32_t mProcessType;
int32_t mCrashType;
const nsString mChildDumpID;
const nsString mName;
};
NS_IMPL_ISUPPORTS(AsyncMinidumpAnalyzer, nsIRunnable, nsIAsyncShutdownBlocker)
/**
* Add information about a crash to the crash manager. This method runs the
* various activities required to gather additional information and notify the
* crash manager asynchronously, since many of them involve I/O or potentially
* long processing.
*
* @param aProcessType The type of process that crashed
* @param aCrashType The type of crash (crash or hang)
* @param aChildDumpID A string holding the ID of the dump associated with this
* crash
*/
/* static */ void
CrashReporterHost::AsyncAddCrash(int32_t aProcessType,
int32_t aCrashType,
const nsString& aChildDumpID)
{
MOZ_ASSERT(NS_IsMainThread());
static StaticRefPtr<LazyIdleThread> sBackgroundThread;
if (!sBackgroundThread) {
// Background thread used for running minidump analysis. It will be
// destroyed immediately after it's done with the task.
sBackgroundThread =
new LazyIdleThread(0, NS_LITERAL_CSTRING("CrashReporterHost"));
ClearOnShutdown(&sBackgroundThread);
}
RefPtr<AsyncMinidumpAnalyzer> task =
new AsyncMinidumpAnalyzer(aProcessType, aCrashType, aChildDumpID);
Unused << sBackgroundThread->Dispatch(task, NS_DISPATCH_NORMAL);
}
/* static */ void
CrashReporterHost::NotifyCrashService(GeckoProcessType aProcessType,
const nsString& aChildDumpID,
@ -79,12 +222,6 @@ CrashReporterHost::NotifyCrashService(GeckoProcessType aProcessType,
MOZ_ASSERT(!aChildDumpID.IsEmpty());
nsCOMPtr<nsICrashService> crashService =
do_GetService("@mozilla.org/crashservice;1");
if (!crashService) {
return;
}
int32_t processType;
int32_t crashType = nsICrashService::CRASH_TYPE_CRASH;
@ -119,7 +256,7 @@ CrashReporterHost::NotifyCrashService(GeckoProcessType aProcessType,
return;
}
crashService->AddCrash(processType, crashType, aChildDumpID);
AsyncAddCrash(processType, crashType, aChildDumpID);
Telemetry::Accumulate(Telemetry::SUBPROCESS_CRASHES_WITH_DUMP, telemetryKey, 1);
}
#endif

View File

@ -50,6 +50,8 @@ public:
private:
void GenerateCrashReport(RefPtr<nsIFile> aCrashDump);
static void AsyncAddCrash(int32_t aProcessType, int32_t aCrashType,
const nsString& aChildDumpID);
private:
GeckoProcessType mProcessType;

View File

@ -924,28 +924,36 @@ this.BrowserTestUtils = {
}
}
if (dumpID) {
let minidumpDirectory = getMinidumpDirectory();
let extrafile = minidumpDirectory.clone();
extrafile.append(dumpID + '.extra');
if (extrafile.exists()) {
dump(`\nNo .extra file for dumpID: ${dumpID}\n`);
if (AppConstants.MOZ_CRASHREPORTER) {
extra = KeyValueParser.parseKeyValuePairsFromFile(extrafile);
} else {
dump('\nCrashReporter not enabled - will not return any extra data\n');
}
}
let removalPromise = Promise.resolve();
removeFile(minidumpDirectory, dumpID + '.dmp');
removeFile(minidumpDirectory, dumpID + '.extra');
if (dumpID) {
removalPromise = Services.crashmanager.ensureCrashIsPresent(dumpID)
.then(() => {
let minidumpDirectory = getMinidumpDirectory();
let extrafile = minidumpDirectory.clone();
extrafile.append(dumpID + '.extra');
if (extrafile.exists()) {
dump(`\nNo .extra file for dumpID: ${dumpID}\n`);
if (AppConstants.MOZ_CRASHREPORTER) {
extra = KeyValueParser.parseKeyValuePairsFromFile(extrafile);
} else {
dump('\nCrashReporter not enabled - will not return any extra data\n');
}
}
removeFile(minidumpDirectory, dumpID + '.dmp');
removeFile(minidumpDirectory, dumpID + '.extra');
});
}
Services.obs.removeObserver(observer, 'ipc:content-shutdown');
dump("\nCrash cleaned up\n");
// There might be other ipc:content-shutdown handlers that need to run before
// we want to continue, so we'll resolve on the next tick of the event loop.
TestUtils.executeSoon(() => resolve());
removalPromise.then(() => {
Services.obs.removeObserver(observer, 'ipc:content-shutdown');
dump("\nCrash cleaned up\n");
// There might be other ipc:content-shutdown handlers that need to
// run before we want to continue, so we'll resolve on the next tick
// of the event loop.
TestUtils.executeSoon(() => resolve());
});
};
Services.obs.addObserver(observer, 'ipc:content-shutdown', false);

View File

@ -144,6 +144,9 @@ this.CrashManager = function(options) {
// Map of crash ID / promise tuples used to track adding new crashes.
this._crashPromises = new Map();
// Promise for the crash ping used only for testing.
this._pingPromise = null;
// The CrashStore currently attached to this object.
this._store = null;
@ -449,7 +452,12 @@ this.CrashManager.prototype = Object.freeze({
this._crashPromises.delete(id);
deferred.resolve();
}
}.bind(this));
// Send a telemetry ping for each content process crash
if (processType === this.PROCESS_TYPE_CONTENT) {
this._sendCrashPing(id, processType, date, metadata);
}
}.bind(this));
return promise;
},
@ -609,7 +617,7 @@ this.CrashManager.prototype = Object.freeze({
}.bind(this));
},
_filterAnnotations: function (annotations) {
_filterAnnotations: function(annotations) {
let filteredAnnotations = {};
for (let line in annotations) {
@ -621,7 +629,7 @@ this.CrashManager.prototype = Object.freeze({
return filteredAnnotations;
},
_sendCrashPing: function (crashId, type, date, metadata) {
_sendCrashPing: function(crashId, type, date, metadata = {}) {
// If we have a saved environment, use it. Otherwise report
// the current environment.
let reportMeta = Cu.cloneInto(metadata, myScope);
@ -634,7 +642,7 @@ this.CrashManager.prototype = Object.freeze({
// Filter the remaining annotations to remove privacy-sensitive ones
reportMeta = this._filterAnnotations(reportMeta);
TelemetryController.submitExternalPing("crash",
this._pingPromise = TelemetryController.submitExternalPing("crash",
{
version: 1,
crashDate: date.toISOString().slice(0, 10), // YYYY-MM-DD

View File

@ -6,9 +6,43 @@
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/AsyncShutdown.jsm", this);
Cu.import("resource://gre/modules/KeyValueParser.jsm");
Cu.import("resource://gre/modules/osfile.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
/**
* Process the .extra file associated with the crash id and return the
* annotations it contains in an object.
*
* @param crashID {string} Crash ID. Likely a UUID.
*
* @return {Promise} A promise that resolves to an object holding the crash
* annotations, this object may be empty if no annotations were found.
*/
function processExtraFile(id) {
let cr = Cc["@mozilla.org/toolkit/crash-reporter;1"]
.getService(Components.interfaces.nsICrashReporter);
let extraPath = OS.Path.join(cr.minidumpPath.path, id + ".extra");
return Task.spawn(function* () {
try {
let decoder = new TextDecoder();
let extraFile = yield OS.File.read(extraPath);
let extraData = decoder.decode(extraFile);
return parseKeyValuePairs(extraData);
} catch (e) {
Cu.reportError(e);
}
return {};
});
}
/**
* This component makes crash data available throughout the application.
*
@ -55,7 +89,13 @@ CrashService.prototype = Object.freeze({
throw new Error("Unrecognized CRASH_TYPE: " + crashType);
}
Services.crashmanager.addCrash(processType, crashType, id, new Date());
AsyncShutdown.profileBeforeChange.addBlocker(
"CrashService waiting for content crash ping to be sent",
processExtraFile(id).then(metadata => {
return Services.crashmanager.addCrash(processType, crashType, id,
new Date(), metadata)
})
);
},
observe: function(subject, topic, data) {

View File

@ -212,13 +212,13 @@ add_task(function* test_schedule_maintenance() {
const crashId = "3cb67eba-0dc7-6f78-6a569a0e-172287ec";
const productName = "Firefox";
const productId = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
const stackTraces = "{\"status\":\"OK\"}";
add_task(function* test_main_crash_event_file() {
let ac = new TelemetryArchiveTesting.Checker();
yield ac.promiseInit();
let theEnvironment = TelemetryEnvironment.currentEnvironment;
const sessionId = "be66af2f-2ee5-4330-ae95-44462dfbdf0c";
let stackTraces = { status: "OK" };
// To test proper escaping, add data to the environment with an embedded
// double-quote
@ -230,7 +230,7 @@ add_task(function* test_main_crash_event_file() {
"ProductID=" + productId + "\n" +
"TelemetryEnvironment=" + JSON.stringify(theEnvironment) + "\n" +
"TelemetrySessionId=" + sessionId + "\n" +
"StackTraces=" + JSON.stringify(stackTraces) + "\n" +
"StackTraces=" + stackTraces + "\n" +
"ThisShouldNot=end-up-in-the-ping\n";
yield m.createEventsFile(crashId, "crash.main.2", DUMMY_DATE, fileContent);
@ -458,6 +458,28 @@ add_task(function* test_addCrash() {
Assert.ok(crash.isOfType(m.PROCESS_TYPE_CONTENT, m.CRASH_TYPE_HANG));
});
add_task(function* test_content_crash_ping() {
let ac = new TelemetryArchiveTesting.Checker();
yield ac.promiseInit();
let m = yield getManager();
let id = yield m.createDummyDump();
yield m.addCrash(m.PROCESS_TYPE_CONTENT, m.CRASH_TYPE_CRASH, id, DUMMY_DATE, {
StackTraces: stackTraces,
ThisShouldNot: "end-up-in-the-ping"
});
yield m._pingPromise;
let found = yield ac.promiseFindPing("crash", [
[["payload", "crashId"], id],
[["payload", "processType"], m.PROCESS_TYPE_CONTENT],
[["payload", "stackTraces", "status"], "OK"],
]);
Assert.ok(found, "Telemetry ping submitted for content crash");
Assert.equal(found.payload.metadata.ThisShouldNot, undefined,
"Non-whitelisted fields should be filtered out");
});
add_task(function* test_generateSubmissionID() {
let m = yield getManager();

View File

@ -1,29 +1,37 @@
"use strict";
/**
* Cleans up the .dmp and .extra file from a crash.
* Returns the id of the crash minidump.
*
* @param subject (nsISupports)
* The subject passed through the ipc:content-shutdown
* observer notification when a content process crash has
* occurred.
* @returns {String} The crash dump id.
*/
function cleanUpMinidump(subject) {
function getCrashDumpId(subject) {
Assert.ok(subject instanceof Ci.nsIPropertyBag2,
"Subject needs to be a nsIPropertyBag2 to clean up properly");
let dumpID = subject.getPropertyAsAString("dumpID");
Assert.ok(dumpID, "There should be a dumpID");
if (dumpID) {
return subject.getPropertyAsAString("dumpID");
}
/**
* Cleans up the .dmp and .extra file from a crash.
*
* @param id {String} The crash dump id.
*/
function cleanUpMinidump(id) {
if (id) {
let dir = Services.dirsvc.get("ProfD", Ci.nsIFile);
dir.append("minidumps");
let file = dir.clone();
file.append(dumpID + ".dmp");
file.append(id + ".dmp");
file.remove(true);
file = dir.clone();
file.append(dumpID + ".extra");
file.append(id + ".extra");
file.remove(true);
}
}
@ -104,7 +112,13 @@ add_task(function* test_crash_in_previous_frameloader() {
// If we don't clean up the minidump, the harness will
// complain.
cleanUpMinidump(subject);
let dumpID = getCrashDumpId(subject);
Assert.ok(dumpID, "There should be a dumpID");
if (dumpID) {
yield Services.crashmanager.ensureCrashIsPresent(dumpID);
cleanUpMinidump(dumpID);
}
info("Content process is gone!");
Assert.ok(!sawTabCrashed,

View File

@ -130,6 +130,15 @@ appropriate crash annotations specific to the crash. All child-process
crashes are annotated with a ``ProcessType`` annotation, such as "content" or
"plugin".
Once the minidump file has been generated the
``mozilla::dom::CrashReporterHost`` is notified of the crash. It will first
try to extract the stack traces from the minidump file using the
*minidump analyzer*. Then the stack traces will be stored in the extra file
together with the rest of the crash annotations and finally the crash will be
recorded by calling ```CrashService.addCrash()```. This last step adds the
crash to the ```CrashManager``` database and automatically sends a crash ping
with information about the crash.
Submission of child process crashes is handled by application code. This
code prompts the user to submit crashes in context-appropriate UI and then
submits the crashes using ``CrashSubmit.jsm``.

View File

@ -8,6 +8,7 @@
#include "nsAppDirectoryServiceDefs.h"
#include "nsDirectoryServiceDefs.h"
#include "nsDirectoryService.h"
#include "nsDataHashtable.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/dom/CrashReporterChild.h"
@ -129,6 +130,7 @@ typedef std::wstring xpstring;
#define XP_STRLEN(x) wcslen(x)
#define my_strlen strlen
#define CRASH_REPORTER_FILENAME "crashreporter.exe"
#define MINIDUMP_ANALYZER_FILENAME "minidump-analyzer.exe"
#define PATH_SEPARATOR "\\"
#define XP_PATH_SEPARATOR L"\\"
#define XP_PATH_SEPARATOR_CHAR L'\\'
@ -147,6 +149,7 @@ typedef std::string xpstring;
#define XP_TEXT(x) x
#define CONVERT_XP_CHAR_TO_UTF16(x) NS_ConvertUTF8toUTF16(x)
#define CRASH_REPORTER_FILENAME "crashreporter"
#define MINIDUMP_ANALYZER_FILENAME "minidump-analyzer"
#define PATH_SEPARATOR "/"
#define XP_PATH_SEPARATOR "/"
#define XP_PATH_SEPARATOR_CHAR '/'
@ -190,6 +193,9 @@ static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr;
static XP_CHAR* pendingDirectory;
static XP_CHAR* crashReporterPath;
static XP_CHAR* memoryReportPath;
#if !defined(MOZ_WIDGET_ANDROID)
static XP_CHAR* minidumpAnalyzerPath;
#endif // !defined(MOZ_WIDGET_ANDROID)
// Where crash events should go.
static XP_CHAR* eventsDirectory;
@ -808,9 +814,11 @@ WriteGlobalMemoryStatus(PlatformWriter* apiData, PlatformWriter* eventFile)
* @param aProgramPath The path of the program to be launched
* @param aMinidumpPath The path of the minidump file, passed as an argument
* to the launched program
* @param aWait If true wait for the program termination
*/
static bool
LaunchProgram(XP_CHAR* aProgramPath, XP_CHAR* aMinidumpPath)
LaunchProgram(const XP_CHAR* aProgramPath, const XP_CHAR* aMinidumpPath,
bool aWait = false)
{
#ifdef XP_WIN
XP_CHAR cmdLine[CMDLINE_SIZE];
@ -823,16 +831,18 @@ LaunchProgram(XP_CHAR* aProgramPath, XP_CHAR* aMinidumpPath)
p = Concat(p, aMinidumpPath, &size);
Concat(p, L"\"", &size);
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
PROCESS_INFORMATION pi = {};
STARTUPINFO si = {};
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
// If CreateProcess() fails don't do anything
if (CreateProcess(nullptr, (LPWSTR)cmdLine, nullptr, nullptr, FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW,
nullptr, nullptr, &si, &pi)) {
if (aWait) {
WaitForSingleObject(pi.hProcess, INFINITE);
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
@ -840,8 +850,8 @@ LaunchProgram(XP_CHAR* aProgramPath, XP_CHAR* aMinidumpPath)
#ifdef XP_MACOSX
pid_t pid = 0;
char* const my_argv[] = {
aProgramPath,
aMinidumpPath,
const_cast<char*>(aProgramPath),
const_cast<char*>(aMinidumpPath),
nullptr
};
@ -854,6 +864,8 @@ LaunchProgram(XP_CHAR* aProgramPath, XP_CHAR* aMinidumpPath)
if (rv != 0) {
return false;
} else if (aWait) {
waitpid(pid, nullptr, 0);
}
#else // !XP_MACOSX
@ -868,6 +880,10 @@ LaunchProgram(XP_CHAR* aProgramPath, XP_CHAR* aMinidumpPath)
Unused << execl(aProgramPath,
aProgramPath, aMinidumpPath, (char*)0);
_exit(1);
} else {
if (aWait) {
sys_waitpid(pid, nullptr, __WALL);
}
}
#endif // XP_MACOSX
#endif // XP_UNIX
@ -1536,6 +1552,32 @@ ChildFilter(void* context)
return result;
}
#if !defined(MOZ_WIDGET_ANDROID)
// Locate the specified executable and store its path as a native string in
// the |aPathPtr| so we can later invoke it from within the exception handler.
static nsresult
LocateExecutable(nsIFile* aXREDirectory, const nsACString& aName,
nsAString& aPath)
{
nsCOMPtr<nsIFile> exePath;
nsresult rv = aXREDirectory->Clone(getter_AddRefs(exePath));
NS_ENSURE_SUCCESS(rv, rv);
#ifdef XP_MACOSX
exePath->SetNativeLeafName(NS_LITERAL_CSTRING("MacOS"));
exePath->Append(NS_LITERAL_STRING("crashreporter.app"));
exePath->Append(NS_LITERAL_STRING("Contents"));
exePath->Append(NS_LITERAL_STRING("MacOS"));
#endif
exePath->AppendNative(aName);
exePath->GetPath(aPath);
return NS_OK;
}
#endif // !defined(MOZ_WIDGET_ANDROID)
nsresult SetExceptionHandler(nsIFile* aXREDirectory,
bool force/*=false*/)
{
@ -1586,30 +1628,33 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory,
NS_ENSURE_TRUE(notesField, NS_ERROR_OUT_OF_MEMORY);
if (!headlessClient) {
// locate crashreporter executable
nsCOMPtr<nsIFile> exePath;
nsresult rv = aXREDirectory->Clone(getter_AddRefs(exePath));
NS_ENSURE_SUCCESS(rv, rv);
#if !defined(MOZ_WIDGET_ANDROID)
// Locate the crash reporter executable
nsAutoString crashReporterPath_temp;
nsresult rv = LocateExecutable(aXREDirectory,
NS_LITERAL_CSTRING(CRASH_REPORTER_FILENAME),
crashReporterPath_temp);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
#if defined(XP_MACOSX)
exePath->SetNativeLeafName(NS_LITERAL_CSTRING("MacOS"));
exePath->Append(NS_LITERAL_STRING("crashreporter.app"));
exePath->Append(NS_LITERAL_STRING("Contents"));
exePath->Append(NS_LITERAL_STRING("MacOS"));
#endif
exePath->AppendNative(NS_LITERAL_CSTRING(CRASH_REPORTER_FILENAME));
nsAutoString minidumpAnalyzerPath_temp;
rv = LocateExecutable(aXREDirectory,
NS_LITERAL_CSTRING(MINIDUMP_ANALYZER_FILENAME),
minidumpAnalyzerPath_temp);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
#ifdef XP_WIN32
nsString crashReporterPath_temp;
exePath->GetPath(crashReporterPath_temp);
crashReporterPath = reinterpret_cast<wchar_t*>(ToNewUnicode(crashReporterPath_temp));
#elif !defined(__ANDROID__)
nsCString crashReporterPath_temp;
exePath->GetNativePath(crashReporterPath_temp);
crashReporterPath = ToNewCString(crashReporterPath_temp);
crashReporterPath =
reinterpret_cast<wchar_t*>(ToNewUnicode(crashReporterPath_temp));
minidumpAnalyzerPath =
reinterpret_cast<wchar_t*>(ToNewUnicode(minidumpAnalyzerPath_temp));
#else
crashReporterPath = ToNewCString(crashReporterPath_temp);
minidumpAnalyzerPath = ToNewCString(minidumpAnalyzerPath_temp);
#endif // XP_WIN32
#else
// On Android, we launch using the application package name instead of a
// filename, so use the dynamically set MOZ_ANDROID_PACKAGE_NAME, or fall
@ -1623,7 +1668,7 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory,
nsCString package(ANDROID_PACKAGE_NAME "/org.mozilla.gecko.CrashReporter");
crashReporterPath = ToNewCString(package);
}
#endif
#endif // !defined(MOZ_WIDGET_ANDROID)
}
// get temp path to use for minidump path
@ -3033,6 +3078,34 @@ AppendExtraData(const nsAString& id, const AnnotationTable& data)
return AppendExtraData(extraFile, data);
}
/**
* Runs the minidump analyzer program on the specified crash dump. The analyzer
* will extract the stack traces from the dump and store them in JSON format as
* an annotation in the extra file associated with the crash.
*
* This method waits synchronously for the program to have finished executing,
* do not call it from the main thread!
*/
void
RunMinidumpAnalyzer(const nsAString& id)
{
#if !defined(MOZ_WIDGET_ANDROID)
nsCOMPtr<nsIFile> file;
if (CrashReporter::GetMinidumpForID(id, getter_AddRefs(file)) && file) {
#ifdef XP_WIN
nsAutoString path;
file->GetPath(path);
#else
nsAutoCString path;
file->GetNativePath(path);
#endif
LaunchProgram(minidumpAnalyzerPath, path.get(), /* aWait */ true);
}
#endif // !defined(MOZ_WIDGET_ANDROID)
}
//-----------------------------------------------------------------------------
// Helpers for AppendExtraData()
//

View File

@ -103,6 +103,7 @@ bool GetExtraFileForID(const nsAString& id, nsIFile** extraFile);
bool GetExtraFileForMinidump(nsIFile* minidump, nsIFile** extraFile);
bool AppendExtraData(const nsAString& id, const AnnotationTable& data);
bool AppendExtraData(nsIFile* extraFile, const AnnotationTable& data);
void RunMinidumpAnalyzer(const nsAString& id);
/*
* Renames the stand alone dump file aDumpFile to:

View File

@ -1,4 +1,8 @@
Components.utils.import("resource://gre/modules/osfile.jsm");
var {utils: Cu} = Components;
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://testing-common/AppData.jsm", this);
function getEventDir() {
return OS.Path.join(do_get_tempdir().path, "crash-events");
@ -85,21 +89,26 @@ function do_crash(setup, callback, canReturnZero)
handleMinidump(callback);
}
function handleMinidump(callback)
{
// find minidump
let minidump = null;
function getMinidump() {
let en = do_get_tempdir().directoryEntries;
while (en.hasMoreElements()) {
let f = en.getNext().QueryInterface(Components.interfaces.nsILocalFile);
if (f.leafName.substr(-4) == ".dmp") {
minidump = f;
break;
return f;
}
}
if (minidump == null)
return null;
}
function handleMinidump(callback)
{
// find minidump
let minidump = getMinidump();
if (minidump == null) {
do_throw("No minidump found!");
}
let extrafile = minidump.clone();
extrafile.leafName = extrafile.leafName.slice(0, -4) + ".extra";
@ -109,25 +118,33 @@ function handleMinidump(callback)
// Just in case, don't let these files linger.
do_register_cleanup(function() {
if (minidump.exists())
minidump.remove(false);
if (extrafile.exists())
extrafile.remove(false);
if (memoryfile.exists())
memoryfile.remove(false);
});
if (minidump.exists()) {
minidump.remove(false);
}
if (extrafile.exists()) {
extrafile.remove(false);
}
if (memoryfile.exists()) {
memoryfile.remove(false);
}
});
do_check_true(extrafile.exists());
let extra = parseKeyValuePairsFromFile(extrafile);
if (callback)
if (callback) {
callback(minidump, extra);
}
if (minidump.exists())
if (minidump.exists()) {
minidump.remove(false);
if (extrafile.exists())
}
if (extrafile.exists()) {
extrafile.remove(false);
if (memoryfile.exists())
}
if (memoryfile.exists()) {
memoryfile.remove(false);
}
}
function do_content_crash(setup, callback)
@ -139,24 +156,31 @@ function do_content_crash(setup, callback)
// that here.
let crashReporter =
Components.classes["@mozilla.org/toolkit/crash-reporter;1"]
.getService(Components.interfaces.nsICrashReporter);
.getService(Components.interfaces.nsICrashReporter);
crashReporter.minidumpPath = do_get_tempdir();
let headfile = do_get_file("../unit/crasher_subprocess_head.js");
let tailfile = do_get_file("../unit/crasher_subprocess_tail.js");
if (setup) {
if (typeof(setup) == "function")
if (typeof(setup) == "function") {
// funky, but convenient
setup = "(" + setup.toSource() + ")();";
}
}
let handleCrash = function() {
try {
handleMinidump(callback);
} catch (x) {
do_report_unexpected_exception(x);
}
do_test_finished();
do_get_profile();
makeFakeAppDir().then(() => {
let id = getMinidump().leafName.slice(0, -4);
return Services.crashmanager.ensureCrashIsPresent(id);
}).then(() => {
try {
handleMinidump(callback);
} catch (x) {
do_report_unexpected_exception(x);
}
do_test_finished();
});
};
sendCommand("load(\"" + headfile.path.replace(/\\/g, "/") + "\");", () =>