1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "DriverCrashGuard.h"
9 #include "nsAppDirectoryServiceDefs.h"
10 #include "nsDirectoryServiceUtils.h"
11 #include "nsExceptionHandler.h"
12 #include "nsServiceManagerUtils.h"
14 #include "nsXULAppAPI.h"
15 #include "mozilla/Preferences.h"
16 #include "mozilla/StaticPrefs_gfx.h"
17 #include "mozilla/StaticPrefs_webgl.h"
18 #include "mozilla/Telemetry.h"
19 #include "mozilla/Components.h"
20 #include "mozilla/gfx/Logging.h"
21 #include "mozilla/dom/ContentChild.h"
26 static const size_t NUM_CRASH_GUARD_TYPES
= size_t(CrashGuardType::NUM_TYPES
);
27 static const char* sCrashGuardNames
[] = {
32 static_assert(MOZ_ARRAY_LENGTH(sCrashGuardNames
) == NUM_CRASH_GUARD_TYPES
,
33 "CrashGuardType updated without a name string");
35 static inline void BuildCrashGuardPrefName(CrashGuardType aType
,
36 nsCString
& aOutPrefName
) {
37 MOZ_ASSERT(aType
< CrashGuardType::NUM_TYPES
);
38 MOZ_ASSERT(sCrashGuardNames
[size_t(aType
)]);
40 aOutPrefName
.AssignLiteral("gfx.crash-guard.status.");
41 aOutPrefName
.Append(sCrashGuardNames
[size_t(aType
)]);
44 DriverCrashGuard::DriverCrashGuard(CrashGuardType aType
,
45 dom::ContentParent
* aContentParent
)
47 mMode(aContentParent
? Mode::Proxy
: Mode::Normal
),
49 mGuardActivated(false),
50 mCrashDetected(false) {
51 BuildCrashGuardPrefName(aType
, mStatusPref
);
54 void DriverCrashGuard::InitializeIfNeeded() {
63 static inline bool AreCrashGuardsEnabled(CrashGuardType aType
) {
64 // Crash guard isn't supported in the GPU or RDD process since the entire
65 // process is basically a crash guard.
66 if (XRE_IsGPUProcess() || XRE_IsRDDProcess()) {
70 // We only use the crash guard on non-nightly channels, since the nightly
71 // channel is for development and having graphics features perma-disabled
72 // is rather annoying. Unless the user forces is with an environment
73 // variable, which comes in handy for testing.
74 // We handle the WMFVPXVideo crash guard differently to the other and always
75 // enable it as it completely breaks playback and there's no way around it.
76 if (aType
!= CrashGuardType::WMFVPXVideo
) {
77 return gfxEnv::MOZ_FORCE_CRASH_GUARD_NIGHTLY();
80 // Check to see if all guards have been disabled through the environment.
81 return !gfxEnv::MOZ_DISABLE_CRASH_GUARD();
84 void DriverCrashGuard::Initialize() {
85 if (!AreCrashGuardsEnabled(mType
)) {
89 // Using DriverCrashGuard off the main thread currently does not work. Under
90 // e10s it could conceivably work by dispatching the IPC calls via the main
91 // thread. In the parent process this would be harder. For now, we simply
92 // exit early instead.
93 if (!NS_IsMainThread()) {
97 mGfxInfo
= components::GfxInfo::Service();
99 if (XRE_IsContentProcess()) {
100 // Ask the parent whether or not activating the guard is okay. The parent
101 // won't bother if it detected a crash.
102 dom::ContentChild
* cc
= dom::ContentChild::GetSingleton();
103 cc
->SendBeginDriverCrashGuard(uint32_t(mType
), &mCrashDetected
);
104 if (mCrashDetected
) {
105 LogFeatureDisabled();
113 // Always check whether or not the lock file exists. For example, we could
114 // have crashed creating a D3D9 device in the parent process, and on restart
115 // are now requesting one in the child process. We catch everything here.
116 if (RecoverFromCrash()) {
117 mCrashDetected
= true;
121 // If the environment has changed, we always activate the guard. In the
122 // parent process this performs main-thread disk I/O. Child process guards
123 // only incur an IPC cost, so if we're proxying for a child process, we
124 // play it safe and activate the guard as long as we don't expect it to
126 if (CheckOrRefreshEnvironment() ||
127 (mMode
== Mode::Proxy
&& GetStatus() != DriverInitStatus::Crashed
)) {
132 // If we got here and our status is "crashed", then the environment has not
133 // updated and we do not want to attempt to use the driver again.
134 if (GetStatus() == DriverInitStatus::Crashed
) {
135 mCrashDetected
= true;
136 LogFeatureDisabled();
140 DriverCrashGuard::~DriverCrashGuard() {
141 if (!mGuardActivated
) {
145 if (XRE_IsParentProcess()) {
147 mGuardFile
->Remove(false);
150 // If during our initialization, no other process encountered a crash, we
151 // proceed to mark the status as okay.
152 if (GetStatus() != DriverInitStatus::Crashed
) {
153 SetStatus(DriverInitStatus::Okay
);
156 dom::ContentChild::GetSingleton()->SendEndDriverCrashGuard(uint32_t(mType
));
159 CrashReporter::RemoveCrashReportAnnotation(
160 CrashReporter::Annotation::GraphicsStartupTest
);
163 bool DriverCrashGuard::Crashed() {
164 InitializeIfNeeded();
166 // Note, we read mCrashDetected instead of GetStatus(), since in child
167 // processes we're not guaranteed that the prefs have been synced in
169 return mCrashDetected
;
172 nsCOMPtr
<nsIFile
> DriverCrashGuard::GetGuardFile() {
173 MOZ_ASSERT(XRE_IsParentProcess());
176 filename
.Assign(sCrashGuardNames
[size_t(mType
)]);
177 filename
.AppendLiteral(".guard");
179 nsCOMPtr
<nsIFile
> file
;
180 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR
,
181 getter_AddRefs(file
));
185 if (!NS_SUCCEEDED(file
->AppendNative(filename
))) {
191 void DriverCrashGuard::ActivateGuard() {
192 mGuardActivated
= true;
194 // Anotate crash reports only if we're a real guard. Otherwise, we could
195 // attribute a random parent process crash to a graphics problem in a child
197 if (mMode
!= Mode::Proxy
) {
198 CrashReporter::AnnotateCrashReport(
199 CrashReporter::Annotation::GraphicsStartupTest
, true);
202 // If we're in the content process, the rest of the guarding is handled
204 if (XRE_IsContentProcess()) {
208 SetStatus(DriverInitStatus::Attempting
);
210 if (mMode
!= Mode::Proxy
) {
211 // In parent process guards, we use two tombstones to detect crashes: a
212 // preferences and a zero-byte file on the filesystem.
215 // Create a temporary tombstone/lockfile.
217 mGuardFile
= GetGuardFile();
218 if (!mGuardFile
|| !NS_SUCCEEDED(mGuardFile
->OpenANSIFileDesc("w", &fp
))) {
225 void DriverCrashGuard::NotifyCrashed() {
226 SetStatus(DriverInitStatus::Crashed
);
231 bool DriverCrashGuard::RecoverFromCrash() {
232 MOZ_ASSERT(XRE_IsParentProcess());
234 nsCOMPtr
<nsIFile
> file
= GetGuardFile();
236 if ((file
&& NS_SUCCEEDED(file
->Exists(&exists
)) && exists
) ||
237 (GetStatus() == DriverInitStatus::Attempting
)) {
238 // If we get here, we've just recovered from a crash. Disable acceleration
239 // until the environment changes.
249 // Return true if the caller should proceed to guard for crashes. False if
250 // the environment has not changed. We persist the "changed" status across
251 // calls, so that after an environment changes, all guards for the new
252 // session are activated rather than just the first.
253 bool DriverCrashGuard::CheckOrRefreshEnvironment() {
254 // Our result can be cached statically since we don't check live prefs.
255 // We need to cache once per crash guard type.
256 // The first call to CheckOrRefrechEnvironment will always return true should
257 // the configuration had changed, following calls will return false.
258 static bool sBaseInfoChanged
[NUM_CRASH_GUARD_TYPES
];
259 static bool sBaseInfoChecked
[NUM_CRASH_GUARD_TYPES
];
261 const uint32_t type
= uint32_t(mType
);
262 if (!sBaseInfoChecked
[type
]) {
263 // None of the prefs we care about, so we cache the result statically.
264 sBaseInfoChecked
[type
] = true;
265 sBaseInfoChanged
[type
] = UpdateBaseEnvironment();
268 // Always update the full environment, even if the base info didn't change.
269 bool result
= UpdateEnvironment() || sBaseInfoChanged
[type
] ||
270 GetStatus() == DriverInitStatus::Unknown
;
271 sBaseInfoChanged
[type
] = false;
275 bool DriverCrashGuard::UpdateBaseEnvironment() {
276 bool changed
= false;
280 // Driver properties.
281 mGfxInfo
->GetAdapterDriverVersion(value
);
282 changed
|= CheckAndUpdatePref("driverVersion", value
);
283 mGfxInfo
->GetAdapterDeviceID(value
);
284 changed
|= CheckAndUpdatePref("deviceID", value
);
287 // Firefox properties.
288 changed
|= CheckAndUpdatePref(
289 "appVersion", NS_LITERAL_STRING_FROM_CSTRING(MOZ_APP_VERSION
));
294 bool DriverCrashGuard::FeatureEnabled(int aFeature
, bool aDefault
) {
299 nsCString discardFailureId
;
301 mGfxInfo
->GetFeatureStatus(aFeature
, discardFailureId
, &status
))) {
304 return status
== nsIGfxInfo::FEATURE_STATUS_OK
;
307 bool DriverCrashGuard::CheckAndUpdateBoolPref(const char* aPrefName
,
308 bool aCurrentValue
) {
309 std::string pref
= GetFullPrefName(aPrefName
);
312 if (NS_SUCCEEDED(Preferences::GetBool(pref
.c_str(), &oldValue
)) &&
313 oldValue
== aCurrentValue
) {
316 Preferences::SetBool(pref
.c_str(), aCurrentValue
);
320 bool DriverCrashGuard::CheckAndUpdatePref(const char* aPrefName
,
321 const nsAString
& aCurrentValue
) {
322 std::string pref
= GetFullPrefName(aPrefName
);
324 nsAutoString oldValue
;
325 Preferences::GetString(pref
.c_str(), oldValue
);
326 if (oldValue
== aCurrentValue
) {
329 Preferences::SetString(pref
.c_str(), aCurrentValue
);
333 std::string
DriverCrashGuard::GetFullPrefName(const char* aPref
) {
334 return std::string("gfx.crash-guard.") +
335 std::string(sCrashGuardNames
[uint32_t(mType
)]) + std::string(".") +
339 DriverInitStatus
DriverCrashGuard::GetStatus() const {
340 return (DriverInitStatus
)Preferences::GetInt(mStatusPref
.get(), 0);
343 void DriverCrashGuard::SetStatus(DriverInitStatus aStatus
) {
344 MOZ_ASSERT(XRE_IsParentProcess());
346 Preferences::SetInt(mStatusPref
.get(), int32_t(aStatus
));
349 void DriverCrashGuard::FlushPreferences() {
350 MOZ_ASSERT(XRE_IsParentProcess());
352 if (nsIPrefService
* prefService
= Preferences::GetService()) {
353 static_cast<Preferences
*>(prefService
)->SavePrefFileBlocking();
357 void DriverCrashGuard::ForEachActiveCrashGuard(
358 const CrashGuardCallback
& aCallback
) {
359 for (size_t i
= 0; i
< NUM_CRASH_GUARD_TYPES
; i
++) {
360 CrashGuardType type
= static_cast<CrashGuardType
>(i
);
362 if (!AreCrashGuardsEnabled(type
)) {
363 // Even if guards look active (via prefs), they can be ignored if globally
369 BuildCrashGuardPrefName(type
, prefName
);
372 static_cast<DriverInitStatus
>(Preferences::GetInt(prefName
.get(), 0));
373 if (status
!= DriverInitStatus::Crashed
) {
377 aCallback(sCrashGuardNames
[i
], prefName
.get());
381 D3D11LayersCrashGuard::D3D11LayersCrashGuard(dom::ContentParent
* aContentParent
)
382 : DriverCrashGuard(CrashGuardType::D3D11Layers
, aContentParent
) {}
384 void D3D11LayersCrashGuard::Initialize() {
385 if (!XRE_IsParentProcess()) {
386 // We assume the parent process already performed crash detection for
391 DriverCrashGuard::Initialize();
393 // If no telemetry states have been recorded, this will set the state to okay.
394 // Otherwise, it will have no effect.
395 RecordTelemetry(TelemetryState::Okay
);
398 bool D3D11LayersCrashGuard::UpdateEnvironment() {
399 // Our result can be cached statically since we don't check live prefs.
400 static bool checked
= false;
403 // We no longer need to bypass the crash guard.
409 bool changed
= false;
412 bool d2dEnabled
= StaticPrefs::gfx_direct2d_force_enabled_AtStartup() ||
413 (!StaticPrefs::gfx_direct2d_disabled_AtStartup() &&
414 FeatureEnabled(nsIGfxInfo::FEATURE_DIRECT2D
));
415 changed
|= CheckAndUpdateBoolPref("feature-d2d", d2dEnabled
);
417 bool d3d11Enabled
= gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING
);
418 changed
|= CheckAndUpdateBoolPref("feature-d3d11", d3d11Enabled
);
420 RecordTelemetry(TelemetryState::EnvironmentChanged
);
427 void D3D11LayersCrashGuard::LogCrashRecovery() {
428 RecordTelemetry(TelemetryState::RecoveredFromCrash
);
429 gfxCriticalNote
<< "D3D11 layers just crashed; D3D11 will be disabled.";
432 void D3D11LayersCrashGuard::LogFeatureDisabled() {
433 RecordTelemetry(TelemetryState::FeatureDisabled
);
434 gfxCriticalNote
<< "D3D11 layers disabled due to a prior crash.";
437 void D3D11LayersCrashGuard::RecordTelemetry(TelemetryState aState
) {
438 // D3D11LayersCrashGuard is a no-op in the child process.
439 if (!XRE_IsParentProcess()) {
443 // Since we instantiate this class more than once, make sure we only record
444 // the first state (since that is really all we care about).
445 static bool sTelemetryStateRecorded
= false;
446 if (sTelemetryStateRecorded
) {
450 Telemetry::Accumulate(Telemetry::GRAPHICS_DRIVER_STARTUP_TEST
,
452 sTelemetryStateRecorded
= true;
455 GLContextCrashGuard::GLContextCrashGuard(dom::ContentParent
* aContentParent
)
456 : DriverCrashGuard(CrashGuardType::GLContext
, aContentParent
) {}
458 void GLContextCrashGuard::Initialize() {
459 if (XRE_IsContentProcess()) {
460 // Disable the GL crash guard in content processes, since we're not going
461 // to lose the entire browser and we don't want to hinder WebGL
466 #if defined(MOZ_WIDGET_ANDROID)
467 // Disable the WebGL crash guard on Android - it doesn't use E10S, and
468 // its drivers will essentially never change, so the crash guard could
469 // permanently disable WebGL.
473 DriverCrashGuard::Initialize();
476 bool GLContextCrashGuard::UpdateEnvironment() {
477 static bool checked
= false;
480 // We no longer need to bypass the crash guard.
486 bool changed
= false;
489 changed
|= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-force-d3d11",
490 StaticPrefs::webgl_angle_force_d3d11());
491 changed
|= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-try-d3d11",
492 StaticPrefs::webgl_angle_try_d3d11());
493 changed
|= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-force-warp",
494 StaticPrefs::webgl_angle_force_warp());
495 changed
|= CheckAndUpdateBoolPref(
496 "gfx.driver-init.webgl-angle",
497 FeatureEnabled(nsIGfxInfo::FEATURE_WEBGL_ANGLE
, false));
498 changed
|= CheckAndUpdateBoolPref(
499 "gfx.driver-init.direct3d11-angle",
500 FeatureEnabled(nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE
, false));
506 void GLContextCrashGuard::LogCrashRecovery() {
507 gfxCriticalNote
<< "GLContext just crashed.";
510 void GLContextCrashGuard::LogFeatureDisabled() {
511 gfxCriticalNote
<< "GLContext remains enabled despite a previous crash.";
514 WMFVPXVideoCrashGuard::WMFVPXVideoCrashGuard(dom::ContentParent
* aContentParent
)
515 : DriverCrashGuard(CrashGuardType::WMFVPXVideo
, aContentParent
) {}
517 void WMFVPXVideoCrashGuard::LogCrashRecovery() {
519 << "WMF VPX decoder just crashed; hardware video will be disabled.";
522 void WMFVPXVideoCrashGuard::LogFeatureDisabled() {
524 << "WMF VPX video decoding is disabled due to a previous crash.";
528 } // namespace mozilla