mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 02:14:43 +00:00
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:
parent
d55bb9094c
commit
1b8ffd2807
@ -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);
|
||||
|
||||
|
@ -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, ¬es);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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``.
|
||||
|
@ -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()
|
||||
//
|
||||
|
@ -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:
|
||||
|
@ -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, "/") + "\");", () =>
|
||||
|
Loading…
Reference in New Issue
Block a user