Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 : #include "DriverCrashGuard.h"
6 : #include "gfxEnv.h"
7 : #include "gfxPrefs.h"
8 : #include "gfxConfig.h"
9 : #include "nsAppDirectoryServiceDefs.h"
10 : #include "nsDirectoryServiceUtils.h"
11 : #ifdef MOZ_CRASHREPORTER
12 : #include "nsExceptionHandler.h"
13 : #endif
14 : #include "nsServiceManagerUtils.h"
15 : #include "nsString.h"
16 : #include "nsXULAppAPI.h"
17 : #include "mozilla/Preferences.h"
18 : #include "mozilla/Telemetry.h"
19 : #include "mozilla/Services.h"
20 : #include "mozilla/gfx/Logging.h"
21 : #include "mozilla/dom/ContentChild.h"
22 :
23 : namespace mozilla {
24 : namespace gfx {
25 :
26 : static const size_t NUM_CRASH_GUARD_TYPES = size_t(CrashGuardType::NUM_TYPES);
27 : static const char* sCrashGuardNames[] = {
28 : "d3d11layers",
29 : "d3d9video",
30 : "glcontext",
31 : "d3d11video",
32 : };
33 : static_assert(MOZ_ARRAY_LENGTH(sCrashGuardNames) == NUM_CRASH_GUARD_TYPES,
34 : "CrashGuardType updated without a name string");
35 :
36 : static inline void
37 0 : BuildCrashGuardPrefName(CrashGuardType aType, nsCString& aOutPrefName)
38 : {
39 0 : MOZ_ASSERT(aType < CrashGuardType::NUM_TYPES);
40 0 : MOZ_ASSERT(sCrashGuardNames[size_t(aType)]);
41 :
42 0 : aOutPrefName.Assign("gfx.crash-guard.status.");
43 0 : aOutPrefName.Append(sCrashGuardNames[size_t(aType)]);
44 0 : }
45 :
46 0 : DriverCrashGuard::DriverCrashGuard(CrashGuardType aType, dom::ContentParent* aContentParent)
47 : : mType(aType)
48 0 : , mMode(aContentParent ? Mode::Proxy : Mode::Normal)
49 : , mInitialized(false)
50 : , mGuardActivated(false)
51 0 : , mCrashDetected(false)
52 : {
53 0 : BuildCrashGuardPrefName(aType, mStatusPref);
54 0 : }
55 :
56 : void
57 0 : DriverCrashGuard::InitializeIfNeeded()
58 : {
59 0 : if (mInitialized) {
60 0 : return;
61 : }
62 :
63 0 : mInitialized = true;
64 0 : Initialize();
65 : }
66 :
67 : static inline bool
68 0 : AreCrashGuardsEnabled()
69 : {
70 : // Crash guard isn't supported in the GPU process since the entire
71 : // process is basically a crash guard.
72 0 : if (XRE_IsGPUProcess()) {
73 0 : return false;
74 : }
75 : #ifdef NIGHTLY_BUILD
76 : // We only use the crash guard on non-nightly channels, since the nightly
77 : // channel is for development and having graphics features perma-disabled
78 : // is rather annoying. Unless the user forces is with an environment
79 : // variable, which comes in handy for testing.
80 0 : return gfxEnv::ForceCrashGuardNightly();
81 : #else
82 : // Check to see if all guards have been disabled through the environment.
83 : if (gfxEnv::DisableCrashGuard()) {
84 : return false;
85 : }
86 : return true;
87 : #endif
88 : }
89 :
90 : void
91 0 : DriverCrashGuard::Initialize()
92 : {
93 0 : if (!AreCrashGuardsEnabled()) {
94 0 : return;
95 : }
96 :
97 : // Using DriverCrashGuard off the main thread currently does not work. Under
98 : // e10s it could conceivably work by dispatching the IPC calls via the main
99 : // thread. In the parent process this would be harder. For now, we simply
100 : // exit early instead.
101 0 : if (!NS_IsMainThread()) {
102 0 : return;
103 : }
104 :
105 0 : mGfxInfo = services::GetGfxInfo();
106 :
107 0 : if (XRE_IsContentProcess()) {
108 : // Ask the parent whether or not activating the guard is okay. The parent
109 : // won't bother if it detected a crash.
110 0 : dom::ContentChild* cc = dom::ContentChild::GetSingleton();
111 0 : cc->SendBeginDriverCrashGuard(uint32_t(mType), &mCrashDetected);
112 0 : if (mCrashDetected) {
113 0 : LogFeatureDisabled();
114 0 : return;
115 : }
116 :
117 0 : ActivateGuard();
118 0 : return;
119 : }
120 :
121 : // Always check whether or not the lock file exists. For example, we could
122 : // have crashed creating a D3D9 device in the parent process, and on restart
123 : // are now requesting one in the child process. We catch everything here.
124 0 : if (RecoverFromCrash()) {
125 0 : mCrashDetected = true;
126 0 : return;
127 : }
128 :
129 : // If the environment has changed, we always activate the guard. In the
130 : // parent process this performs main-thread disk I/O. Child process guards
131 : // only incur an IPC cost, so if we're proxying for a child process, we
132 : // play it safe and activate the guard as long as we don't expect it to
133 : // crash.
134 0 : if (CheckOrRefreshEnvironment() ||
135 0 : (mMode == Mode::Proxy && GetStatus() != DriverInitStatus::Crashed))
136 : {
137 0 : ActivateGuard();
138 0 : return;
139 : }
140 :
141 : // If we got here and our status is "crashed", then the environment has not
142 : // updated and we do not want to attempt to use the driver again.
143 0 : if (GetStatus() == DriverInitStatus::Crashed) {
144 0 : mCrashDetected = true;
145 0 : LogFeatureDisabled();
146 : }
147 : }
148 :
149 0 : DriverCrashGuard::~DriverCrashGuard()
150 : {
151 0 : if (!mGuardActivated) {
152 0 : return;
153 : }
154 :
155 0 : if (XRE_IsParentProcess()) {
156 0 : if (mGuardFile) {
157 0 : mGuardFile->Remove(false);
158 : }
159 :
160 : // If during our initialization, no other process encountered a crash, we
161 : // proceed to mark the status as okay.
162 0 : if (GetStatus() != DriverInitStatus::Crashed) {
163 0 : SetStatus(DriverInitStatus::Okay);
164 : }
165 : } else {
166 0 : dom::ContentChild::GetSingleton()->SendEndDriverCrashGuard(uint32_t(mType));
167 : }
168 :
169 : #ifdef MOZ_CRASHREPORTER
170 : // Remove the crash report annotation.
171 0 : CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("GraphicsStartupTest"),
172 0 : NS_LITERAL_CSTRING(""));
173 : #endif
174 0 : }
175 :
176 : bool
177 0 : DriverCrashGuard::Crashed()
178 : {
179 0 : InitializeIfNeeded();
180 :
181 : // Note, we read mCrashDetected instead of GetStatus(), since in child
182 : // processes we're not guaranteed that the prefs have been synced in
183 : // time.
184 0 : return mCrashDetected;
185 : }
186 :
187 : nsCOMPtr<nsIFile>
188 0 : DriverCrashGuard::GetGuardFile()
189 : {
190 0 : MOZ_ASSERT(XRE_IsParentProcess());
191 :
192 0 : nsCString filename;
193 0 : filename.Assign(sCrashGuardNames[size_t(mType)]);
194 0 : filename.Append(".guard");
195 :
196 0 : nsCOMPtr<nsIFile> file;
197 0 : NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, getter_AddRefs(file));
198 0 : if (!file) {
199 0 : return nullptr;
200 : }
201 0 : if (!NS_SUCCEEDED(file->AppendNative(filename))) {
202 0 : return nullptr;
203 : }
204 0 : return file;
205 : }
206 :
207 : void
208 0 : DriverCrashGuard::ActivateGuard()
209 : {
210 0 : mGuardActivated = true;
211 :
212 : #ifdef MOZ_CRASHREPORTER
213 : // Anotate crash reports only if we're a real guard. Otherwise, we could
214 : // attribute a random parent process crash to a graphics problem in a child
215 : // process.
216 0 : if (mMode != Mode::Proxy) {
217 0 : CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("GraphicsStartupTest"),
218 0 : NS_LITERAL_CSTRING("1"));
219 : }
220 : #endif
221 :
222 : // If we're in the content process, the rest of the guarding is handled
223 : // in the parent.
224 0 : if (XRE_IsContentProcess()) {
225 0 : return;
226 : }
227 :
228 0 : SetStatus(DriverInitStatus::Attempting);
229 :
230 0 : if (mMode != Mode::Proxy) {
231 : // In parent process guards, we use two tombstones to detect crashes: a
232 : // preferences and a zero-byte file on the filesystem.
233 0 : FlushPreferences();
234 :
235 : // Create a temporary tombstone/lockfile.
236 0 : FILE* fp = nullptr;
237 0 : mGuardFile = GetGuardFile();
238 0 : if (!mGuardFile || !NS_SUCCEEDED(mGuardFile->OpenANSIFileDesc("w", &fp))) {
239 0 : return;
240 : }
241 0 : fclose(fp);
242 : }
243 : }
244 :
245 : void
246 0 : DriverCrashGuard::NotifyCrashed()
247 : {
248 0 : CheckOrRefreshEnvironment();
249 0 : SetStatus(DriverInitStatus::Crashed);
250 0 : FlushPreferences();
251 0 : LogCrashRecovery();
252 0 : }
253 :
254 : bool
255 0 : DriverCrashGuard::RecoverFromCrash()
256 : {
257 0 : MOZ_ASSERT(XRE_IsParentProcess());
258 :
259 0 : nsCOMPtr<nsIFile> file = GetGuardFile();
260 : bool exists;
261 0 : if ((file &&
262 0 : NS_SUCCEEDED(file->Exists(&exists)) &&
263 0 : exists) ||
264 0 : (GetStatus() == DriverInitStatus::Attempting))
265 : {
266 : // If we get here, we've just recovered from a crash. Disable acceleration
267 : // until the environment changes.
268 0 : if (file) {
269 0 : file->Remove(false);
270 : }
271 0 : NotifyCrashed();
272 0 : return true;
273 : }
274 0 : return false;
275 : }
276 :
277 : // Return true if the caller should proceed to guard for crashes. False if
278 : // the environment has not changed. We persist the "changed" status across
279 : // calls, so that after an environment changes, all guards for the new
280 : // session are activated rather than just the first.
281 : bool
282 0 : DriverCrashGuard::CheckOrRefreshEnvironment()
283 : {
284 : // Our result can be cached statically since we don't check live prefs.
285 : static bool sBaseInfoChanged = false;
286 : static bool sBaseInfoChecked = false;
287 :
288 0 : if (!sBaseInfoChecked) {
289 : // None of the prefs we care about, so we cache the result statically.
290 0 : sBaseInfoChecked = true;
291 0 : sBaseInfoChanged = UpdateBaseEnvironment();
292 : }
293 :
294 : // Always update the full environment, even if the base info didn't change.
295 0 : return UpdateEnvironment() ||
296 0 : sBaseInfoChanged ||
297 0 : GetStatus() == DriverInitStatus::Unknown;
298 : }
299 :
300 : bool
301 0 : DriverCrashGuard::UpdateBaseEnvironment()
302 : {
303 0 : bool changed = false;
304 0 : if (mGfxInfo) {
305 0 : nsString value;
306 :
307 : // Driver properties.
308 0 : mGfxInfo->GetAdapterDriverVersion(value);
309 0 : changed |= CheckAndUpdatePref("driverVersion", value);
310 0 : mGfxInfo->GetAdapterDeviceID(value);
311 0 : changed |= CheckAndUpdatePref("deviceID", value);
312 : }
313 :
314 : // Firefox properties.
315 0 : changed |= CheckAndUpdatePref("appVersion", NS_LITERAL_STRING(MOZ_APP_VERSION));
316 :
317 0 : return changed;
318 : }
319 :
320 : bool
321 0 : DriverCrashGuard::FeatureEnabled(int aFeature, bool aDefault)
322 : {
323 0 : if (!mGfxInfo) {
324 0 : return aDefault;
325 : }
326 : int32_t status;
327 0 : nsCString discardFailureId;
328 0 : if (!NS_SUCCEEDED(mGfxInfo->GetFeatureStatus(aFeature, discardFailureId, &status))) {
329 0 : return false;
330 : }
331 0 : return status == nsIGfxInfo::FEATURE_STATUS_OK;
332 : }
333 :
334 : bool
335 0 : DriverCrashGuard::CheckAndUpdateBoolPref(const char* aPrefName, bool aCurrentValue)
336 : {
337 0 : std::string pref = GetFullPrefName(aPrefName);
338 :
339 : bool oldValue;
340 0 : if (NS_SUCCEEDED(Preferences::GetBool(pref.c_str(), &oldValue)) &&
341 0 : oldValue == aCurrentValue)
342 : {
343 0 : return false;
344 : }
345 0 : Preferences::SetBool(pref.c_str(), aCurrentValue);
346 0 : return true;
347 : }
348 :
349 : bool
350 0 : DriverCrashGuard::CheckAndUpdatePref(const char* aPrefName, const nsAString& aCurrentValue)
351 : {
352 0 : std::string pref = GetFullPrefName(aPrefName);
353 :
354 0 : nsAdoptingString oldValue = Preferences::GetString(pref.c_str());
355 0 : if (oldValue == aCurrentValue) {
356 0 : return false;
357 : }
358 0 : Preferences::SetString(pref.c_str(), aCurrentValue);
359 0 : return true;
360 : }
361 :
362 : std::string
363 0 : DriverCrashGuard::GetFullPrefName(const char* aPref)
364 : {
365 0 : return std::string("gfx.crash-guard.") +
366 0 : std::string(sCrashGuardNames[uint32_t(mType)]) +
367 0 : std::string(".") +
368 0 : std::string(aPref);
369 : }
370 :
371 : DriverInitStatus
372 0 : DriverCrashGuard::GetStatus() const
373 : {
374 0 : return (DriverInitStatus)Preferences::GetInt(mStatusPref.get(), 0);
375 : }
376 :
377 : void
378 0 : DriverCrashGuard::SetStatus(DriverInitStatus aStatus)
379 : {
380 0 : MOZ_ASSERT(XRE_IsParentProcess());
381 :
382 0 : Preferences::SetInt(mStatusPref.get(), int32_t(aStatus));
383 0 : }
384 :
385 : void
386 0 : DriverCrashGuard::FlushPreferences()
387 : {
388 0 : MOZ_ASSERT(XRE_IsParentProcess());
389 :
390 0 : if (nsIPrefService* prefService = Preferences::GetService()) {
391 0 : static_cast<Preferences *>(prefService)->SavePrefFileBlocking();
392 : }
393 0 : }
394 :
395 : void
396 0 : DriverCrashGuard::ForEachActiveCrashGuard(const CrashGuardCallback& aCallback)
397 : {
398 0 : if (!AreCrashGuardsEnabled()) {
399 : // Even if guards look active (via prefs), they can be ignored if globally
400 : // disabled.
401 0 : return;
402 : }
403 :
404 0 : for (size_t i = 0; i < NUM_CRASH_GUARD_TYPES; i++) {
405 0 : CrashGuardType type = static_cast<CrashGuardType>(i);
406 :
407 0 : nsCString prefName;
408 0 : BuildCrashGuardPrefName(type, prefName);
409 :
410 : auto status =
411 0 : static_cast<DriverInitStatus>(Preferences::GetInt(prefName.get(), 0));
412 0 : if (status != DriverInitStatus::Crashed) {
413 0 : continue;
414 : }
415 :
416 0 : aCallback(sCrashGuardNames[i], prefName.get());
417 : }
418 : }
419 :
420 0 : D3D11LayersCrashGuard::D3D11LayersCrashGuard(dom::ContentParent* aContentParent)
421 0 : : DriverCrashGuard(CrashGuardType::D3D11Layers, aContentParent)
422 : {
423 0 : }
424 :
425 : void
426 0 : D3D11LayersCrashGuard::Initialize()
427 : {
428 0 : if (!XRE_IsParentProcess()) {
429 : // We assume the parent process already performed crash detection for
430 : // graphics devices.
431 0 : return;
432 : }
433 :
434 0 : DriverCrashGuard::Initialize();
435 :
436 : // If no telemetry states have been recorded, this will set the state to okay.
437 : // Otherwise, it will have no effect.
438 0 : RecordTelemetry(TelemetryState::Okay);
439 : }
440 :
441 : bool
442 0 : D3D11LayersCrashGuard::UpdateEnvironment()
443 : {
444 : // Our result can be cached statically since we don't check live prefs.
445 : static bool checked = false;
446 : static bool changed = false;
447 :
448 0 : if (checked) {
449 0 : return changed;
450 : }
451 :
452 0 : checked = true;
453 :
454 : // Feature status.
455 : #if defined(XP_WIN)
456 : bool d2dEnabled = gfxPrefs::Direct2DForceEnabled() ||
457 : (!gfxPrefs::Direct2DDisabled() && FeatureEnabled(nsIGfxInfo::FEATURE_DIRECT2D));
458 : changed |= CheckAndUpdateBoolPref("feature-d2d", d2dEnabled);
459 :
460 : bool d3d11Enabled = gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING);
461 : changed |= CheckAndUpdateBoolPref("feature-d3d11", d3d11Enabled);
462 : #endif
463 :
464 0 : if (!changed) {
465 0 : return false;
466 : }
467 :
468 0 : RecordTelemetry(TelemetryState::EnvironmentChanged);
469 0 : return true;
470 : }
471 :
472 : void
473 0 : D3D11LayersCrashGuard::LogCrashRecovery()
474 : {
475 0 : RecordTelemetry(TelemetryState::RecoveredFromCrash);
476 0 : gfxCriticalNote << "D3D11 layers just crashed; D3D11 will be disabled.";
477 0 : }
478 :
479 : void
480 0 : D3D11LayersCrashGuard::LogFeatureDisabled()
481 : {
482 0 : RecordTelemetry(TelemetryState::FeatureDisabled);
483 0 : gfxCriticalNote << "D3D11 layers disabled due to a prior crash.";
484 0 : }
485 :
486 : void
487 0 : D3D11LayersCrashGuard::RecordTelemetry(TelemetryState aState)
488 : {
489 : // D3D11LayersCrashGuard is a no-op in the child process.
490 0 : if (!XRE_IsParentProcess()) {
491 0 : return;
492 : }
493 :
494 : // Since we instantiate this class more than once, make sure we only record
495 : // the first state (since that is really all we care about).
496 : static bool sTelemetryStateRecorded = false;
497 0 : if (sTelemetryStateRecorded) {
498 0 : return;
499 : }
500 :
501 0 : Telemetry::Accumulate(Telemetry::GRAPHICS_DRIVER_STARTUP_TEST, int32_t(aState));
502 0 : sTelemetryStateRecorded = true;
503 : }
504 :
505 0 : D3D9VideoCrashGuard::D3D9VideoCrashGuard(dom::ContentParent* aContentParent)
506 0 : : DriverCrashGuard(CrashGuardType::D3D9Video, aContentParent)
507 : {
508 0 : }
509 :
510 : bool
511 0 : D3D9VideoCrashGuard::UpdateEnvironment()
512 : {
513 : // We don't care about any extra preferences here.
514 0 : return false;
515 : }
516 :
517 : void
518 0 : D3D9VideoCrashGuard::LogCrashRecovery()
519 : {
520 0 : gfxCriticalNote << "DXVA2D3D9 just crashed; hardware video will be disabled.";
521 0 : }
522 :
523 : void
524 0 : D3D9VideoCrashGuard::LogFeatureDisabled()
525 : {
526 0 : gfxCriticalNote << "DXVA2D3D9 video decoding is disabled due to a previous crash.";
527 0 : }
528 :
529 0 : D3D11VideoCrashGuard::D3D11VideoCrashGuard(dom::ContentParent* aContentParent)
530 0 : : DriverCrashGuard(CrashGuardType::D3D11Video, aContentParent)
531 : {
532 0 : }
533 :
534 : bool
535 0 : D3D11VideoCrashGuard::UpdateEnvironment()
536 : {
537 : // We don't care about any extra preferences here.
538 0 : return false;
539 : }
540 :
541 : void
542 0 : D3D11VideoCrashGuard::LogCrashRecovery()
543 : {
544 0 : gfxCriticalNote << "DXVA2D3D11 just crashed; hardware video will be disabled.";
545 0 : }
546 :
547 : void
548 0 : D3D11VideoCrashGuard::LogFeatureDisabled()
549 : {
550 0 : gfxCriticalNote << "DXVA2D3D11 video decoding is disabled due to a previous crash.";
551 0 : }
552 :
553 0 : GLContextCrashGuard::GLContextCrashGuard(dom::ContentParent* aContentParent)
554 0 : : DriverCrashGuard(CrashGuardType::GLContext, aContentParent)
555 : {
556 0 : }
557 :
558 : void
559 0 : GLContextCrashGuard::Initialize()
560 : {
561 0 : if (XRE_IsContentProcess()) {
562 : // Disable the GL crash guard in content processes, since we're not going
563 : // to lose the entire browser and we don't want to hinder WebGL availability.
564 0 : return;
565 : }
566 :
567 : #if defined(MOZ_WIDGET_ANDROID)
568 : // Disable the WebGL crash guard on Android - it doesn't use E10S, and
569 : // its drivers will essentially never change, so the crash guard could
570 : // permanently disable WebGL.
571 : return;
572 : #endif
573 :
574 0 : DriverCrashGuard::Initialize();
575 : }
576 :
577 : bool
578 0 : GLContextCrashGuard::UpdateEnvironment()
579 : {
580 : static bool checked = false;
581 : static bool changed = false;
582 :
583 0 : if (checked) {
584 0 : return changed;
585 : }
586 :
587 0 : checked = true;
588 :
589 : #if defined(XP_WIN)
590 : changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-force-d3d11",
591 : gfxPrefs::WebGLANGLEForceD3D11());
592 : changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-try-d3d11",
593 : gfxPrefs::WebGLANGLETryD3D11());
594 : changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-force-warp",
595 : gfxPrefs::WebGLANGLEForceWARP());
596 : changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle",
597 : FeatureEnabled(nsIGfxInfo::FEATURE_WEBGL_ANGLE, false));
598 : changed |= CheckAndUpdateBoolPref("gfx.driver-init.direct3d11-angle",
599 : FeatureEnabled(nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE, false));
600 : #endif
601 :
602 0 : return changed;
603 : }
604 :
605 : void
606 0 : GLContextCrashGuard::LogCrashRecovery()
607 : {
608 0 : gfxCriticalNote << "GLContext just crashed.";
609 0 : }
610 :
611 : void
612 0 : GLContextCrashGuard::LogFeatureDisabled()
613 : {
614 0 : gfxCriticalNote << "GLContext remains enabled despite a previous crash.";
615 0 : }
616 :
617 : } // namespace gfx
618 : } // namespace mozilla
|