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 = let (crashReporter =
Components.classes["@mozilla.org/toolkit/crash-reporter;1"] Components.classes["@mozilla.org/toolkit/crash-reporter;1"]
.getService(Components.interfaces.nsICrashReporter)) { .getService(Components.interfaces.nsICrashReporter)) {
crashReporter.UpdateCrashEventsDir();
crashReporter.minidumpPath = do_get_minidumpdir(); crashReporter.minidumpPath = do_get_minidumpdir();
} }
} }
@ -1184,6 +1185,15 @@ function do_get_profile() {
let obsSvc = Components.classes["@mozilla.org/observer-service;1"]. let obsSvc = Components.classes["@mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService); 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) { if (!_profileInitialized) {
obsSvc.notifyObservers(null, "profile-do-change", "xpcshell-do-get-profile"); obsSvc.notifyObservers(null, "profile-do-change", "xpcshell-do-get-profile");
_profileInitialized = true; _profileInitialized = true;

View File

@ -151,11 +151,16 @@ static const XP_CHAR dumpFileExtension[] = {'.', 'd', 'm', 'p',
static const XP_CHAR extraFileExtension[] = {'.', 'e', 'x', 't', static const XP_CHAR extraFileExtension[] = {'.', 'e', 'x', 't',
'r', 'a', '\0'}; // .extra 'r', 'a', '\0'}; // .extra
static const char kCrashMainID[] = "crash.main.1\n";
static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr; static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr;
static XP_CHAR* pendingDirectory; static XP_CHAR* pendingDirectory;
static XP_CHAR* crashReporterPath; static XP_CHAR* crashReporterPath;
// Where crash events should go.
static XP_CHAR* eventsDirectory;
// If this is false, we don't launch the crash reporter // If this is false, we don't launch the crash reporter
static bool doReport = true; static bool doReport = true;
@ -557,6 +562,63 @@ bool MinidumpCallback(
#endif #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 defined(XP_WIN32)
if (!crashReporterAPIData->IsEmpty()) { if (!crashReporterAPIData->IsEmpty()) {
// write out API data // write out API data
@ -1271,6 +1333,19 @@ InitInstallTime(nsACString& aInstallTime)
return NS_OK; 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 // Annotate the crash report with a Unique User ID and time
// since install. Also do some prep work for recording // since install. Also do some prep work for recording
// time since last crash, which must be calculated at // 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")); rv = dataDirectory->AppendNative(NS_LITERAL_CSTRING("Crash Reports"));
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
bool exists; EnsureDirectoryExists(dataDirectory);
rv = dataDirectory->Exists(&exists);
NS_ENSURE_SUCCESS(rv, rv);
if (!exists) {
rv = dataDirectory->Create(nsIFile::DIRECTORY_TYPE, 0700);
NS_ENSURE_SUCCESS(rv, rv);
}
#if defined(XP_WIN32) #if defined(XP_WIN32)
nsAutoString dataDirEnv(NS_LITERAL_STRING("MOZ_CRASHREPORTER_DATA_DIRECTORY=")); nsAutoString dataDirEnv(NS_LITERAL_STRING("MOZ_CRASHREPORTER_DATA_DIRECTORY="));
@ -1439,6 +1507,11 @@ nsresult UnsetExceptionHandler()
crashReporterPath = nullptr; crashReporterPath = nullptr;
} }
if (eventsDirectory) {
NS_Free(eventsDirectory);
eventsDirectory = nullptr;
}
#ifdef XP_MACOSX #ifdef XP_MACOSX
posix_spawnattr_destroy(&spawnattr); posix_spawnattr_destroy(&spawnattr);
#endif #endif
@ -1996,6 +2069,68 @@ nsresult SetSubmitReports(bool aSubmitReports)
return NS_OK; 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 static void
FindPendingDir() FindPendingDir()
{ {
@ -2406,6 +2541,7 @@ OOPInit()
dumpMapLock = new Mutex("CrashReporter::dumpMapLock"); dumpMapLock = new Mutex("CrashReporter::dumpMapLock");
FindPendingDir(); FindPendingDir();
UpdateCrashEventsDir();
} }
static void static void

View File

@ -33,6 +33,20 @@ class nsCStringHashKey;
namespace CrashReporter { namespace CrashReporter {
nsresult SetExceptionHandler(nsIFile* aXREDirectory, bool force=false); nsresult SetExceptionHandler(nsIFile* aXREDirectory, bool force=false);
nsresult UnsetExceptionHandler(); 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 GetEnabled();
bool GetServerURL(nsACString& aServerURL); bool GetServerURL(nsACString& aServerURL);
nsresult SetServerURL(const nsACString& aServerURL); nsresult SetServerURL(const nsACString& aServerURL);

View File

@ -12,6 +12,9 @@ let crashReporter =
Components.classes["@mozilla.org/toolkit/crash-reporter;1"] Components.classes["@mozilla.org/toolkit/crash-reporter;1"]
.getService(Components.interfaces.nsICrashReporter); .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 // Setting the minidump path is not allowed in content processes
let processType = Components.classes["@mozilla.org/xre/runtime;1"]. let processType = Components.classes["@mozilla.org/xre/runtime;1"].
getService(Components.interfaces.nsIXULRuntime).processType; 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. * Run an xpcshell subprocess and crash it.
* *
@ -55,10 +61,25 @@ function do_crash(setup, callback, canReturnZero)
args.push('-e', setup); args.push('-e', setup);
} }
args.push('-f', tailfile.path); 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 { try {
process.run(true, args, args.length); process.run(true, args, args.length);
} }
catch(ex) {} // on Windows we exit with a -1 status when crashing. catch(ex) {} // on Windows we exit with a -1 status when crashing.
finally {
env.set("CRASHES_EVENTS_DIR", "");
}
if (!canReturnZero) { if (!canReturnZero) {
// should exit with an error (should have crashed) // 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 skip-if = os=='linux' && bits==32
[test_crash_AsyncShutdown.js] [test_crash_AsyncShutdown.js]
[test_event_files.js]

View File

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

View File

@ -109,4 +109,12 @@ interface nsICrashReporter : nsISupports
* User preference for submitting crash reports. * User preference for submitting crash reports.
*/ */
attribute boolean submitReports; 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();
}; };