From a0a4ade497b4a669618b8bce52e2a5385c715795 Mon Sep 17 00:00:00 2001 From: Gregory Szorc Date: Tue, 18 Feb 2014 15:58:03 -0800 Subject: [PATCH] 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 --- testing/xpcshell/head.js | 10 ++ toolkit/crashreporter/nsExceptionHandler.cpp | 152 +++++++++++++++++- toolkit/crashreporter/nsExceptionHandler.h | 14 ++ .../test/unit/crasher_subprocess_head.js | 3 + .../test/unit/head_crashreporter.js | 21 +++ .../test/unit/test_event_files.js | 42 +++++ toolkit/crashreporter/test/unit/xpcshell.ini | 1 + toolkit/xre/nsAppRunner.cpp | 10 ++ xpcom/system/nsICrashReporter.idl | 8 + 9 files changed, 253 insertions(+), 8 deletions(-) create mode 100644 toolkit/crashreporter/test/unit/test_event_files.js diff --git a/testing/xpcshell/head.js b/testing/xpcshell/head.js index a3d162fd2957..4e1c643a2cd3 100644 --- a/testing/xpcshell/head.js +++ b/testing/xpcshell/head.js @@ -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; diff --git a/toolkit/crashreporter/nsExceptionHandler.cpp b/toolkit/crashreporter/nsExceptionHandler.cpp index 8d37f746e192..8bdc49232d97 100644 --- a/toolkit/crashreporter/nsExceptionHandler.cpp +++ b/toolkit/crashreporter/nsExceptionHandler.cpp @@ -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 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(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 diff --git a/toolkit/crashreporter/nsExceptionHandler.h b/toolkit/crashreporter/nsExceptionHandler.h index 72dacfc2f4eb..cdf97da77ed0 100644 --- a/toolkit/crashreporter/nsExceptionHandler.h +++ b/toolkit/crashreporter/nsExceptionHandler.h @@ -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); diff --git a/toolkit/crashreporter/test/unit/crasher_subprocess_head.js b/toolkit/crashreporter/test/unit/crasher_subprocess_head.js index af00f5a62c4f..a5d12e75facb 100644 --- a/toolkit/crashreporter/test/unit/crasher_subprocess_head.js +++ b/toolkit/crashreporter/test/unit/crasher_subprocess_head.js @@ -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; diff --git a/toolkit/crashreporter/test/unit/head_crashreporter.js b/toolkit/crashreporter/test/unit/head_crashreporter.js index 8153b65c2446..cd420177730e 100644 --- a/toolkit/crashreporter/test/unit/head_crashreporter.js +++ b/toolkit/crashreporter/test/unit/head_crashreporter.js @@ -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) diff --git a/toolkit/crashreporter/test/unit/test_event_files.js b/toolkit/crashreporter/test/unit/test_event_files.js new file mode 100644 index 000000000000..69dced363b0b --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_event_files.js @@ -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"); +}); diff --git a/toolkit/crashreporter/test/unit/xpcshell.ini b/toolkit/crashreporter/test/unit/xpcshell.ini index 104f7eac40f5..6875c80415e7 100644 --- a/toolkit/crashreporter/test/unit/xpcshell.ini +++ b/toolkit/crashreporter/test/unit/xpcshell.ini @@ -26,3 +26,4 @@ run-if = os == 'win' || os == 'linux' skip-if = os=='linux' && bits==32 [test_crash_AsyncShutdown.js] +[test_event_files.js] diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp index 6e9f091206ff..eafece0a8ad8 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -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; diff --git a/xpcom/system/nsICrashReporter.idl b/xpcom/system/nsICrashReporter.idl index f822a1b8fffa..6b285d641385 100644 --- a/xpcom/system/nsICrashReporter.idl +++ b/xpcom/system/nsICrashReporter.idl @@ -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(); };