DriverCrashGuard.cpp (16351B)
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" 7 #include "gfxEnv.h" 8 #include "gfxConfig.h" 9 #include "nsAppDirectoryServiceDefs.h" 10 #include "nsDirectoryServiceUtils.h" 11 #include "nsExceptionHandler.h" 12 #include "nsServiceManagerUtils.h" 13 #include "nsString.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/glean/GfxMetrics.h" 19 #include "mozilla/Components.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 "glcontext", 30 "wmfvpxvideo", 31 }; 32 static_assert(std::size(sCrashGuardNames) == NUM_CRASH_GUARD_TYPES, 33 "CrashGuardType updated without a name string"); 34 35 static inline void BuildCrashGuardPrefName(CrashGuardType aType, 36 nsCString& aOutPrefName) { 37 MOZ_ASSERT(aType < CrashGuardType::NUM_TYPES); 38 MOZ_ASSERT(sCrashGuardNames[size_t(aType)]); 39 40 aOutPrefName.AssignLiteral("gfx.crash-guard.status."); 41 aOutPrefName.Append(sCrashGuardNames[size_t(aType)]); 42 } 43 44 DriverCrashGuard::DriverCrashGuard(CrashGuardType aType, 45 dom::ContentParent* aContentParent) 46 : mType(aType), 47 mMode(aContentParent ? Mode::Proxy : Mode::Normal), 48 mInitialized(false), 49 mGuardActivated(false), 50 mCrashDetected(false) { 51 BuildCrashGuardPrefName(aType, mStatusPref); 52 } 53 54 void DriverCrashGuard::InitializeIfNeeded() { 55 if (mInitialized) { 56 return; 57 } 58 59 mInitialized = true; 60 Initialize(); 61 } 62 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()) { 67 return false; 68 } 69 #ifdef NIGHTLY_BUILD 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(); 78 } 79 #endif 80 // Check to see if all guards have been disabled through the environment. 81 return !gfxEnv::MOZ_DISABLE_CRASH_GUARD(); 82 } 83 84 void DriverCrashGuard::Initialize() { 85 if (!AreCrashGuardsEnabled(mType)) { 86 return; 87 } 88 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()) { 94 return; 95 } 96 97 mGfxInfo = components::GfxInfo::Service(); 98 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(); 106 return; 107 } 108 109 ActivateGuard(); 110 return; 111 } 112 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; 118 return; 119 } 120 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 125 // crash. 126 if (CheckOrRefreshEnvironment() || 127 (mMode == Mode::Proxy && GetStatus() != DriverInitStatus::Crashed)) { 128 ActivateGuard(); 129 return; 130 } 131 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(); 137 } 138 } 139 140 DriverCrashGuard::~DriverCrashGuard() { 141 if (!mGuardActivated) { 142 return; 143 } 144 145 if (XRE_IsParentProcess()) { 146 if (mGuardFile) { 147 mGuardFile->Remove(false); 148 } 149 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); 154 } 155 } else { 156 dom::ContentChild::GetSingleton()->SendEndDriverCrashGuard(uint32_t(mType)); 157 } 158 159 CrashReporter::UnrecordAnnotation( 160 CrashReporter::Annotation::GraphicsStartupTest); 161 } 162 163 bool DriverCrashGuard::Crashed() { 164 InitializeIfNeeded(); 165 166 // Note, we read mCrashDetected instead of GetStatus(), since in child 167 // processes we're not guaranteed that the prefs have been synced in 168 // time. 169 return mCrashDetected; 170 } 171 172 nsCOMPtr<nsIFile> DriverCrashGuard::GetGuardFile() { 173 MOZ_ASSERT(XRE_IsParentProcess()); 174 175 nsCString filename; 176 filename.Assign(sCrashGuardNames[size_t(mType)]); 177 filename.AppendLiteral(".guard"); 178 179 nsCOMPtr<nsIFile> file; 180 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, 181 getter_AddRefs(file)); 182 if (!file) { 183 return nullptr; 184 } 185 if (!NS_SUCCEEDED(file->AppendNative(filename))) { 186 return nullptr; 187 } 188 return file; 189 } 190 191 void DriverCrashGuard::ActivateGuard() { 192 mGuardActivated = true; 193 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 196 // process. 197 if (mMode != Mode::Proxy) { 198 CrashReporter::RecordAnnotationBool( 199 CrashReporter::Annotation::GraphicsStartupTest, true); 200 } 201 202 // If we're in the content process, the rest of the guarding is handled 203 // in the parent. 204 if (XRE_IsContentProcess()) { 205 return; 206 } 207 208 SetStatus(DriverInitStatus::Attempting); 209 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. 213 FlushPreferences(); 214 215 // Create a temporary tombstone/lockfile. 216 FILE* fp = nullptr; 217 mGuardFile = GetGuardFile(); 218 if (!mGuardFile || !NS_SUCCEEDED(mGuardFile->OpenANSIFileDesc("w", &fp))) { 219 return; 220 } 221 fclose(fp); 222 } 223 } 224 225 void DriverCrashGuard::NotifyCrashed() { 226 SetStatus(DriverInitStatus::Crashed); 227 FlushPreferences(); 228 LogCrashRecovery(); 229 } 230 231 bool DriverCrashGuard::RecoverFromCrash() { 232 MOZ_ASSERT(XRE_IsParentProcess()); 233 234 nsCOMPtr<nsIFile> file = GetGuardFile(); 235 bool exists; 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. 240 if (file) { 241 file->Remove(false); 242 } 243 NotifyCrashed(); 244 return true; 245 } 246 return false; 247 } 248 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]; 260 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(); 266 } 267 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; 272 return result; 273 } 274 275 bool DriverCrashGuard::UpdateBaseEnvironment() { 276 bool changed = false; 277 if (mGfxInfo) { 278 nsString value; 279 280 // Driver properties. 281 mGfxInfo->GetAdapterDriverVersion(value); 282 changed |= CheckAndUpdatePref("driverVersion", value); 283 mGfxInfo->GetAdapterDeviceID(value); 284 changed |= CheckAndUpdatePref("deviceID", value); 285 } 286 287 // Firefox properties. 288 changed |= CheckAndUpdatePref( 289 "appVersion", NS_LITERAL_STRING_FROM_CSTRING(MOZ_APP_VERSION)); 290 291 return changed; 292 } 293 294 bool DriverCrashGuard::FeatureEnabled(int aFeature, bool aDefault) { 295 if (!mGfxInfo) { 296 return aDefault; 297 } 298 int32_t status; 299 nsCString discardFailureId; 300 if (!NS_SUCCEEDED( 301 mGfxInfo->GetFeatureStatus(aFeature, discardFailureId, &status))) { 302 return false; 303 } 304 return status == nsIGfxInfo::FEATURE_STATUS_OK; 305 } 306 307 bool DriverCrashGuard::CheckAndUpdateBoolPref(const char* aPrefName, 308 bool aCurrentValue) { 309 std::string pref = GetFullPrefName(aPrefName); 310 311 bool oldValue; 312 if (NS_SUCCEEDED(Preferences::GetBool(pref.c_str(), &oldValue)) && 313 oldValue == aCurrentValue) { 314 return false; 315 } 316 Preferences::SetBool(pref.c_str(), aCurrentValue); 317 return true; 318 } 319 320 bool DriverCrashGuard::CheckAndUpdatePref(const char* aPrefName, 321 const nsAString& aCurrentValue) { 322 std::string pref = GetFullPrefName(aPrefName); 323 324 nsAutoString oldValue; 325 Preferences::GetString(pref.c_str(), oldValue); 326 if (oldValue == aCurrentValue) { 327 return false; 328 } 329 Preferences::SetString(pref.c_str(), aCurrentValue); 330 return true; 331 } 332 333 std::string DriverCrashGuard::GetFullPrefName(const char* aPref) { 334 return std::string("gfx.crash-guard.") + 335 std::string(sCrashGuardNames[uint32_t(mType)]) + std::string(".") + 336 std::string(aPref); 337 } 338 339 DriverInitStatus DriverCrashGuard::GetStatus() const { 340 return (DriverInitStatus)Preferences::GetInt(mStatusPref.get(), 0); 341 } 342 343 void DriverCrashGuard::SetStatus(DriverInitStatus aStatus) { 344 MOZ_ASSERT(XRE_IsParentProcess()); 345 346 Preferences::SetInt(mStatusPref.get(), int32_t(aStatus)); 347 } 348 349 void DriverCrashGuard::FlushPreferences() { 350 MOZ_ASSERT(XRE_IsParentProcess()); 351 352 if (nsIPrefService* prefService = Preferences::GetService()) { 353 static_cast<Preferences*>(prefService)->SavePrefFileBlocking(); 354 } 355 } 356 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); 361 362 if (!AreCrashGuardsEnabled(type)) { 363 // Even if guards look active (via prefs), they can be ignored if globally 364 // disabled. 365 continue; 366 } 367 368 nsCString prefName; 369 BuildCrashGuardPrefName(type, prefName); 370 371 auto status = 372 static_cast<DriverInitStatus>(Preferences::GetInt(prefName.get(), 0)); 373 if (status != DriverInitStatus::Crashed) { 374 continue; 375 } 376 377 aCallback(sCrashGuardNames[i], prefName.get()); 378 } 379 } 380 381 D3D11LayersCrashGuard::D3D11LayersCrashGuard(dom::ContentParent* aContentParent) 382 : DriverCrashGuard(CrashGuardType::D3D11Layers, aContentParent) {} 383 384 void D3D11LayersCrashGuard::Initialize() { 385 if (!XRE_IsParentProcess()) { 386 // We assume the parent process already performed crash detection for 387 // graphics devices. 388 return; 389 } 390 391 DriverCrashGuard::Initialize(); 392 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); 396 } 397 398 bool D3D11LayersCrashGuard::UpdateEnvironment() { 399 // Our result can be cached statically since we don't check live prefs. 400 static bool checked = false; 401 402 if (checked) { 403 // We no longer need to bypass the crash guard. 404 return false; 405 } 406 407 checked = true; 408 409 bool changed = false; 410 // Feature status. 411 #if defined(XP_WIN) 412 bool d3d11Enabled = gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING); 413 changed |= CheckAndUpdateBoolPref("feature-d3d11", d3d11Enabled); 414 if (changed) { 415 RecordTelemetry(TelemetryState::EnvironmentChanged); 416 } 417 #endif 418 419 return changed; 420 } 421 422 void D3D11LayersCrashGuard::LogCrashRecovery() { 423 RecordTelemetry(TelemetryState::RecoveredFromCrash); 424 gfxCriticalNote << "D3D11 layers just crashed; D3D11 will be disabled."; 425 } 426 427 void D3D11LayersCrashGuard::LogFeatureDisabled() { 428 RecordTelemetry(TelemetryState::FeatureDisabled); 429 gfxCriticalNote << "D3D11 layers disabled due to a prior crash."; 430 } 431 432 void D3D11LayersCrashGuard::RecordTelemetry(TelemetryState aState) { 433 // D3D11LayersCrashGuard is a no-op in the child process. 434 if (!XRE_IsParentProcess()) { 435 return; 436 } 437 438 // Since we instantiate this class more than once, make sure we only record 439 // the first state (since that is really all we care about). 440 static bool sTelemetryStateRecorded = false; 441 if (sTelemetryStateRecorded) { 442 return; 443 } 444 445 glean::gfx::graphics_driver_startup_test.AccumulateSingleSample( 446 int32_t(aState)); 447 sTelemetryStateRecorded = true; 448 } 449 450 GLContextCrashGuard::GLContextCrashGuard(dom::ContentParent* aContentParent) 451 : DriverCrashGuard(CrashGuardType::GLContext, aContentParent) {} 452 453 void GLContextCrashGuard::Initialize() { 454 if (XRE_IsContentProcess()) { 455 // Disable the GL crash guard in content processes, since we're not going 456 // to lose the entire browser and we don't want to hinder WebGL 457 // availability. 458 return; 459 } 460 461 #if defined(MOZ_WIDGET_ANDROID) 462 // Disable the WebGL crash guard on Android - it doesn't use E10S, and 463 // its drivers will essentially never change, so the crash guard could 464 // permanently disable WebGL. 465 return; 466 #else 467 DriverCrashGuard::Initialize(); 468 #endif 469 } 470 471 bool GLContextCrashGuard::UpdateEnvironment() { 472 static bool checked = false; 473 474 if (checked) { 475 // We no longer need to bypass the crash guard. 476 return false; 477 } 478 479 checked = true; 480 481 bool changed = false; 482 483 #if defined(XP_WIN) 484 changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-force-d3d11", 485 StaticPrefs::webgl_angle_force_d3d11()); 486 changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-try-d3d11", 487 StaticPrefs::webgl_angle_try_d3d11()); 488 changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-force-warp", 489 StaticPrefs::webgl_angle_force_warp()); 490 changed |= CheckAndUpdateBoolPref( 491 "gfx.driver-init.webgl-angle", 492 FeatureEnabled(nsIGfxInfo::FEATURE_WEBGL_ANGLE, false)); 493 changed |= CheckAndUpdateBoolPref( 494 "gfx.driver-init.direct3d11-angle", 495 FeatureEnabled(nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE, false)); 496 #endif 497 498 return changed; 499 } 500 501 void GLContextCrashGuard::LogCrashRecovery() { 502 gfxCriticalNote << "GLContext just crashed."; 503 } 504 505 void GLContextCrashGuard::LogFeatureDisabled() { 506 gfxCriticalNote << "GLContext remains enabled despite a previous crash."; 507 } 508 509 WMFVPXVideoCrashGuard::WMFVPXVideoCrashGuard(dom::ContentParent* aContentParent) 510 : DriverCrashGuard(CrashGuardType::WMFVPXVideo, aContentParent) {} 511 512 void WMFVPXVideoCrashGuard::LogCrashRecovery() { 513 gfxCriticalNote 514 << "WMF VPX decoder just crashed; hardware video will be disabled."; 515 } 516 517 void WMFVPXVideoCrashGuard::LogFeatureDisabled() { 518 gfxCriticalNote 519 << "WMF VPX video decoding is disabled due to a previous crash."; 520 } 521 522 } // namespace gfx 523 } // namespace mozilla