Bug 875562 - Part 8: Write crash events for main process crashes; r=bsmedberg

This patch makes the crash reporter crash event directory aware and
writes main process crash event files at crash time.

--HG--
extra : rebase_source : 1d6a817ae401230b3fe8fcd236468ceb3e309a10
This commit is contained in:
Gregory Szorc 2014-02-18 15:58:03 -08:00
parent bdadbd3fbe
commit a0a4ade497
9 changed files with 253 additions and 8 deletions

View File

@ -93,6 +93,7 @@ try {
let (crashReporter =
Components.classes["@mozilla.org/toolkit/crash-reporter;1"]
.getService(Components.interfaces.nsICrashReporter)) {
crashReporter.UpdateCrashEventsDir();
crashReporter.minidumpPath = do_get_minidumpdir();
}
}
@ -1184,6 +1185,15 @@ function do_get_profile() {
let obsSvc = Components.classes["@mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService);
// We need to update the crash events directory when the profile changes.
if (runningInParent &&
"@mozilla.org/toolkit/crash-reporter;1" in Components.classes) {
let crashReporter =
Components.classes["@mozilla.org/toolkit/crash-reporter;1"]
.getService(Components.interfaces.nsICrashReporter);
crashReporter.UpdateCrashEventsDir();
}
if (!_profileInitialized) {
obsSvc.notifyObservers(null, "profile-do-change", "xpcshell-do-get-profile");
_profileInitialized = true;

View File

@ -151,11 +151,16 @@ static const XP_CHAR dumpFileExtension[] = {'.', 'd', 'm', 'p',
static const XP_CHAR extraFileExtension[] = {'.', 'e', 'x', 't',
'r', 'a', '\0'}; // .extra
static const char kCrashMainID[] = "crash.main.1\n";
static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr;
static XP_CHAR* pendingDirectory;
static XP_CHAR* crashReporterPath;
// Where crash events should go.
static XP_CHAR* eventsDirectory;
// If this is false, we don't launch the crash reporter
static bool doReport = true;
@ -557,6 +562,63 @@ bool MinidumpCallback(
#endif
}
// Write crash event file.
// Minidump IDs are UUIDs (36) + NULL.
static char id_ascii[37];
#ifdef XP_LINUX
const char * index = strrchr(descriptor.path(), '/');
MOZ_ASSERT(index);
MOZ_ASSERT(strlen(index) == 1 + 36 + 4); // "/" + UUID + ".dmp"
for (uint32_t i = 0; i < 36; i++) {
id_ascii[i] = *(index + 1 + i);
}
#else
MOZ_ASSERT(XP_STRLEN(minidump_id) == 36);
for (uint32_t i = 0; i < 36; i++) {
id_ascii[i] = *((char *)(minidump_id + i));
}
#endif
if (eventsDirectory) {
static XP_CHAR crashEventPath[XP_PATH_MAX];
int size = XP_PATH_MAX;
XP_CHAR* p;
p = Concat(crashEventPath, eventsDirectory, &size);
p = Concat(p, XP_PATH_SEPARATOR, &size);
#ifdef XP_LINUX
p = Concat(p, id_ascii, &size);
#else
p = Concat(p, minidump_id, &size);
#endif
#if defined(XP_WIN32)
HANDLE hFile = CreateFile(crashEventPath, GENERIC_WRITE, 0,
nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
nullptr);
if (hFile != INVALID_HANDLE_VALUE) {
DWORD nBytes;
WriteFile(hFile, kCrashMainID, sizeof(kCrashMainID) - 1, &nBytes,
nullptr);
WriteFile(hFile, crashTimeString, crashTimeStringLen, &nBytes, nullptr);
WriteFile(hFile, "\n", 1, &nBytes, nullptr);
WriteFile(hFile, id_ascii, strlen(id_ascii), &nBytes, nullptr);
CloseHandle(hFile);
}
#elif defined(XP_UNIX)
int fd = sys_open(crashEventPath,
O_WRONLY | O_CREAT | O_TRUNC,
0600);
if (fd != -1) {
unused << sys_write(fd, kCrashMainID, sizeof(kCrashMainID) - 1);
unused << sys_write(fd, crashTimeString, crashTimeStringLen);
unused << sys_write(fd, "\n", 1);
unused << sys_write(fd, id_ascii, strlen(id_ascii));
sys_close(fd);
}
#endif
}
#if defined(XP_WIN32)
if (!crashReporterAPIData->IsEmpty()) {
// write out API data
@ -1271,6 +1333,19 @@ InitInstallTime(nsACString& aInstallTime)
return NS_OK;
}
// Ensure a directory exists and create it if missing.
static nsresult
EnsureDirectoryExists(nsIFile* dir)
{
nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0700);
if (NS_WARN_IF(NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)) {
return rv;
}
return NS_OK;
}
// Annotate the crash report with a Unique User ID and time
// since install. Also do some prep work for recording
// time since last crash, which must be calculated at
@ -1286,14 +1361,7 @@ nsresult SetupExtraData(nsIFile* aAppDataDirectory,
rv = dataDirectory->AppendNative(NS_LITERAL_CSTRING("Crash Reports"));
NS_ENSURE_SUCCESS(rv, rv);
bool exists;
rv = dataDirectory->Exists(&exists);
NS_ENSURE_SUCCESS(rv, rv);
if (!exists) {
rv = dataDirectory->Create(nsIFile::DIRECTORY_TYPE, 0700);
NS_ENSURE_SUCCESS(rv, rv);
}
EnsureDirectoryExists(dataDirectory);
#if defined(XP_WIN32)
nsAutoString dataDirEnv(NS_LITERAL_STRING("MOZ_CRASHREPORTER_DATA_DIRECTORY="));
@ -1439,6 +1507,11 @@ nsresult UnsetExceptionHandler()
crashReporterPath = nullptr;
}
if (eventsDirectory) {
NS_Free(eventsDirectory);
eventsDirectory = nullptr;
}
#ifdef XP_MACOSX
posix_spawnattr_destroy(&spawnattr);
#endif
@ -1996,6 +2069,68 @@ nsresult SetSubmitReports(bool aSubmitReports)
return NS_OK;
}
void
UpdateCrashEventsDir()
{
nsCOMPtr<nsIFile> eventsDir;
// We prefer the following locations in order:
//
// 1. If environment variable is present, use it. We don't expect
// the environment variable except for tests and other atypical setups.
// 2. Inside the profile directory.
// 3. Inside the user application data directory (no profile available).
// 4. A temporary directory (setup likely is invalid / application is buggy).
const char *env = PR_GetEnv("CRASHES_EVENTS_DIR");
if (env) {
eventsDir = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
if (!eventsDir) {
return;
}
eventsDir->InitWithNativePath(nsDependentCString(env));
EnsureDirectoryExists(eventsDir);
} else {
nsresult rv = NS_GetSpecialDirectory("ProfD", getter_AddRefs(eventsDir));
if (NS_SUCCEEDED(rv)) {
eventsDir->Append(NS_LITERAL_STRING("crashes"));
EnsureDirectoryExists(eventsDir);
eventsDir->Append(NS_LITERAL_STRING("events"));
EnsureDirectoryExists(eventsDir);
} else {
rv = NS_GetSpecialDirectory("UAppData", getter_AddRefs(eventsDir));
if (NS_SUCCEEDED(rv)) {
eventsDir->Append(NS_LITERAL_STRING("Crash Reports"));
EnsureDirectoryExists(eventsDir);
eventsDir->Append(NS_LITERAL_STRING("events"));
EnsureDirectoryExists(eventsDir);
} else {
NS_WARNING("Couldn't get the user appdata directory. Crash events may not be produced.");
return;
}
}
}
#ifdef XP_WIN
nsString path;
eventsDir->GetPath(path);
eventsDirectory = reinterpret_cast<wchar_t*>(ToNewUnicode(path));
#else
nsCString path;
eventsDir->GetNativePath(path);
eventsDirectory = ToNewCString(path);
#endif
}
bool GetCrashEventsDir(nsAString& aPath)
{
if (!eventsDirectory) {
return false;
}
aPath = CONVERT_XP_CHAR_TO_UTF16(eventsDirectory);
return true;
}
static void
FindPendingDir()
{
@ -2406,6 +2541,7 @@ OOPInit()
dumpMapLock = new Mutex("CrashReporter::dumpMapLock");
FindPendingDir();
UpdateCrashEventsDir();
}
static void

View File

@ -33,6 +33,20 @@ class nsCStringHashKey;
namespace CrashReporter {
nsresult SetExceptionHandler(nsIFile* aXREDirectory, bool force=false);
nsresult UnsetExceptionHandler();
/**
* Tell the crash reporter to recalculate where crash events files should go.
*
* This should be called during crash reporter initialization and when a
* profile is activated or deactivated.
*/
void UpdateCrashEventsDir();
/**
* Get the path where crash event files should be written.
*/
bool GetCrashEventsDir(nsAString& aPath);
bool GetEnabled();
bool GetServerURL(nsACString& aServerURL);
nsresult SetServerURL(const nsACString& aServerURL);

View File

@ -12,6 +12,9 @@ let crashReporter =
Components.classes["@mozilla.org/toolkit/crash-reporter;1"]
.getService(Components.interfaces.nsICrashReporter);
// We need to call this or crash events go in an undefined location.
crashReporter.UpdateCrashEventsDir();
// Setting the minidump path is not allowed in content processes
let processType = Components.classes["@mozilla.org/xre/runtime;1"].
getService(Components.interfaces.nsIXULRuntime).processType;

View File

@ -1,3 +1,9 @@
Components.utils.import("resource://gre/modules/osfile.jsm");
function getEventDir() {
return OS.Path.join(do_get_tempdir().path, "crash-events");
}
/*
* Run an xpcshell subprocess and crash it.
*
@ -55,10 +61,25 @@ function do_crash(setup, callback, canReturnZero)
args.push('-e', setup);
}
args.push('-f', tailfile.path);
let env = Components.classes["@mozilla.org/process/environment;1"]
.getService(Components.interfaces.nsIEnvironment);
let crashD = do_get_tempdir();
crashD.append("crash-events");
if (!crashD.exists()) {
crashD.create(crashD.DIRECTORY_TYPE, 0700);
}
env.set("CRASHES_EVENTS_DIR", crashD.path);
try {
process.run(true, args, args.length);
}
catch(ex) {} // on Windows we exit with a -1 status when crashing.
finally {
env.set("CRASHES_EVENTS_DIR", "");
}
if (!canReturnZero) {
// should exit with an error (should have crashed)

View File

@ -0,0 +1,42 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const {utils: Cu} = Components;
Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://testing-common/AppData.jsm", this);
function run_test() {
run_next_test();
}
add_task(function* test_setup() {
do_get_profile();
yield makeFakeAppDir();
});
add_task(function* test_main_process_crash() {
let cm = Services.crashmanager;
Assert.ok(cm, "CrashManager available.");
let basename;
let deferred = Promise.defer();
do_crash("crashType = CrashTestUtils.CRASH_RUNTIMEABORT;",
(minidump, extra) => {
basename = minidump.leafName;
cm._eventsDirs = [getEventDir()];
cm.aggregateEventsFiles().then(deferred.resolve, deferred.reject);
},
true);
let count = yield deferred.promise;
Assert.equal(count, 1, "A single crash event file was seen.");
let crashes = yield cm.getCrashes();
Assert.equal(crashes.length, 1);
let crash = crashes[0];
Assert.ok(crash.isMainProcessCrash);
Assert.equal(crash.id + ".dmp", basename, "ID recorded properly");
});

View File

@ -26,3 +26,4 @@ run-if = os == 'win' || os == 'linux'
skip-if = os=='linux' && bits==32
[test_crash_AsyncShutdown.js]
[test_event_files.js]

View File

@ -1110,6 +1110,13 @@ nsXULAppInfo::SetSubmitReports(bool aEnabled)
return CrashReporter::SetSubmitReports(aEnabled);
}
NS_IMETHODIMP
nsXULAppInfo::UpdateCrashEventsDir()
{
CrashReporter::UpdateCrashEventsDir();
return NS_OK;
}
#endif
static const nsXULAppInfo kAppInfo;
@ -2991,6 +2998,7 @@ XREMain::XRE_mainInit(bool* aExitFlag)
if ((mAppData->flags & NS_XRE_ENABLE_CRASH_REPORTER) &&
NS_SUCCEEDED(
CrashReporter::SetExceptionHandler(mAppData->xreDirectory))) {
CrashReporter::UpdateCrashEventsDir();
if (mAppData->crashReporterURL)
CrashReporter::SetServerURL(nsDependentCString(mAppData->crashReporterURL));
@ -3644,6 +3652,8 @@ XREMain::XRE_mainStartup(bool* aExitFlag)
#ifdef MOZ_CRASHREPORTER
if (mAppData->flags & NS_XRE_ENABLE_CRASH_REPORTER)
MakeOrSetMinidumpPath(mProfD);
CrashReporter::UpdateCrashEventsDir();
#endif
nsAutoCString version;

View File

@ -109,4 +109,12 @@ interface nsICrashReporter : nsISupports
* User preference for submitting crash reports.
*/
attribute boolean submitReports;
/**
* Cause the crash reporter to re-evaluate where crash events should go.
*
* This should be called during application startup and whenever profiles
* change.
*/
void UpdateCrashEventsDir();
};