/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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 "gfxConfig.h" #include "nsAppDirectoryServiceDefs.h" #include "nsDirectoryServiceUtils.h" #include "nsExceptionHandler.h" #include "nsServiceManagerUtils.h" #include "nsString.h" #include "nsXULAppAPI.h" #include "mozilla/Preferences.h" #include "mozilla/StaticPrefs_gfx.h" #include "mozilla/StaticPrefs_webgl.h" #include "mozilla/Telemetry.h" #include "mozilla/Components.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", "glcontext", "wmfvpxvideo", }; 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.AssignLiteral("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(CrashGuardType aType) { // Crash guard isn't supported in the GPU or RDD process since the entire // process is basically a crash guard. if (XRE_IsGPUProcess() || XRE_IsRDDProcess()) { 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. // We handle the WMFVPXVideo crash guard differently to the other and always // enable it as it completely breaks playback and there's no way around it. if (aType != CrashGuardType::WMFVPXVideo) { return gfxEnv::MOZ_FORCE_CRASH_GUARD_NIGHTLY(); } #endif // Check to see if all guards have been disabled through the environment. return !gfxEnv::MOZ_DISABLE_CRASH_GUARD(); } void DriverCrashGuard::Initialize() { if (!AreCrashGuardsEnabled(mType)) { 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 = components::GfxInfo::Service(); 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)); } CrashReporter::RemoveCrashReportAnnotation( CrashReporter::Annotation::GraphicsStartupTest); } 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 DriverCrashGuard::GetGuardFile() { MOZ_ASSERT(XRE_IsParentProcess()); nsCString filename; filename.Assign(sCrashGuardNames[size_t(mType)]); filename.AppendLiteral(".guard"); nsCOMPtr 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; // 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( CrashReporter::Annotation::GraphicsStartupTest, true); } // 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() { SetStatus(DriverInitStatus::Crashed); FlushPreferences(); LogCrashRecovery(); } bool DriverCrashGuard::RecoverFromCrash() { MOZ_ASSERT(XRE_IsParentProcess()); nsCOMPtr 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. // We need to cache once per crash guard type. // The first call to CheckOrRefrechEnvironment will always return true should // the configuration had changed, following calls will return false. static bool sBaseInfoChanged[NUM_CRASH_GUARD_TYPES]; static bool sBaseInfoChecked[NUM_CRASH_GUARD_TYPES]; const uint32_t type = uint32_t(mType); if (!sBaseInfoChecked[type]) { // None of the prefs we care about, so we cache the result statically. sBaseInfoChecked[type] = true; sBaseInfoChanged[type] = UpdateBaseEnvironment(); } // Always update the full environment, even if the base info didn't change. bool result = UpdateEnvironment() || sBaseInfoChanged[type] || GetStatus() == DriverInitStatus::Unknown; sBaseInfoChanged[type] = false; return result; } 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_FROM_CSTRING(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); nsAutoString oldValue; Preferences::GetString(pref.c_str(), oldValue); 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()) { static_cast(prefService)->SavePrefFileBlocking(); } } void DriverCrashGuard::ForEachActiveCrashGuard( const CrashGuardCallback& aCallback) { for (size_t i = 0; i < NUM_CRASH_GUARD_TYPES; i++) { CrashGuardType type = static_cast(i); if (!AreCrashGuardsEnabled(type)) { // Even if guards look active (via prefs), they can be ignored if globally // disabled. continue; } nsCString prefName; BuildCrashGuardPrefName(type, prefName); auto status = static_cast(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; if (checked) { // We no longer need to bypass the crash guard. return false; } checked = true; bool changed = false; // Feature status. #if defined(XP_WIN) bool d2dEnabled = StaticPrefs::gfx_direct2d_force_enabled_AtStartup() || (!StaticPrefs::gfx_direct2d_disabled_AtStartup() && FeatureEnabled(nsIGfxInfo::FEATURE_DIRECT2D)); changed |= CheckAndUpdateBoolPref("feature-d2d", d2dEnabled); bool d3d11Enabled = gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING); changed |= CheckAndUpdateBoolPref("feature-d3d11", d3d11Enabled); if (changed) { RecordTelemetry(TelemetryState::EnvironmentChanged); } #endif return changed; } 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; } 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; if (checked) { // We no longer need to bypass the crash guard. return false; } checked = true; bool changed = false; #if defined(XP_WIN) changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-force-d3d11", StaticPrefs::webgl_angle_force_d3d11()); changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-try-d3d11", StaticPrefs::webgl_angle_try_d3d11()); changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-force-warp", StaticPrefs::webgl_angle_force_warp()); 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."; } WMFVPXVideoCrashGuard::WMFVPXVideoCrashGuard(dom::ContentParent* aContentParent) : DriverCrashGuard(CrashGuardType::WMFVPXVideo, aContentParent) {} void WMFVPXVideoCrashGuard::LogCrashRecovery() { gfxCriticalNote << "WMF VPX decoder just crashed; hardware video will be disabled."; } void WMFVPXVideoCrashGuard::LogFeatureDisabled() { gfxCriticalNote << "WMF VPX video decoding is disabled due to a previous crash."; } } // namespace gfx } // namespace mozilla