summaryrefslogtreecommitdiffstats
path: root/gfx/src/DriverCrashGuard.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/src/DriverCrashGuard.cpp')
-rw-r--r--gfx/src/DriverCrashGuard.cpp620
1 files changed, 620 insertions, 0 deletions
diff --git a/gfx/src/DriverCrashGuard.cpp b/gfx/src/DriverCrashGuard.cpp
new file mode 100644
index 000000000..36d08dcf3
--- /dev/null
+++ b/gfx/src/DriverCrashGuard.cpp
@@ -0,0 +1,620 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "DriverCrashGuard.h"
+#include "gfxEnv.h"
+#include "gfxPrefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#endif
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Services.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/dom/ContentChild.h"
+
+namespace mozilla {
+namespace gfx {
+
+static const size_t NUM_CRASH_GUARD_TYPES = size_t(CrashGuardType::NUM_TYPES);
+static const char* sCrashGuardNames[] = {
+ "d3d11layers",
+ "d3d9video",
+ "glcontext",
+ "d3d11video",
+};
+static_assert(MOZ_ARRAY_LENGTH(sCrashGuardNames) == NUM_CRASH_GUARD_TYPES,
+ "CrashGuardType updated without a name string");
+
+static inline void
+BuildCrashGuardPrefName(CrashGuardType aType, nsCString& aOutPrefName)
+{
+ MOZ_ASSERT(aType < CrashGuardType::NUM_TYPES);
+ MOZ_ASSERT(sCrashGuardNames[size_t(aType)]);
+
+ aOutPrefName.Assign("gfx.crash-guard.status.");
+ aOutPrefName.Append(sCrashGuardNames[size_t(aType)]);
+}
+
+DriverCrashGuard::DriverCrashGuard(CrashGuardType aType, dom::ContentParent* aContentParent)
+ : mType(aType)
+ , mMode(aContentParent ? Mode::Proxy : Mode::Normal)
+ , mInitialized(false)
+ , mGuardActivated(false)
+ , mCrashDetected(false)
+{
+ BuildCrashGuardPrefName(aType, mStatusPref);
+}
+
+void
+DriverCrashGuard::InitializeIfNeeded()
+{
+ if (mInitialized) {
+ return;
+ }
+
+ mInitialized = true;
+ Initialize();
+}
+
+static inline bool
+AreCrashGuardsEnabled()
+{
+ // Crash guard isn't supported in the GPU process since the entire
+ // process is basically a crash guard.
+ if (XRE_IsGPUProcess()) {
+ return false;
+ }
+#ifdef NIGHTLY_BUILD
+ // We only use the crash guard on non-nightly channels, since the nightly
+ // channel is for development and having graphics features perma-disabled
+ // is rather annoying. Unless the user forces is with an environment
+ // variable, which comes in handy for testing.
+ return gfxEnv::ForceCrashGuardNightly();
+#else
+ // Check to see if all guards have been disabled through the environment.
+ if (gfxEnv::DisableCrashGuard()) {
+ return false;
+ }
+ return true;
+#endif
+}
+
+void
+DriverCrashGuard::Initialize()
+{
+ if (!AreCrashGuardsEnabled()) {
+ return;
+ }
+
+ // Using DriverCrashGuard off the main thread currently does not work. Under
+ // e10s it could conceivably work by dispatching the IPC calls via the main
+ // thread. In the parent process this would be harder. For now, we simply
+ // exit early instead.
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ mGfxInfo = services::GetGfxInfo();
+
+ if (XRE_IsContentProcess()) {
+ // Ask the parent whether or not activating the guard is okay. The parent
+ // won't bother if it detected a crash.
+ dom::ContentChild* cc = dom::ContentChild::GetSingleton();
+ cc->SendBeginDriverCrashGuard(uint32_t(mType), &mCrashDetected);
+ if (mCrashDetected) {
+ LogFeatureDisabled();
+ return;
+ }
+
+ ActivateGuard();
+ return;
+ }
+
+ // Always check whether or not the lock file exists. For example, we could
+ // have crashed creating a D3D9 device in the parent process, and on restart
+ // are now requesting one in the child process. We catch everything here.
+ if (RecoverFromCrash()) {
+ mCrashDetected = true;
+ return;
+ }
+
+ // If the environment has changed, we always activate the guard. In the
+ // parent process this performs main-thread disk I/O. Child process guards
+ // only incur an IPC cost, so if we're proxying for a child process, we
+ // play it safe and activate the guard as long as we don't expect it to
+ // crash.
+ if (CheckOrRefreshEnvironment() ||
+ (mMode == Mode::Proxy && GetStatus() != DriverInitStatus::Crashed))
+ {
+ ActivateGuard();
+ return;
+ }
+
+ // If we got here and our status is "crashed", then the environment has not
+ // updated and we do not want to attempt to use the driver again.
+ if (GetStatus() == DriverInitStatus::Crashed) {
+ mCrashDetected = true;
+ LogFeatureDisabled();
+ }
+}
+
+DriverCrashGuard::~DriverCrashGuard()
+{
+ if (!mGuardActivated) {
+ return;
+ }
+
+ if (XRE_IsParentProcess()) {
+ if (mGuardFile) {
+ mGuardFile->Remove(false);
+ }
+
+ // If during our initialization, no other process encountered a crash, we
+ // proceed to mark the status as okay.
+ if (GetStatus() != DriverInitStatus::Crashed) {
+ SetStatus(DriverInitStatus::Okay);
+ }
+ } else {
+ dom::ContentChild::GetSingleton()->SendEndDriverCrashGuard(uint32_t(mType));
+ }
+
+#ifdef MOZ_CRASHREPORTER
+ // Remove the crash report annotation.
+ CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("GraphicsStartupTest"),
+ NS_LITERAL_CSTRING(""));
+#endif
+}
+
+bool
+DriverCrashGuard::Crashed()
+{
+ InitializeIfNeeded();
+
+ // Note, we read mCrashDetected instead of GetStatus(), since in child
+ // processes we're not guaranteed that the prefs have been synced in
+ // time.
+ return mCrashDetected;
+}
+
+nsCOMPtr<nsIFile>
+DriverCrashGuard::GetGuardFile()
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsCString filename;
+ filename.Assign(sCrashGuardNames[size_t(mType)]);
+ filename.Append(".guard");
+
+ nsCOMPtr<nsIFile> file;
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, getter_AddRefs(file));
+ if (!file) {
+ return nullptr;
+ }
+ if (!NS_SUCCEEDED(file->AppendNative(filename))) {
+ return nullptr;
+ }
+ return file;
+}
+
+void
+DriverCrashGuard::ActivateGuard()
+{
+ mGuardActivated = true;
+
+#ifdef MOZ_CRASHREPORTER
+ // Anotate crash reports only if we're a real guard. Otherwise, we could
+ // attribute a random parent process crash to a graphics problem in a child
+ // process.
+ if (mMode != Mode::Proxy) {
+ CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("GraphicsStartupTest"),
+ NS_LITERAL_CSTRING("1"));
+ }
+#endif
+
+ // If we're in the content process, the rest of the guarding is handled
+ // in the parent.
+ if (XRE_IsContentProcess()) {
+ return;
+ }
+
+ SetStatus(DriverInitStatus::Attempting);
+
+ if (mMode != Mode::Proxy) {
+ // In parent process guards, we use two tombstones to detect crashes: a
+ // preferences and a zero-byte file on the filesystem.
+ FlushPreferences();
+
+ // Create a temporary tombstone/lockfile.
+ FILE* fp = nullptr;
+ mGuardFile = GetGuardFile();
+ if (!mGuardFile || !NS_SUCCEEDED(mGuardFile->OpenANSIFileDesc("w", &fp))) {
+ return;
+ }
+ fclose(fp);
+ }
+}
+
+void
+DriverCrashGuard::NotifyCrashed()
+{
+ CheckOrRefreshEnvironment();
+ SetStatus(DriverInitStatus::Crashed);
+ FlushPreferences();
+ LogCrashRecovery();
+}
+
+bool
+DriverCrashGuard::RecoverFromCrash()
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsCOMPtr<nsIFile> file = GetGuardFile();
+ bool exists;
+ if ((file &&
+ NS_SUCCEEDED(file->Exists(&exists)) &&
+ exists) ||
+ (GetStatus() == DriverInitStatus::Attempting))
+ {
+ // If we get here, we've just recovered from a crash. Disable acceleration
+ // until the environment changes.
+ if (file) {
+ file->Remove(false);
+ }
+ NotifyCrashed();
+ return true;
+ }
+ return false;
+}
+
+// Return true if the caller should proceed to guard for crashes. False if
+// the environment has not changed. We persist the "changed" status across
+// calls, so that after an environment changes, all guards for the new
+// session are activated rather than just the first.
+bool
+DriverCrashGuard::CheckOrRefreshEnvironment()
+{
+ // Our result can be cached statically since we don't check live prefs.
+ static bool sBaseInfoChanged = false;
+ static bool sBaseInfoChecked = false;
+
+ if (!sBaseInfoChecked) {
+ // None of the prefs we care about, so we cache the result statically.
+ sBaseInfoChecked = true;
+ sBaseInfoChanged = UpdateBaseEnvironment();
+ }
+
+ // Always update the full environment, even if the base info didn't change.
+ return UpdateEnvironment() ||
+ sBaseInfoChanged ||
+ GetStatus() == DriverInitStatus::Unknown;
+}
+
+bool
+DriverCrashGuard::UpdateBaseEnvironment()
+{
+ bool changed = false;
+ if (mGfxInfo) {
+ nsString value;
+
+ // Driver properties.
+ mGfxInfo->GetAdapterDriverVersion(value);
+ changed |= CheckAndUpdatePref("driverVersion", value);
+ mGfxInfo->GetAdapterDeviceID(value);
+ changed |= CheckAndUpdatePref("deviceID", value);
+ }
+
+ // Firefox properties.
+ changed |= CheckAndUpdatePref("appVersion", NS_LITERAL_STRING(MOZ_APP_VERSION));
+
+ return changed;
+}
+
+bool
+DriverCrashGuard::FeatureEnabled(int aFeature, bool aDefault)
+{
+ if (!mGfxInfo) {
+ return aDefault;
+ }
+ int32_t status;
+ nsCString discardFailureId;
+ if (!NS_SUCCEEDED(mGfxInfo->GetFeatureStatus(aFeature, discardFailureId, &status))) {
+ return false;
+ }
+ return status == nsIGfxInfo::FEATURE_STATUS_OK;
+}
+
+bool
+DriverCrashGuard::CheckAndUpdateBoolPref(const char* aPrefName, bool aCurrentValue)
+{
+ std::string pref = GetFullPrefName(aPrefName);
+
+ bool oldValue;
+ if (NS_SUCCEEDED(Preferences::GetBool(pref.c_str(), &oldValue)) &&
+ oldValue == aCurrentValue)
+ {
+ return false;
+ }
+ Preferences::SetBool(pref.c_str(), aCurrentValue);
+ return true;
+}
+
+bool
+DriverCrashGuard::CheckAndUpdatePref(const char* aPrefName, const nsAString& aCurrentValue)
+{
+ std::string pref = GetFullPrefName(aPrefName);
+
+ nsAdoptingString oldValue = Preferences::GetString(pref.c_str());
+ if (oldValue == aCurrentValue) {
+ return false;
+ }
+ Preferences::SetString(pref.c_str(), aCurrentValue);
+ return true;
+}
+
+std::string
+DriverCrashGuard::GetFullPrefName(const char* aPref)
+{
+ return std::string("gfx.crash-guard.") +
+ std::string(sCrashGuardNames[uint32_t(mType)]) +
+ std::string(".") +
+ std::string(aPref);
+}
+
+DriverInitStatus
+DriverCrashGuard::GetStatus() const
+{
+ return (DriverInitStatus)Preferences::GetInt(mStatusPref.get(), 0);
+}
+
+void
+DriverCrashGuard::SetStatus(DriverInitStatus aStatus)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ Preferences::SetInt(mStatusPref.get(), int32_t(aStatus));
+}
+
+void
+DriverCrashGuard::FlushPreferences()
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (nsIPrefService* prefService = Preferences::GetService()) {
+ prefService->SavePrefFile(nullptr);
+ }
+}
+
+void
+DriverCrashGuard::ForEachActiveCrashGuard(const CrashGuardCallback& aCallback)
+{
+ if (!AreCrashGuardsEnabled()) {
+ // Even if guards look active (via prefs), they can be ignored if globally
+ // disabled.
+ return;
+ }
+
+ for (size_t i = 0; i < NUM_CRASH_GUARD_TYPES; i++) {
+ CrashGuardType type = static_cast<CrashGuardType>(i);
+
+ nsCString prefName;
+ BuildCrashGuardPrefName(type, prefName);
+
+ auto status =
+ static_cast<DriverInitStatus>(Preferences::GetInt(prefName.get(), 0));
+ if (status != DriverInitStatus::Crashed) {
+ continue;
+ }
+
+ aCallback(sCrashGuardNames[i], prefName.get());
+ }
+}
+
+D3D11LayersCrashGuard::D3D11LayersCrashGuard(dom::ContentParent* aContentParent)
+ : DriverCrashGuard(CrashGuardType::D3D11Layers, aContentParent)
+{
+}
+
+void
+D3D11LayersCrashGuard::Initialize()
+{
+ if (!XRE_IsParentProcess()) {
+ // We assume the parent process already performed crash detection for
+ // graphics devices.
+ return;
+ }
+
+ DriverCrashGuard::Initialize();
+
+ // If no telemetry states have been recorded, this will set the state to okay.
+ // Otherwise, it will have no effect.
+ RecordTelemetry(TelemetryState::Okay);
+}
+
+bool
+D3D11LayersCrashGuard::UpdateEnvironment()
+{
+ // Our result can be cached statically since we don't check live prefs.
+ static bool checked = false;
+ static bool changed = false;
+
+ if (checked) {
+ return changed;
+ }
+
+ checked = true;
+
+ // Feature status.
+#if defined(XP_WIN)
+ bool d2dEnabled = gfxPrefs::Direct2DForceEnabled() ||
+ (!gfxPrefs::Direct2DDisabled() && FeatureEnabled(nsIGfxInfo::FEATURE_DIRECT2D));
+ changed |= CheckAndUpdateBoolPref("feature-d2d", d2dEnabled);
+
+ bool d3d11Enabled = !gfxPrefs::LayersPreferD3D9();
+ if (!FeatureEnabled(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS)) {
+ d3d11Enabled = false;
+ }
+ changed |= CheckAndUpdateBoolPref("feature-d3d11", d3d11Enabled);
+#endif
+
+ if (!changed) {
+ return false;
+ }
+
+ RecordTelemetry(TelemetryState::EnvironmentChanged);
+ return true;
+}
+
+void
+D3D11LayersCrashGuard::LogCrashRecovery()
+{
+ RecordTelemetry(TelemetryState::RecoveredFromCrash);
+ gfxCriticalNote << "D3D11 layers just crashed; D3D11 will be disabled.";
+}
+
+void
+D3D11LayersCrashGuard::LogFeatureDisabled()
+{
+ RecordTelemetry(TelemetryState::FeatureDisabled);
+ gfxCriticalNote << "D3D11 layers disabled due to a prior crash.";
+}
+
+void
+D3D11LayersCrashGuard::RecordTelemetry(TelemetryState aState)
+{
+ // D3D11LayersCrashGuard is a no-op in the child process.
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ // Since we instantiate this class more than once, make sure we only record
+ // the first state (since that is really all we care about).
+ static bool sTelemetryStateRecorded = false;
+ if (sTelemetryStateRecorded) {
+ return;
+ }
+
+ Telemetry::Accumulate(Telemetry::GRAPHICS_DRIVER_STARTUP_TEST, int32_t(aState));
+ sTelemetryStateRecorded = true;
+}
+
+D3D9VideoCrashGuard::D3D9VideoCrashGuard(dom::ContentParent* aContentParent)
+ : DriverCrashGuard(CrashGuardType::D3D9Video, aContentParent)
+{
+}
+
+bool
+D3D9VideoCrashGuard::UpdateEnvironment()
+{
+ // We don't care about any extra preferences here.
+ return false;
+}
+
+void
+D3D9VideoCrashGuard::LogCrashRecovery()
+{
+ gfxCriticalNote << "DXVA2D3D9 just crashed; hardware video will be disabled.";
+}
+
+void
+D3D9VideoCrashGuard::LogFeatureDisabled()
+{
+ gfxCriticalNote << "DXVA2D3D9 video decoding is disabled due to a previous crash.";
+}
+
+D3D11VideoCrashGuard::D3D11VideoCrashGuard(dom::ContentParent* aContentParent)
+ : DriverCrashGuard(CrashGuardType::D3D11Video, aContentParent)
+{
+}
+
+bool
+D3D11VideoCrashGuard::UpdateEnvironment()
+{
+ // We don't care about any extra preferences here.
+ return false;
+}
+
+void
+D3D11VideoCrashGuard::LogCrashRecovery()
+{
+ gfxCriticalNote << "DXVA2D3D11 just crashed; hardware video will be disabled.";
+}
+
+void
+D3D11VideoCrashGuard::LogFeatureDisabled()
+{
+ gfxCriticalNote << "DXVA2D3D11 video decoding is disabled due to a previous crash.";
+}
+
+GLContextCrashGuard::GLContextCrashGuard(dom::ContentParent* aContentParent)
+ : DriverCrashGuard(CrashGuardType::GLContext, aContentParent)
+{
+}
+
+void
+GLContextCrashGuard::Initialize()
+{
+ if (XRE_IsContentProcess()) {
+ // Disable the GL crash guard in content processes, since we're not going
+ // to lose the entire browser and we don't want to hinder WebGL availability.
+ return;
+ }
+
+#if defined(MOZ_WIDGET_ANDROID)
+ // Disable the WebGL crash guard on Android - it doesn't use E10S, and
+ // its drivers will essentially never change, so the crash guard could
+ // permanently disable WebGL.
+ return;
+#endif
+
+ DriverCrashGuard::Initialize();
+}
+
+bool
+GLContextCrashGuard::UpdateEnvironment()
+{
+ static bool checked = false;
+ static bool changed = false;
+
+ if (checked) {
+ return changed;
+ }
+
+ checked = true;
+
+#if defined(XP_WIN)
+ changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-force-d3d11",
+ gfxPrefs::WebGLANGLEForceD3D11());
+ changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-try-d3d11",
+ gfxPrefs::WebGLANGLETryD3D11());
+ changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-force-warp",
+ gfxPrefs::WebGLANGLEForceWARP());
+ changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle",
+ FeatureEnabled(nsIGfxInfo::FEATURE_WEBGL_ANGLE, false));
+ changed |= CheckAndUpdateBoolPref("gfx.driver-init.direct3d11-angle",
+ FeatureEnabled(nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE, false));
+#endif
+
+ return changed;
+}
+
+void
+GLContextCrashGuard::LogCrashRecovery()
+{
+ gfxCriticalNote << "GLContext just crashed.";
+}
+
+void
+GLContextCrashGuard::LogFeatureDisabled()
+{
+ gfxCriticalNote << "GLContext remains enabled despite a previous crash.";
+}
+
+} // namespace gfx
+} // namespace mozilla