tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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