Bug 902587 - Part 2A: Refactor late-write-checks as client of IO Interposer r=BenWa
☠☠ backed out by 429806586db9 ☠ ☠
authorJonas Finnemann Jensen <jopsen@gmail.com>
Thu, 24 Oct 2013 17:02:00 +0100
changeset 167009 2d6c5cbcc2138976abce2b1431ad893c9c3f0f08
parent 167008 c1ecd3ac0f716fe7999b0beea9a3c7adb07b2a8d
child 167010 e43b02e631f068ac6e6ca87bed9fab0791a88f3d
push id428
push userbbajaj@mozilla.com
push dateTue, 28 Jan 2014 00:16:25 +0000
treeherdermozilla-release@cd72a7ff3a75 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersBenWa
bugs902587
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 902587 - Part 2A: Refactor late-write-checks as client of IO Interposer r=BenWa
browser/app/nsBrowserApp.cpp
media/mtransport/test/mtransport_test_utils.h
storage/src/mozStorageService.cpp
toolkit/components/startup/nsAppStartup.cpp
toolkit/components/telemetry/Telemetry.cpp
toolkit/crashreporter/nsExceptionHandler.cpp
toolkit/xre/nsAppRunner.cpp
tools/trace-malloc/lib/nsTraceMalloc.c
xpcom/base/nsCycleCollector.cpp
xpcom/base/nsTraceRefcntImpl.cpp
xpcom/build/LateWriteChecks.cpp
xpcom/build/LateWriteChecks.h
xpcom/build/PoisonIOInterposer.h
xpcom/build/PoisonIOInterposerBase.cpp
xpcom/build/PoisonIOInterposerMac.cpp
xpcom/build/PoisonIOInterposerWin.cpp
xpcom/build/moz.build
xpcom/build/mozPoisonWrite.h
xpcom/build/mozPoisonWriteBase.cpp
xpcom/build/mozPoisonWriteBase.h
xpcom/build/mozPoisonWriteMac.cpp
xpcom/build/mozPoisonWriteStub.cpp
xpcom/build/mozPoisonWriteWin.cpp
xpcom/build/nsXPComInit.cpp
xpcom/build/nsXULAppAPI.h
--- a/browser/app/nsBrowserApp.cpp
+++ b/browser/app/nsBrowserApp.cpp
@@ -145,29 +145,29 @@ XRE_GetFileFromPathType XRE_GetFileFromP
 XRE_CreateAppDataType XRE_CreateAppData;
 XRE_FreeAppDataType XRE_FreeAppData;
 #ifdef XRE_HAS_DLL_BLOCKLIST
 XRE_SetupDllBlocklistType XRE_SetupDllBlocklist;
 #endif
 XRE_TelemetryAccumulateType XRE_TelemetryAccumulate;
 XRE_StartupTimelineRecordType XRE_StartupTimelineRecord;
 XRE_mainType XRE_main;
-XRE_DisableWritePoisoningType XRE_DisableWritePoisoning;
+XRE_StopLateWriteChecksType XRE_StopLateWriteChecks;
 
 static const nsDynamicFunctionLoad kXULFuncs[] = {
     { "XRE_GetFileFromPath", (NSFuncPtr*) &XRE_GetFileFromPath },
     { "XRE_CreateAppData", (NSFuncPtr*) &XRE_CreateAppData },
     { "XRE_FreeAppData", (NSFuncPtr*) &XRE_FreeAppData },
 #ifdef XRE_HAS_DLL_BLOCKLIST
     { "XRE_SetupDllBlocklist", (NSFuncPtr*) &XRE_SetupDllBlocklist },
 #endif
     { "XRE_TelemetryAccumulate", (NSFuncPtr*) &XRE_TelemetryAccumulate },
     { "XRE_StartupTimelineRecord", (NSFuncPtr*) &XRE_StartupTimelineRecord },
     { "XRE_main", (NSFuncPtr*) &XRE_main },
-    { "XRE_DisableWritePoisoning", (NSFuncPtr*) &XRE_DisableWritePoisoning },
+    { "XRE_StopLateWriteChecks", (NSFuncPtr*) &XRE_StopLateWriteChecks },
     { nullptr, nullptr }
 };
 
 static int do_main(int argc, char* argv[], nsIFile *xreDirectory)
 {
   nsCOMPtr<nsIFile> appini;
   nsresult rv;
   uint32_t mainFlags = 0;
@@ -637,13 +637,13 @@ int main(int argc, char* argv[])
   NS_LogTerm();
 
 #ifdef XP_MACOSX
   // Allow writes again. While we would like to catch writes from static
   // destructors to allow early exits to use _exit, we know that there is
   // at least one such write that we don't control (see bug 826029). For
   // now we enable writes again and early exits will have to use exit instead
   // of _exit.
-  XRE_DisableWritePoisoning();
+  XRE_StopLateWriteChecks();
 #endif
 
   return result;
 }
--- a/media/mtransport/test/mtransport_test_utils.h
+++ b/media/mtransport/test/mtransport_test_utils.h
@@ -24,17 +24,16 @@
 #include "nsDirectoryServiceUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #ifdef MOZ_CRASHREPORTER
 #include "nsICrashReporter.h"
 #endif
 #include "nsPISocketTransportService.h"
 #include "nsServiceManagerUtils.h"
 #include "TestHarness.h"
-#include "mozilla/mozPoisonWrite.h"
 
 class MtransportTestUtils {
  public:
   MtransportTestUtils() : xpcom_("") {
     if (!sts_) {
       InitServices();
     }
   }
--- a/storage/src/mozStorageService.cpp
+++ b/storage/src/mozStorageService.cpp
@@ -17,17 +17,17 @@
 #include "mozStoragePrivateHelpers.h"
 #include "nsILocale.h"
 #include "nsILocaleService.h"
 #include "nsIXPConnect.h"
 #include "nsIObserverService.h"
 #include "nsIPropertyBag2.h"
 #include "mozilla/Services.h"
 #include "mozilla/Preferences.h"
-#include "mozilla/mozPoisonWrite.h"
+#include "mozilla/LateWriteChecks.h"
 #include "mozIStorageCompletionCallback.h"
 
 #include "sqlite3.h"
 
 #ifdef SQLITE_OS_WIN
 // "windows.h" was included and it can #define lots of things we care about...
 #undef CompareString
 #endif
--- a/toolkit/components/startup/nsAppStartup.cpp
+++ b/toolkit/components/startup/nsAppStartup.cpp
@@ -32,17 +32,16 @@
 #include "nsWidgetsCID.h"
 #include "nsAppShellCID.h"
 #include "nsXPCOMCIDInternal.h"
 #include "mozilla/Services.h"
 #include "nsIXPConnect.h"
 #include "jsapi.h"
 #include "prenv.h"
 #include "nsAppDirectoryServiceDefs.h"
-#include "mozilla/mozPoisonWrite.h"
 
 #if defined(XP_WIN)
 // Prevent collisions with nsAppStartup::GetStartupInfo()
 #undef GetStartupInfo
 #endif
 
 #include "mozilla/Telemetry.h"
 #include "mozilla/StartupTimeline.h"
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -44,17 +44,17 @@
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "plstr.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "mozilla/ProcessedStack.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/Preferences.h"
-#include "mozilla/mozPoisonWrite.h"
+#include "mozilla/PoisonIOInterposer.h"
 #if defined(MOZ_ENABLE_PROFILER_SPS)
 #include "shared-libraries.h"
 #endif
 
 namespace {
 
 using namespace base;
 using namespace mozilla;
--- a/toolkit/crashreporter/nsExceptionHandler.cpp
+++ b/toolkit/crashreporter/nsExceptionHandler.cpp
@@ -77,17 +77,17 @@ using mozilla::InjectCrashRunnable;
 #include "nsDebug.h"
 #include "nsCRT.h"
 #include "nsIFile.h"
 #include "prprf.h"
 #include <map>
 #include <vector>
 
 #include "mozilla/mozalloc_oom.h"
-#include "mozilla/mozPoisonWrite.h"
+#include "mozilla/LateWriteChecks.h"
 
 #if defined(XP_MACOSX)
 CFStringRef reporterClientAppID = CFSTR("org.mozilla.crashreporter");
 #endif
 #if defined(MOZ_WIDGET_ANDROID)
 #include "common/linux/file_id.h"
 #endif
 
@@ -787,17 +787,17 @@ static bool ShouldReport()
     return false;
   }
 
   return true;
 }
 
 namespace {
   bool Filter(void* context) {
-    mozilla::DisableWritePoisoning();
+    mozilla::StopLateWriteChecks();
     return true;
   }
 }
 
 
 nsresult SetExceptionHandler(nsIFile* aXREDirectory,
                              bool force/*=false*/)
 {
@@ -2229,17 +2229,17 @@ OnChildProcessDumpRequested(void* aConte
 static bool
 OOPInitialized()
 {
   return pidToMinidump != nullptr;
 }
 
 #ifdef XP_MACOSX
 static bool ChildFilter(void *context) {
-  mozilla::DisableWritePoisoning();
+  mozilla::StopLateWriteChecks();
   return true;
 }
 #endif
 
 void
 OOPInit()
 {
   if (OOPInitialized())
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -124,17 +124,17 @@
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsXULAppAPI.h"
 #include "nsXREDirProvider.h"
 #include "nsToolkitCompsCID.h"
 
 #include "nsINIParser.h"
 #include "mozilla/Omnijar.h"
 #include "mozilla/StartupTimeline.h"
-#include "mozilla/mozPoisonWrite.h"
+#include "mozilla/LateWriteChecks.h"
 
 #include <stdlib.h>
 
 // for old system jemalloc version check
 #if !defined(MOZ_MEMORY) && defined(__NetBSD__)
 #include <sys/param.h>
 #endif
 
@@ -3956,20 +3956,20 @@ XREMain::XRE_main(int argc, char* argv[]
 #ifdef MOZ_INSTRUMENT_EVENT_LOOP
   mozilla::ShutdownEventTracing();
 #endif
 
   // Check for an application initiated restart.  This is one that
   // corresponds to nsIAppStartup.quit(eRestart)
   if (rv == NS_SUCCESS_RESTART_APP) {
     appInitiatedRestart = true;
-  } else {
-    // We will have a real shutdown, let ShutdownXPCOM poison writes to
-    // find any late ones.
-    mozilla::EnableWritePoisoning();
+
+    // We have an application restart don't do any shutdown checks here
+    // In particular we don't want to poison IO for checking late-writes.
+    gShutdownChecks = SCM_NOTHING;
   }
 
   if (!mShuttingDown) {
 #ifdef MOZ_ENABLE_XREMOTE
     // shut down the x remote proxy window
     if (mRemoteService) {
       mRemoteService->Shutdown();
     }
@@ -4139,18 +4139,18 @@ XRE_mainMetro(int argc, char* argv[], co
                "XPCOM Shutdown hasn't occured, and we are exiting.");
   return 0;
 }
 
 void SetWindowsEnvironment(WindowsEnvironmentType aEnvID);
 #endif // MOZ_METRO || !defined(XP_WIN)
 
 void
-XRE_DisableWritePoisoning(void) {
-  mozilla::DisableWritePoisoning();
+XRE_StopLateWriteChecks(void) {
+  mozilla::StopLateWriteChecks();
 }
 
 int
 XRE_main(int argc, char* argv[], const nsXREAppData* aAppData, uint32_t aFlags)
 {
 #if !defined(MOZ_METRO) || !defined(XP_WIN)
   XREMain main;
   int result = main.XRE_main(argc, argv, aAppData);
--- a/tools/trace-malloc/lib/nsTraceMalloc.c
+++ b/tools/trace-malloc/lib/nsTraceMalloc.c
@@ -30,17 +30,17 @@
 #include "nsTraceMalloc.h"
 #include "nscore.h"
 #include "prinit.h"
 #include "prthread.h"
 #include "plstr.h"
 #include "nsStackWalk.h"
 #include "nsTraceMallocCallbacks.h"
 #include "nsTypeInfo.h"
-#include "mozilla/mozPoisonWrite.h"
+#include "mozilla/PoisonIOInterposer.h"
 
 #if defined(XP_MACOSX)
 
 #include <malloc/malloc.h>
 
 #define WRITE_FLAGS "w"
 
 #define __libc_malloc(x)                malloc(x)
--- a/xpcom/base/nsCycleCollector.cpp
+++ b/xpcom/base/nsCycleCollector.cpp
@@ -118,17 +118,17 @@
 #include "nsIFile.h"
 #include "nsMemoryInfoDumper.h"
 #include "xpcpublic.h"
 #include "GeckoProfiler.h"
 #include <stdint.h>
 #include <stdio.h>
 
 #include "mozilla/Likely.h"
-#include "mozilla/mozPoisonWrite.h"
+#include "mozilla/PoisonIOInterposer.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/ThreadLocal.h"
 
 using namespace mozilla;
 
 //#define COLLECT_TIME_DEBUG
 
 // Enable assertions that are useful for diagnosing errors in graph construction.
--- a/xpcom/base/nsTraceRefcntImpl.cpp
+++ b/xpcom/base/nsTraceRefcntImpl.cpp
@@ -26,17 +26,17 @@
 #include <unistd.h>
 #endif
 
 #ifdef NS_TRACE_MALLOC
 #include "nsTraceMalloc.h"
 #endif
 
 #include "mozilla/BlockingResourceBase.h"
-#include "mozilla/mozPoisonWrite.h"
+#include "mozilla/PoisonIOInterposer.h"
 
 #ifdef HAVE_DLOPEN
 #include <dlfcn.h>
 #endif
 
 ////////////////////////////////////////////////////////////////////////////////
 
 void
new file mode 100644
--- /dev/null
+++ b/xpcom/build/LateWriteChecks.cpp
@@ -0,0 +1,248 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 ci et: */
+/* 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 <algorithm>
+
+#include "mozilla/IOInterposer.h"
+#include "mozilla/PoisonIOInterposer.h"
+#include "mozilla/ProcessedStack.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/Scoped.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Telemetry.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsPrintfCString.h"
+#include "nsStackWalk.h"
+#include "plstr.h"
+
+#ifdef XP_WIN
+#define NS_T(str) L ## str
+#define NS_SLASH "\\"
+#include <fcntl.h>
+#include <io.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <windows.h>
+#else
+#define NS_SLASH "/"
+#endif
+
+#include "LateWriteChecks.h"
+
+using namespace mozilla;
+
+/*************************** Auxiliary Declarations ***************************/
+
+// This a wrapper over a file descriptor that provides a Printf method and
+// computes the sha1 of the data that passes through it.
+class SHA1Stream
+{
+public:
+    explicit SHA1Stream(FILE *stream)
+      : mFile(stream)
+    {
+      MozillaRegisterDebugFILE(mFile);
+    }
+
+    void Printf(const char *aFormat, ...)
+    {
+        MOZ_ASSERT(mFile);
+        va_list list;
+        va_start(list, aFormat);
+        nsAutoCString str;
+        str.AppendPrintf(aFormat, list);
+        va_end(list);
+        mSHA1.update(str.get(), str.Length());
+        fwrite(str.get(), 1, str.Length(), mFile);
+    }
+    void Finish(SHA1Sum::Hash &aHash)
+    {
+        int fd = fileno(mFile);
+        fflush(mFile);
+        MozillaUnRegisterDebugFD(fd);
+        fclose(mFile);
+        mSHA1.finish(aHash);
+        mFile = NULL;
+    }
+private:
+    FILE *mFile;
+    SHA1Sum mSHA1;
+};
+
+static void RecordStackWalker(void *aPC, void *aSP, void *aClosure)
+{
+    std::vector<uintptr_t> *stack =
+        static_cast<std::vector<uintptr_t>*>(aClosure);
+    stack->push_back(reinterpret_cast<uintptr_t>(aPC));
+}
+
+/**************************** Late-Write Observer  ****************************/
+
+/**
+ * An implementation of IOInterposeObserver to be registered with IOInterposer.
+ * This observer logs all writes as late writes.
+ */
+class LateWriteObserver MOZ_FINAL : public IOInterposeObserver
+{
+public:
+  LateWriteObserver(const char* aProfileDirectory)
+    : mProfileDirectory(PL_strdup(aProfileDirectory))
+  {
+  }
+  ~LateWriteObserver() {
+    PL_strfree(mProfileDirectory);
+    mProfileDirectory = nullptr;
+  }
+
+  void Observe(IOInterposeObserver::Observation& aObservation);
+private:
+  char* mProfileDirectory;
+};
+
+void LateWriteObserver::Observe(IOInterposeObserver::Observation& aOb)
+{
+  // Crash if that is the shutdown check mode
+  if (gShutdownChecks == SCM_CRASH) {
+    MOZ_CRASH();
+  }
+
+  // If we have shutdown mode SCM_NOTHING or we can't record then abort
+  if (gShutdownChecks == SCM_NOTHING || !Telemetry::CanRecord()) {
+    return;
+  }
+
+  // Write the stack and loaded libraries to a file. We can get here
+  // concurrently from many writes, so we use multiple temporary files.
+  std::vector<uintptr_t> rawStack;
+
+  NS_StackWalk(RecordStackWalker, /* skipFrames */ 0, /* maxFrames */ 0,
+               reinterpret_cast<void*>(&rawStack), 0, nullptr);
+  Telemetry::ProcessedStack stack = Telemetry::GetStackAndModules(rawStack);
+
+  nsPrintfCString nameAux("%s%s%s", mProfileDirectory,
+                          NS_SLASH, "Telemetry.LateWriteTmpXXXXXX");
+  char *name;
+  nameAux.GetMutableData(&name);
+
+  // We want the sha1 of the entire file, so please don't write to fd
+  // directly; use sha1Stream.
+  FILE *stream;
+#ifdef XP_WIN
+  HANDLE hFile;
+  do {
+    // mkstemp isn't supported so keep trying until we get a file
+    int result = _mktemp_s(name, strlen(name) + 1);
+    hFile = CreateFileA(name, GENERIC_WRITE, 0, NULL, CREATE_NEW,
+                        FILE_ATTRIBUTE_NORMAL, NULL);
+  } while (GetLastError() == ERROR_FILE_EXISTS);
+
+  if (hFile == INVALID_HANDLE_VALUE) {
+    NS_RUNTIMEABORT("Um, how did we get here?");
+  }
+
+  // http://support.microsoft.com/kb/139640
+  int fd = _open_osfhandle((intptr_t)hFile, _O_APPEND);
+  if (fd == -1) {
+    NS_RUNTIMEABORT("Um, how did we get here?");
+  }
+
+  stream = _fdopen(fd, "w");
+#else
+  int fd = mkstemp(name);
+  stream = fdopen(fd, "w");
+#endif
+
+  SHA1Stream sha1Stream(stream);
+
+  size_t numModules = stack.GetNumModules();
+  sha1Stream.Printf("%u\n", (unsigned)numModules);
+  for (size_t i = 0; i < numModules; ++i) {
+    Telemetry::ProcessedStack::Module module = stack.GetModule(i);
+    sha1Stream.Printf("%s %s\n", module.mBreakpadId.c_str(),
+                      module.mName.c_str());
+  }
+
+  size_t numFrames = stack.GetStackSize();
+  sha1Stream.Printf("%u\n", (unsigned)numFrames);
+  for (size_t i = 0; i < numFrames; ++i) {
+    const Telemetry::ProcessedStack::Frame &frame =
+        stack.GetFrame(i);
+    // NOTE: We write the offsets, while the atos tool expects a value with
+    // the virtual address added. For example, running otool -l on the the firefox
+    // binary shows
+    //      cmd LC_SEGMENT_64
+    //      cmdsize 632
+    //      segname __TEXT
+    //      vmaddr 0x0000000100000000
+    // so to print the line matching the offset 123 one has to run
+    // atos -o firefox 0x100000123.
+    sha1Stream.Printf("%d %x\n", frame.mModIndex, (unsigned)frame.mOffset);
+  }
+
+  SHA1Sum::Hash sha1;
+  sha1Stream.Finish(sha1);
+
+  // Note: These files should be deleted by telemetry once it reads them. If
+  // there were no telemetry runs by the time we shut down, we just add files
+  // to the existing ones instead of replacing them. Given that each of these
+  // files is a bug to be fixed, that is probably the right thing to do.
+
+  // We append the sha1 of the contents to the file name. This provides a simple
+  // client side deduplication.
+  nsPrintfCString finalName("%s%s", mProfileDirectory,
+                            "/Telemetry.LateWriteFinal-");
+  for (int i = 0; i < 20; ++i) {
+    finalName.AppendPrintf("%02x", sha1[i]);
+  }
+  PR_Delete(finalName.get());
+  PR_Rename(name, finalName.get());
+}
+
+/******************************* Setup/Teardown *******************************/
+
+static StaticAutoPtr<LateWriteObserver> sLateWriteObserver;
+
+namespace mozilla{
+
+void InitLateWriteChecks()
+{
+  nsCOMPtr<nsIFile> mozFile;
+  NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mozFile));
+  if (mozFile) {
+    nsAutoCString nativePath;
+    nsresult rv = mozFile->GetNativePath(nativePath);
+    if (NS_SUCCEEDED(rv) && nativePath.get()) {
+      sLateWriteObserver = new LateWriteObserver(nativePath.get());
+    }
+  }
+}
+
+void BeginLateWriteChecks()
+{
+  if (sLateWriteObserver) {
+    IOInterposer::Register(
+      IOInterposeObserver::OpWriteFSync,
+      sLateWriteObserver
+    );
+  }
+}
+
+void StopLateWriteChecks()
+{
+  if (sLateWriteObserver) {
+    IOInterposer::Unregister(
+      IOInterposeObserver::OpAll,
+      sLateWriteObserver
+    );
+    // Deallocation would not be thread-safe, and StopLateWriteChecks() is
+    // called at shutdown and only in special cases.
+    // sLateWriteObserver = nullptr;
+  }
+}
+
+} // namespace mozilla
rename from xpcom/build/mozPoisonWrite.h
rename to xpcom/build/LateWriteChecks.h
--- a/xpcom/build/mozPoisonWrite.h
+++ b/xpcom/build/LateWriteChecks.h
@@ -1,36 +1,59 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* vim:set ts=4 sw=4 sts=4 ci et: */
 /* 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/. */
 
-#ifndef MOZPOISONWRITE_H
-#define MOZPOISONWRITE_H
+#ifndef mozilla_LateWriteChecks_h
+#define mozilla_LateWriteChecks_h
 
-#include "mozilla/Types.h"
-#include <stdio.h>
+// This file, along with LateWriteChecks.cpp, serves to check for and report
+// late writes. The idea is discover writes to the file system that happens
+// during shutdown such that these maybe be moved forward and the process may be
+// killed without waiting for static destructors.
+
+namespace mozilla {
 
-MOZ_BEGIN_EXTERN_C
-  void MozillaRegisterDebugFD(int fd);
-  void MozillaRegisterDebugFILE(FILE *f);
-  void MozillaUnRegisterDebugFD(int fd);
-  void MozillaUnRegisterDebugFILE(FILE *f);
-MOZ_END_EXTERN_C
+/** Different shutdown check modes */
+enum ShutdownChecksMode {
+  SCM_CRASH,      /** Crash on shutdown check failure */
+  SCM_RECORD,     /** Record shutdown check violations */
+  SCM_NOTHING     /** Don't attempt any shutdown checks */
+};
 
-#ifdef __cplusplus
-namespace mozilla {
-enum ShutdownChecksMode {
-  SCM_CRASH,
-  SCM_RECORD,
-  SCM_NOTHING
-};
+/**
+ * Current shutdown check mode.
+ * This variable is defined and initialized in nsAppRunner.cpp
+ */
 extern ShutdownChecksMode gShutdownChecks;
 
-void InitWritePoisoning();
-void PoisonWrite();
-void DisableWritePoisoning();
-void EnableWritePoisoning();
-}
-#endif
+/**
+ * Allocate structures and acquire information from XPCOM necessary to do late
+ * write checks. This function must be invoked before BeginLateWriteChecks()
+ * and before XPCOM has stopped working.
+ */
+void InitLateWriteChecks();
 
-#endif
+/**
+ * Begin recording all writes as late-writes. This function should be called
+ * when all legitimate writes have occurred. This function does not rely on
+ * XPCOM as it is designed to be invoked during XPCOM shutdown.
+ *
+ * For late-write checks to work you must initialize one or more backends that
+ * reports IO through the IOInterposer API. PoisonIOInterposer would probably
+ * be the backend of choice in this case.
+ *
+ * Note: BeginLateWriteChecks() must have been invoked before this function.
+ */
+void BeginLateWriteChecks();
+
+/**
+ * Stop recording all writes as late-writes, call this function when you want
+ * late-write checks to stop. I.e. exception handling, or the special case on
+ * Mac described in bug 826029.
+ */
+void StopLateWriteChecks();
+
+} // mozilla
+
+#endif // mozilla_LateWriteChecks_h
copy from xpcom/build/mozPoisonWrite.h
copy to xpcom/build/PoisonIOInterposer.h
--- a/xpcom/build/mozPoisonWrite.h
+++ b/xpcom/build/PoisonIOInterposer.h
@@ -1,36 +1,88 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* vim:set ts=4 sw=4 sts=4 ci et: */
 /* 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/. */
 
-#ifndef MOZPOISONWRITE_H
-#define MOZPOISONWRITE_H
+#ifndef mozilla_PoisonIOInterposer_h
+#define mozilla_PoisonIOInterposer_h
 
 #include "mozilla/Types.h"
 #include <stdio.h>
 
+#if defined(MOZ_ENABLE_PROFILER_SPS) && (defined(XP_WIN) || defined(XP_MACOSX))
+
 MOZ_BEGIN_EXTERN_C
-  void MozillaRegisterDebugFD(int fd);
-  void MozillaRegisterDebugFILE(FILE *f);
-  void MozillaUnRegisterDebugFD(int fd);
-  void MozillaUnRegisterDebugFILE(FILE *f);
+
+/** Register file descriptor to be ignored by poisoning IO interposer */
+void MozillaRegisterDebugFD(int fd);
+
+/** Register file to be ignored by poisoning IO interposer */
+void MozillaRegisterDebugFILE(FILE *f);
+
+/** Unregister file descriptor from being ignored by poisoning IO interposer */
+void MozillaUnRegisterDebugFD(int fd);
+
+/** Unregister file from being ignored by poisoning IO interposer */
+void MozillaUnRegisterDebugFILE(FILE *f);
+
 MOZ_END_EXTERN_C
 
 #ifdef __cplusplus
 namespace mozilla {
-enum ShutdownChecksMode {
-  SCM_CRASH,
-  SCM_RECORD,
-  SCM_NOTHING
-};
-extern ShutdownChecksMode gShutdownChecks;
+
+/**
+ * Check if a file is registered as a debug file.
+ */
+bool IsDebugFile(intptr_t aFileID);
+
+/**
+ * Initialize IO poisoning, this is only safe to do on the main-thread when no
+ * other threads are running.
+ *
+ * Please, note that this probably has performance implications as all
+ */
+void InitPoisonIOInterposer();
+
+#ifdef XP_MACOSX
+/**
+ * Check that writes are dirty before reporting I/O (Mac OS X only)
+ * This is necessary for late-write checks on Mac OS X, but reading the buffer
+ * from file to see if we're writing dirty bits is expensive, so we don't want
+ * to do this for everything else that uses
+ */
+void OnlyReportDirtyWrites();
+#endif /* XP_MACOSX */
 
-void InitWritePoisoning();
-void PoisonWrite();
-void DisableWritePoisoning();
-void EnableWritePoisoning();
-}
-#endif
+/**
+ * Clear IO poisoning, this is only safe to do on the main-thread when no other
+ * threads are running.
+ */
+void ClearPoisonIOInterposer();
+
+} // namespace mozilla
+#endif /* __cplusplus */
+
+#else /* MOZ_ENABLE_PROFILER_SPS && (XP_WIN || XP_MACOSX) */
 
-#endif
+MOZ_BEGIN_EXTERN_C
+inline void MozillaRegisterDebugFD(int fd){}
+inline void MozillaRegisterDebugFILE(FILE *f){}
+inline void MozillaUnRegisterDebugFD(int fd){}
+inline void MozillaUnRegisterDebugFILE(FILE *f){}
+MOZ_END_EXTERN_C
+
+#ifdef __cplusplus
+namespace mozilla {
+inline bool IsDebugFile(intptr_t aFileID){ return true; }
+inline void InitPoisonIOInterposer(){}
+inline void ClearPoisonIOInterposer(){}
+#ifdef XP_MACOSX
+inline void OnlyReportDirtyWrites(){}
+#endif /* XP_MACOSX */
+} // namespace mozilla
+#endif /* __cplusplus */
+
+#endif /* MOZ_ENABLE_PROFILER_SPS && (XP_WIN || XP_MACOSX) */
+
+#endif // mozilla_PoisonIOInterposer_h
rename from xpcom/build/mozPoisonWriteBase.cpp
rename to xpcom/build/PoisonIOInterposerBase.cpp
--- a/xpcom/build/mozPoisonWriteBase.cpp
+++ b/xpcom/build/PoisonIOInterposerBase.cpp
@@ -1,38 +1,34 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 ci et: */
 /* 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 "mozPoisonWrite.h"
-#include "mozPoisonWriteBase.h"
-#include "mozilla/ProcessedStack.h"
+#include "mozilla/Mutex.h"
 #include "mozilla/Scoped.h"
-#include "mozilla/SHA1.h"
-#include "mozilla/Telemetry.h"
-#include "nsAppDirectoryServiceDefs.h"
-#include "nsDirectoryServiceUtils.h"
-#include "nsStackWalk.h"
-#include "nsPrintfCString.h"
-#include "plstr.h"
+
 #include <algorithm>
-#ifdef XP_WIN
-#define NS_T(str) L ## str
-#define NS_SLASH "\\"
-#include <windows.h>
+#include <vector>
+
+#include "PoisonIOInterposer.h"
+
+// Auxiliary method to convert file descriptors to ids
+#if defined(XP_WIN32)
 #include <io.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <sys/stat.h>
+inline intptr_t FileDescriptorToID(int aFd) {
+  return _get_osfhandle(aFd);
+}
 #else
-#define NS_SLASH "/"
-#endif
+inline intptr_t FileDescriptorToID(int aFd) {
+  return aFd;
+}
+#endif /* if not XP_WIN32 */
+
 using namespace mozilla;
 
 namespace {
 struct DebugFilesAutoLockTraits {
   typedef PRLock *type;
   const static type empty() {
     return nullptr;
   }
@@ -47,20 +43,18 @@ public:
   static void Clear();
   static PRLock *getDebugFileIDsLock() {
     // On windows this static is not thread safe, but we know that the first
     // call is from
     // * An early registration of a debug FD or
     // * The call to InitWritePoisoning.
     // Since the early debug FDs are logs created early in the main thread
     // and no writes are trapped before InitWritePoisoning, we are safe.
-    static bool Initialized = false;
-    if (!Initialized) {
+    if (!Lock) {
       Lock = PR_NewLock();
-      Initialized = true;
     }
 
     // We have to use something lower level than a mutex. If we don't, we
     // can get recursive in here when called from logging a call to free.
     return Lock;
   }
 
   DebugFilesAutoLock() :
@@ -70,274 +64,87 @@ public:
 };
 
 PRLock *DebugFilesAutoLock::Lock;
 void DebugFilesAutoLock::Clear() {
   MOZ_ASSERT(Lock != nullptr);
   Lock = nullptr;
 }
 
-static char *sProfileDirectory = nullptr;
-
 // Return a vector used to hold the IDs of the current debug files. On unix
 // an ID is a file descriptor. On Windows it is a file HANDLE.
 std::vector<intptr_t>* getDebugFileIDs() {
-  PRLock *lock = DebugFilesAutoLock::getDebugFileIDsLock();
-  PR_ASSERT_CURRENT_THREAD_OWNS_LOCK(lock);
+  PR_ASSERT_CURRENT_THREAD_OWNS_LOCK(DebugFilesAutoLock::getDebugFileIDsLock());
   // We have to use new as some write happen during static destructors
   // so an static std::vector might be destroyed while we still need it.
   static std::vector<intptr_t> *DebugFileIDs = new std::vector<intptr_t>();
   return DebugFileIDs;
 }
 
-// This a wrapper over a file descriptor that provides a Printf method and
-// computes the sha1 of the data that passes through it.
-class SHA1Stream
-{
-public:
-    explicit SHA1Stream(FILE *stream)
-      : mFile(stream)
-    {
-      MozillaRegisterDebugFILE(mFile);
-    }
-
-    void Printf(const char *aFormat, ...)
-    {
-        MOZ_ASSERT(mFile);
-        va_list list;
-        va_start(list, aFormat);
-        nsAutoCString str;
-        str.AppendPrintf(aFormat, list);
-        va_end(list);
-        mSHA1.update(str.get(), str.Length());
-        fwrite(str.get(), 1, str.Length(), mFile);
-    }
-    void Finish(SHA1Sum::Hash &aHash)
-    {
-        int fd = fileno(mFile);
-        fflush(mFile);
-        MozillaUnRegisterDebugFD(fd);
-        fclose(mFile);
-        mSHA1.finish(aHash);
-        mFile = nullptr;
-    }
-private:
-    FILE *mFile;
-    SHA1Sum mSHA1;
-};
-
-static void RecordStackWalker(void *aPC, void *aSP, void *aClosure)
-{
-    std::vector<uintptr_t> *stack =
-        static_cast<std::vector<uintptr_t>*>(aClosure);
-    stack->push_back(reinterpret_cast<uintptr_t>(aPC));
-}
-
-
-enum PoisonState {
-  POISON_UNINITIALIZED = 0,
-  POISON_ON,
-  POISON_OFF
-};
-
-// POISON_OFF has two consequences
-// * It prevents PoisonWrite from patching the write functions.
-// * If the patching has already been done, it prevents AbortOnBadWrite from
-//   asserting. Note that not all writes use AbortOnBadWrite at this point
-//   (aio_write for example), so disabling writes after patching doesn't
-//   completely undo it.
-PoisonState sPoisoningState = POISON_UNINITIALIZED;
-}
-
-namespace mozilla {
-
-void InitWritePoisoning()
-{
-  // Stdout and Stderr are OK.
-  MozillaRegisterDebugFD(1);
-  MozillaRegisterDebugFD(2);
-
-  nsCOMPtr<nsIFile> mozFile;
-  NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mozFile));
-  if (mozFile) {
-    nsAutoCString nativePath;
-    nsresult rv = mozFile->GetNativePath(nativePath);
-    if (NS_SUCCEEDED(rv)) {
-      sProfileDirectory = PL_strdup(nativePath.get());
-    }
-  }
-}
-
-bool ValidWriteAssert(bool ok)
-{
-    if (gShutdownChecks == SCM_CRASH && !ok) {
-        MOZ_CRASH();
-    }
-
-    // We normally don't poison writes if gShutdownChecks is SCM_NOTHING, but
-    // write poisoning can get more users in the future (profiling for example),
-    // so make sure we behave correctly.
-    if (gShutdownChecks == SCM_NOTHING || ok || !sProfileDirectory ||
-        !Telemetry::CanRecord()) {
-        return ok;
-    }
-
-    // Write the stack and loaded libraries to a file. We can get here
-    // concurrently from many writes, so we use multiple temporary files.
-    std::vector<uintptr_t> rawStack;
-
-    NS_StackWalk(RecordStackWalker, /* skipFrames */ 0, /* maxFrames */ 0,
-                 reinterpret_cast<void*>(&rawStack), 0, nullptr);
-    Telemetry::ProcessedStack stack = Telemetry::GetStackAndModules(rawStack);
+} // anonymous namespace
 
-    nsPrintfCString nameAux("%s%s%s", sProfileDirectory,
-                            NS_SLASH, "Telemetry.LateWriteTmpXXXXXX");
-    char *name;
-    nameAux.GetMutableData(&name);
-
-    // We want the sha1 of the entire file, so please don't write to fd
-    // directly; use sha1Stream.
-    FILE *stream;
-#ifdef XP_WIN
-    HANDLE hFile;
-    do {
-      // mkstemp isn't supported so keep trying until we get a file
-      int result = _mktemp_s(name, strlen(name) + 1);
-      hFile = CreateFileA(name, GENERIC_WRITE, 0, nullptr,
-                          CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
-    } while (GetLastError() == ERROR_FILE_EXISTS);
-
-    if (hFile == INVALID_HANDLE_VALUE) {
-      NS_RUNTIMEABORT("Um, how did we get here?");
-    }
-
-    // http://support.microsoft.com/kb/139640
-    int fd = _open_osfhandle((intptr_t)hFile, _O_APPEND);
-    if (fd == -1) {
-      NS_RUNTIMEABORT("Um, how did we get here?");
-    }
-
-    stream = _fdopen(fd, "w");
-#else
-    int fd = mkstemp(name);
-    stream = fdopen(fd, "w");
-#endif
-
-    SHA1Stream sha1Stream(stream);
-
-    size_t numModules = stack.GetNumModules();
-    sha1Stream.Printf("%u\n", (unsigned)numModules);
-    for (size_t i = 0; i < numModules; ++i) {
-        Telemetry::ProcessedStack::Module module = stack.GetModule(i);
-        sha1Stream.Printf("%s %s\n", module.mBreakpadId.c_str(),
-                          module.mName.c_str());
-    }
+namespace mozilla{
 
-    size_t numFrames = stack.GetStackSize();
-    sha1Stream.Printf("%u\n", (unsigned)numFrames);
-    for (size_t i = 0; i < numFrames; ++i) {
-        const Telemetry::ProcessedStack::Frame &frame =
-            stack.GetFrame(i);
-        // NOTE: We write the offsets, while the atos tool expects a value with
-        // the virtual address added. For example, running otool -l on the the firefox
-        // binary shows
-        //      cmd LC_SEGMENT_64
-        //      cmdsize 632
-        //      segname __TEXT
-        //      vmaddr 0x0000000100000000
-        // so to print the line matching the offset 123 one has to run
-        // atos -o firefox 0x100000123.
-        sha1Stream.Printf("%d %x\n", frame.mModIndex, (unsigned)frame.mOffset);
-    }
-
-    SHA1Sum::Hash sha1;
-    sha1Stream.Finish(sha1);
-
-    // Note: These files should be deleted by telemetry once it reads them. If
-    // there were no telemery runs by the time we shut down, we just add files
-    // to the existing ones instead of replacing them. Given that each of these
-    // files is a bug to be fixed, that is probably the right thing to do.
-
-    // We append the sha1 of the contents to the file name. This provides a simple
-    // client side deduplication.
-    nsPrintfCString finalName("%s%s", sProfileDirectory, "/Telemetry.LateWriteFinal-");
-    for (int i = 0; i < 20; ++i) {
-        finalName.AppendPrintf("%02x", sha1[i]);
-    }
-    PR_Delete(finalName.get());
-    PR_Rename(name, finalName.get());
-    return false;
-}
-
-void DisableWritePoisoning() {
-  if (sPoisoningState != POISON_ON)
-    return;
-
-  sPoisoningState = POISON_OFF;
-  PL_strfree(sProfileDirectory);
-  sProfileDirectory = nullptr;
-
-  PRLock *Lock;
-  {
-    DebugFilesAutoLock lockedScope;
-    delete getDebugFileIDs();
-    Lock = DebugFilesAutoLock::getDebugFileIDsLock();
-    DebugFilesAutoLock::Clear();
-  }
-  PR_DestroyLock(Lock);
-}
-void EnableWritePoisoning() {
-  sPoisoningState = POISON_ON;
-}
-
-bool PoisonWriteEnabled()
-{
-  return sPoisoningState == POISON_ON;
-}
-
+// Auxiliary Method to test if a file descriptor is registered to be ignored
+// by the poisoning IO interposer
 bool IsDebugFile(intptr_t aFileID) {
   DebugFilesAutoLock lockedScope;
 
   std::vector<intptr_t> &Vec = *getDebugFileIDs();
   return std::find(Vec.begin(), Vec.end(), aFileID) != Vec.end();
 }
 
-} // mozilla
+// Clean-up for the registered debug files.
+// We should probably make sure all debug files are unregistered instead.
+// But as the poison IO interposer is used for late-write checks we're not
+// disabling it at any point yet. So Really no need for this.
+//
+// void ClearDebugFileRegister() {
+//   PRLock *Lock;
+//   {
+//     DebugFilesAutoLock lockedScope;
+//     delete getDebugFileIDs();
+//     Lock = DebugFilesAutoLock::getDebugFileIDsLock();
+//     DebugFilesAutoLock::Clear();
+//   }
+//   PR_DestroyLock(Lock);
+// }
+
+} // namespace mozilla
 
 extern "C" {
+
   void MozillaRegisterDebugFD(int fd) {
-    if (sPoisoningState == POISON_OFF)
-      return;
+    intptr_t fileId = FileDescriptorToID(fd);
     DebugFilesAutoLock lockedScope;
-    intptr_t fileId = FileDescriptorToID(fd);
     std::vector<intptr_t> &Vec = *getDebugFileIDs();
     MOZ_ASSERT(std::find(Vec.begin(), Vec.end(), fileId) == Vec.end());
     Vec.push_back(fileId);
   }
+
   void MozillaRegisterDebugFILE(FILE *f) {
-    if (sPoisoningState == POISON_OFF)
+    int fd = fileno(f);
+    if (fd == 1 || fd == 2) {
       return;
-    int fd = fileno(f);
-    if (fd == 1 || fd == 2)
-      return;
+    }
     MozillaRegisterDebugFD(fd);
   }
+
   void MozillaUnRegisterDebugFD(int fd) {
-    if (sPoisoningState == POISON_OFF)
-      return;
     DebugFilesAutoLock lockedScope;
     intptr_t fileId = FileDescriptorToID(fd);
     std::vector<intptr_t> &Vec = *getDebugFileIDs();
     std::vector<intptr_t>::iterator i =
       std::find(Vec.begin(), Vec.end(), fileId);
     MOZ_ASSERT(i != Vec.end());
     Vec.erase(i);
   }
+
   void MozillaUnRegisterDebugFILE(FILE *f) {
-    if (sPoisoningState == POISON_OFF)
+    int fd = fileno(f);
+    if (fd == 1 || fd == 2) {
       return;
-    int fd = fileno(f);
-    if (fd == 1 || fd == 2)
-      return;
+    }
     fflush(f);
     MozillaUnRegisterDebugFD(fd);
   }
-}
+
+}
\ No newline at end of file
rename from xpcom/build/mozPoisonWriteMac.cpp
rename to xpcom/build/PoisonIOInterposerMac.cpp
--- a/xpcom/build/mozPoisonWriteMac.cpp
+++ b/xpcom/build/PoisonIOInterposerMac.cpp
@@ -1,93 +1,247 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* vim:set ts=4 sw=4 sts=4 ci et: */
 /* 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 "mozilla/mozPoisonWrite.h"
-#include "mozPoisonWriteBase.h"
-#include "mozilla/Util.h"
-#include "nsTraceRefcntImpl.h"
+#include "PoisonIOInterposer.h"
+#include "mach_override.h"
+
 #include "mozilla/Assertions.h"
 #include "mozilla/DebugOnly.h"
-#include "mozilla/Scoped.h"
+#include "mozilla/IOInterposer.h"
 #include "mozilla/Mutex.h"
-#include "mozilla/Telemetry.h"
 #include "mozilla/ProcessedStack.h"
-#include "nsStackWalk.h"
+#include "mozilla/Scoped.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Util.h"
 #include "nsPrintfCString.h"
-#include "mach_override.h"
+#include "nsStackWalk.h"
+#include "nsTraceRefcntImpl.h"
+#include "plstr.h"
 #include "prio.h"
-#include "plstr.h"
-#include "nsCOMPtr.h"
-#include "nsAppDirectoryServiceDefs.h"
-#include "nsDirectoryServiceUtils.h"
-#include "mozilla/SHA1.h"
-#include <sys/stat.h>
-#include <sys/socket.h>
+
 #include <vector>
 #include <algorithm>
 #include <string.h>
+
+#include <sys/stat.h>
+#include <sys/socket.h>
 #include <sys/uio.h>
 #include <aio.h>
 #include <dlfcn.h>
 
 namespace {
+
 using namespace mozilla;
 
+// Bit tracking if poisoned writes are enabled
+static bool sIsEnabled = false;
+
+// Check if writes are dirty before reporting IO
+static bool sOnlyReportDirtyWrites = false;
+
+// Routines for write validation
+bool IsValidWrite(int fd, const void *wbuf, size_t count);
+bool IsIPCWrite(int fd, const struct stat &buf);
+
+/******************************** IO AutoTimer ********************************/
+
+/**
+ * RAII class for timing the duration of an I/O call and reporting the result
+ * to the IOInterposeObserver API.
+ */
+class MacIOAutoObservation : public IOInterposeObserver::Observation
+{
+public:
+  MacIOAutoObservation(IOInterposeObserver::Operation aOp,
+                       const char* aReference, int aFd)
+    : mFd(aFd),
+      mShouldObserve(sIsEnabled && IOInterposer::IsObservedOperation(aOp) &&
+                     !IsDebugFile(aFd))
+  {
+    if (mShouldObserve) {
+      mOperation = aOp;
+      mReference = aReference;
+      mStart = TimeStamp::Now();
+    }
+  }
+
+  MacIOAutoObservation(IOInterposeObserver::Operation aOp,
+                       const char* aReference, int aFd, const void *aBuf,
+                       size_t aCount)
+    : mFd(aFd),
+      mShouldObserve(sIsEnabled && IOInterposer::IsObservedOperation(aOp) &&
+                     !IsDebugFile(aFd))
+  {
+    if (mShouldObserve) {
+      mShouldObserve = IsValidWrite(aFd, aBuf, aCount);
+      if (mShouldObserve) {
+        mOperation = aOp;
+        mReference = aReference;
+        mStart = TimeStamp::Now();
+      }
+    }
+  }
+
+  ~MacIOAutoObservation()
+  {
+    if (mShouldObserve) {
+      mEnd = TimeStamp::Now();
+
+      // Report this observation
+      IOInterposer::Report(*this);
+    }
+  }
+
+private:
+  int                 mFd;
+  bool                mShouldObserve;
+};
+
+/****************************** Write Validation ******************************/
+
+// We want to detect "actual" writes, not IPC. Some IPC mechanisms are
+// implemented with file descriptors, so filter them out.
+bool IsIPCWrite(int fd, const struct stat &buf) {
+  if ((buf.st_mode & S_IFMT) == S_IFIFO) {
+    return true;
+  }
+
+  if ((buf.st_mode & S_IFMT) != S_IFSOCK) {
+    return false;
+  }
+
+  sockaddr_storage address;
+  socklen_t len = sizeof(address);
+  if (getsockname(fd, (sockaddr*) &address, &len) != 0) {
+    return true; // Ignore the fd if we can't find what it is.
+  }
+
+  return address.ss_family == AF_UNIX;
+}
+
+// We want to report actual disk IO not things that don't move bits on the disk
+bool IsValidWrite(int fd, const void *wbuf, size_t count)
+{
+  // Ignore writes of zero bytes, Firefox does some during shutdown.
+  if (count == 0) {
+    return false;
+  }
+
+  {
+    struct stat buf;
+    int rv = fstat(fd, &buf);
+    if (rv != 0) {
+      return true;
+    }
+
+    if (IsIPCWrite(fd, buf)) {
+      return false;
+    }
+  }
+
+  // For writev we pass NULL in wbuf. We should only get here from
+  // dbm, and it uses write, so assert that we have wbuf.
+  if (!wbuf) {
+    return true;
+  }
+
+  // Break, here if we're allowed to report non-dirty writes
+  if(!sOnlyReportDirtyWrites) {
+    return true;
+  }
+
+  // As a really bad hack, accept writes that don't change the on disk
+  // content. This is needed because dbm doesn't keep track of dirty bits
+  // and can end up writing the same data to disk twice. Once when the
+  // user (nss) asks it to sync and once when closing the database.
+  ScopedFreePtr<void> wbuf2(malloc(count));
+  if (!wbuf2) {
+    return true;
+  }
+  off_t pos = lseek(fd, 0, SEEK_CUR);
+  if (pos == -1) {
+    return true;
+  }
+  ssize_t r = read(fd, wbuf2, count);
+  if (r < 0 || (size_t)r != count) {
+    return true;
+  }
+  int cmp = memcmp(wbuf, wbuf2, count);
+  if (cmp != 0) {
+    return true;
+  }
+  off_t pos2 = lseek(fd, pos, SEEK_SET);
+  if (pos2 != pos) {
+    return true;
+  }
+
+  // Otherwise this is not a valid write
+  return false;
+}
+
+/*************************** Function Interception  ***************************/
+
+/** Structure for declaration of function override */
 struct FuncData {
-    const char *Name;      // Name of the function for the ones we use dlsym
-    const void *Wrapper;   // The function that we will replace 'Function' with
-    void *Function;        // The function that will be replaced with 'Wrapper'
-    void *Buffer;          // Will point to the jump buffer that lets us call
-                           // 'Function' after it has been replaced.
+  const char *Name;      // Name of the function for the ones we use dlsym
+  const void *Wrapper;   // The function that we will replace 'Function' with
+  void *Function;        // The function that will be replaced with 'Wrapper'
+  void *Buffer;          // Will point to the jump buffer that lets us call
+                         // 'Function' after it has been replaced.
 };
 
 // Wrap aio_write. We have not seen it before, so just assert/report it.
 typedef ssize_t (*aio_write_t)(struct aiocb *aiocbp);
 ssize_t wrap_aio_write(struct aiocb *aiocbp);
 FuncData aio_write_data = { 0, (void*) wrap_aio_write, (void*) aio_write };
 ssize_t wrap_aio_write(struct aiocb *aiocbp) {
-    ValidWriteAssert(0);
-    aio_write_t old_write = (aio_write_t) aio_write_data.Buffer;
-    return old_write(aiocbp);
+  const char* ref = "aio_write";
+  MacIOAutoObservation timer(IOInterposeObserver::OpWrite, ref,
+                             aiocbp->aio_fildes);
+
+  aio_write_t old_write = (aio_write_t) aio_write_data.Buffer;
+  return old_write(aiocbp);
 }
 
 // Wrap pwrite-like functions.
 // We have not seen them before, so just assert/report it.
 typedef ssize_t (*pwrite_t)(int fd, const void *buf, size_t nbyte, off_t offset);
 template<FuncData &foo>
 ssize_t wrap_pwrite_temp(int fd, const void *buf, size_t nbyte, off_t offset) {
-    ValidWriteAssert(0);
-    pwrite_t old_write = (pwrite_t) foo.Buffer;
-    return old_write(fd, buf, nbyte, offset);
+  const char* ref = "pwrite_*";
+  MacIOAutoObservation timer(IOInterposeObserver::OpWrite, ref, fd);
+  pwrite_t old_write = (pwrite_t) foo.Buffer;
+  return old_write(fd, buf, nbyte, offset);
 }
 
 // Define a FuncData for a pwrite-like functions.
 #define DEFINE_PWRITE_DATA(X, NAME)                                        \
 FuncData X ## _data = { NAME, (void*) wrap_pwrite_temp<X ## _data> };      \
 
 // This exists everywhere.
 DEFINE_PWRITE_DATA(pwrite, "pwrite")
 // These exist on 32 bit OS X
 DEFINE_PWRITE_DATA(pwrite_NOCANCEL_UNIX2003, "pwrite$NOCANCEL$UNIX2003");
 DEFINE_PWRITE_DATA(pwrite_UNIX2003, "pwrite$UNIX2003");
 // This exists on 64 bit OS X
 DEFINE_PWRITE_DATA(pwrite_NOCANCEL, "pwrite$NOCANCEL");
 
-void AbortOnBadWrite(int fd, const void *wbuf, size_t count);
 
 typedef ssize_t (*writev_t)(int fd, const struct iovec *iov, int iovcnt);
 template<FuncData &foo>
 ssize_t wrap_writev_temp(int fd, const struct iovec *iov, int iovcnt) {
-    AbortOnBadWrite(fd, 0, iovcnt);
-    writev_t old_write = (writev_t) foo.Buffer;
-    return old_write(fd, iov, iovcnt);
+  const char* ref = "pwrite_*";
+  MacIOAutoObservation timer(IOInterposeObserver::OpWrite, ref, fd, nullptr,
+                             iovcnt);
+  writev_t old_write = (writev_t) foo.Buffer;
+  return old_write(fd, iov, iovcnt);
 }
 
 // Define a FuncData for a writev-like functions.
 #define DEFINE_WRITEV_DATA(X, NAME)                                   \
 FuncData X ## _data = { NAME, (void*) wrap_writev_temp<X ## _data> }; \
 
 // This exists everywhere.
 DEFINE_WRITEV_DATA(writev, "writev");
@@ -95,19 +249,21 @@ DEFINE_WRITEV_DATA(writev, "writev");
 DEFINE_WRITEV_DATA(writev_NOCANCEL_UNIX2003, "writev$NOCANCEL$UNIX2003");
 DEFINE_WRITEV_DATA(writev_UNIX2003, "writev$UNIX2003");
 // This exists on 64 bit OS X
 DEFINE_WRITEV_DATA(writev_NOCANCEL, "writev$NOCANCEL");
 
 typedef ssize_t (*write_t)(int fd, const void *buf, size_t count);
 template<FuncData &foo>
 ssize_t wrap_write_temp(int fd, const void *buf, size_t count) {
-    AbortOnBadWrite(fd, buf, count);
-    write_t old_write = (write_t) foo.Buffer;
-    return old_write(fd, buf, count);
+  const char* ref = "pwrite_*";
+  MacIOAutoObservation timer(IOInterposeObserver::OpWrite, ref, fd, buf,
+                             count);
+  write_t old_write = (write_t) foo.Buffer;
+  return old_write(fd, buf, count);
 }
 
 // Define a FuncData for a write-like functions.
 #define DEFINE_WRITE_DATA(X, NAME)                                   \
 FuncData X ## _data = { NAME, (void*) wrap_write_temp<X ## _data> }; \
 
 // This exists everywhere.
 DEFINE_WRITE_DATA(write, "write");
@@ -131,104 +287,54 @@ FuncData *Functions[] = { &aio_write_dat
 
                           &writev_data,
                           &writev_NOCANCEL_UNIX2003_data,
                           &writev_UNIX2003_data,
                           &writev_NOCANCEL_data};
 
 const int NumFunctions = ArrayLength(Functions);
 
-// We want to detect "actual" writes, not IPC. Some IPC mechanisms are
-// implemented with file descriptors, so filter them out.
-bool IsIPCWrite(int fd, const struct stat &buf) {
-    if ((buf.st_mode & S_IFMT) == S_IFIFO) {
-        return true;
-    }
-
-    if ((buf.st_mode & S_IFMT) != S_IFSOCK) {
-        return false;
-    }
-
-    sockaddr_storage address;
-    socklen_t len = sizeof(address);
-    if (getsockname(fd, (sockaddr*) &address, &len) != 0) {
-        return true; // Ignore the fd if we can't find what it is.
-    }
-
-    return address.ss_family == AF_UNIX;
-}
-
-void AbortOnBadWrite(int fd, const void *wbuf, size_t count) {
-    if (!PoisonWriteEnabled())
-        return;
-
-    // Ignore writes of zero bytes, firefox does some during shutdown.
-    if (count == 0)
-        return;
-
-    struct stat buf;
-    int rv = fstat(fd, &buf);
-    if (!ValidWriteAssert(rv == 0))
-        return;
+} // anonymous namespace
 
-    if (IsIPCWrite(fd, buf))
-        return;
-
-    // Debugging FDs are OK
-    if (IsDebugFile(fd))
-        return;
-
-    // For writev we pass nullptr in wbuf. We should only get here from
-    // dbm, and it uses write, so assert that we have wbuf.
-    if (!ValidWriteAssert(wbuf))
-        return;
-
-    // As a really bad hack, accept writes that don't change the on disk
-    // content. This is needed because dbm doesn't keep track of dirty bits
-    // and can end up writing the same data to disk twice. Once when the
-    // user (nss) asks it to sync and once when closing the database.
-    ScopedFreePtr<void> wbuf2(malloc(count));
-    if (!ValidWriteAssert(wbuf2))
-        return;
-    off_t pos = lseek(fd, 0, SEEK_CUR);
-    if (!ValidWriteAssert(pos != -1))
-        return;
-    ssize_t r = read(fd, wbuf2, count);
-    if (!ValidWriteAssert(r == static_cast<ssize_t>(count)))
-        return;
-    int cmp = memcmp(wbuf, wbuf2, count);
-    if (!ValidWriteAssert(cmp == 0))
-        return;
-    off_t pos2 = lseek(fd, pos, SEEK_SET);
-    if (!ValidWriteAssert(pos2 == pos))
-        return;
-}
-} // anonymous namespace
+/******************************** IO Poisoning ********************************/
 
 namespace mozilla {
 
-intptr_t FileDescriptorToID(int aFd) {
-    return aFd;
+void InitPoisonIOInterposer() {
+  // Enable reporting from poisoned write methods
+  sIsEnabled = true;
+
+  // Make sure we only poison writes once!
+  static bool WritesArePoisoned = false;
+  if (WritesArePoisoned) {
+    return;
+  }
+  WritesArePoisoned = true;
+
+  // stdout and stderr are OK.
+  MozillaRegisterDebugFD(1);
+  MozillaRegisterDebugFD(2);
+
+  for (int i = 0; i < NumFunctions; ++i) {
+    FuncData *d = Functions[i];
+    if (!d->Function) {
+      d->Function = dlsym(RTLD_DEFAULT, d->Name);
+    }
+    if (!d->Function) {
+      continue;
+    }
+    DebugOnly<mach_error_t> t = mach_override_ptr(d->Function, d->Wrapper,
+                                       &d->Buffer);
+    MOZ_ASSERT(t == err_none);
+  }
 }
 
-void PoisonWrite() {
-    // Quick sanity check that we don't poison twice.
-    static bool WritesArePoisoned = false;
-    MOZ_ASSERT(!WritesArePoisoned);
-    if (WritesArePoisoned)
-        return;
-    WritesArePoisoned = true;
-
-    if (!PoisonWriteEnabled())
-        return;
+void OnlyReportDirtyWrites() {
+  sOnlyReportDirtyWrites = true;
+}
 
-    for (int i = 0; i < NumFunctions; ++i) {
-        FuncData *d = Functions[i];
-        if (!d->Function)
-            d->Function = dlsym(RTLD_DEFAULT, d->Name);
-        if (!d->Function)
-            continue;
-        DebugOnly<mach_error_t> t = mach_override_ptr(d->Function, d->Wrapper,
-                                           &d->Buffer);
-        MOZ_ASSERT(t == err_none);
-    }
+void ClearPoisonIOInterposer() {
+  // Not sure how or if we can unpoison the functions. Would be nice, but no
+  // worries we won't need to do this anyway.
+  sIsEnabled = false;
 }
-}
+
+} // namespace mozilla
\ No newline at end of file
rename from xpcom/build/mozPoisonWriteWin.cpp
rename to xpcom/build/PoisonIOInterposerWin.cpp
--- a/xpcom/build/mozPoisonWriteWin.cpp
+++ b/xpcom/build/PoisonIOInterposerWin.cpp
@@ -1,90 +1,222 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 ci et: */
 /* 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 "PoisonIOInterposer.h"
+
+#include <algorithm>
 #include <stdio.h>
-#include <windows.h>
-#include <winternl.h> // NTSTATUS
+#include <vector>
+
 #include <io.h>
+#include <windows.h>
+#include <winternl.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/IOInterposer.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/TimeStamp.h"
+#include "nsTArray.h"
 #include "nsWindowsDllInterceptor.h"
-#include "mozilla/mozPoisonWrite.h"
-#include "mozPoisonWriteBase.h"
-#include "mozilla/Assertions.h"
+#include "plstr.h"
+
+using namespace mozilla;
+
+namespace {
+
+// Keep track of poisoned state. Notice that there is no reason to lock access
+// to this variable as it's only changed in InitPoisonIOInterposer and
+// ClearPoisonIOInterposer which may only be called on the main-thread when no
+// other threads are running.
+static bool sIOPoisoned = false;
+
+/************************ Internal NT API Declarations ************************/
+
+/**
+ * Function pointer declaration for internal NT routine to write data to file.
+ * For documentation on the NtWriteFile routine, see ZwWriteFile on MSDN.
+ */
+typedef NTSTATUS (WINAPI *NtWriteFileFn)(
+  IN    HANDLE                  aFileHandle,
+  IN    HANDLE                  aEvent,
+  IN    PIO_APC_ROUTINE         aApc,
+  IN    PVOID                   aApcCtx,
+  OUT   PIO_STATUS_BLOCK        aIoStatus,
+  IN    PVOID                   aBuffer,
+  IN    ULONG                   aLength,
+  IN    PLARGE_INTEGER          aOffset,
+  IN    PULONG                  aKey
+);
+
+/**
+ * Function pointer declaration for internal NT routine to write data to file.
+ * No documentation exists, see wine sources for details.
+ */
+typedef NTSTATUS (WINAPI *NtWriteFileGatherFn)(
+  IN    HANDLE                  aFileHandle,
+  IN    HANDLE                  aEvent,
+  IN    PIO_APC_ROUTINE         aApc,
+  IN    PVOID                   aApcCtx,
+  OUT   PIO_STATUS_BLOCK        aIoStatus,
+  IN    FILE_SEGMENT_ELEMENT*   aSegments,
+  IN    ULONG                   aLength,
+  IN    PLARGE_INTEGER          aOffset,
+  IN    PULONG                  aKey
+);
+
+/*************************** Auxiliary Declarations ***************************/
+
+/**
+ * RAII class for timing the duration of an I/O call and reporting the result
+ * to the IOInterposeObserver API.
+ */
+class WinIOAutoObservation : public IOInterposeObserver::Observation
+{
+public:
+  WinIOAutoObservation(IOInterposeObserver::Operation aOp,
+                       const char* aReference, HANDLE aFileHandle)
+    : mFileHandle(aFileHandle),
+      mShouldObserve(IOInterposer::IsObservedOperation(aOp) &&
+                     !IsDebugFile(reinterpret_cast<intptr_t>(aFileHandle)))
+  {
+    if (mShouldObserve) {
+      mOperation = aOp;
+      mReference = aReference;
+      mStart = TimeStamp::Now();
+    }
+  }
+
+  ~WinIOAutoObservation()
+  {
+    if (mShouldObserve) {
+      mEnd = TimeStamp::Now();
+      // Report this observation
+      IOInterposer::Report(*this);
+    }
+  }
+
+private:
+  HANDLE              mFileHandle;
+  bool                mShouldObserve;
+};
+
+/*************************** IO Interposing Methods ***************************/
+
+// Function pointers to original functions
+static NtWriteFileFn          gOriginalNtWriteFile;
+static NtWriteFileGatherFn    gOriginalNtWriteFileGather;
+
+// Interposed NtWriteFile function
+static NTSTATUS WINAPI InterposedNtWriteFile(
+  HANDLE                        aFileHandle,
+  HANDLE                        aEvent,
+  PIO_APC_ROUTINE               aApc,
+  PVOID                         aApcCtx,
+  PIO_STATUS_BLOCK              aIoStatus,
+  PVOID                         aBuffer,
+  ULONG                         aLength,
+  PLARGE_INTEGER                aOffset,
+  PULONG                        aKey)
+{
+  // Report IO
+  const char* ref = "NtWriteFile";
+  WinIOAutoObservation timer(IOInterposeObserver::OpWrite, ref, aFileHandle);
+
+  // Something is badly wrong if this function is undefined
+  MOZ_ASSERT(gOriginalNtWriteFile);
+
+  // Execute original function
+  return gOriginalNtWriteFile(
+    aFileHandle,
+    aEvent,
+    aApc,
+    aApcCtx,
+    aIoStatus,
+    aBuffer,
+    aLength,
+    aOffset,
+    aKey
+  );
+}
+
+// Interposed NtWriteFileGather function
+static NTSTATUS WINAPI InterposedNtWriteFileGather(
+  HANDLE                        aFileHandle,
+  HANDLE                        aEvent,
+  PIO_APC_ROUTINE               aApc,
+  PVOID                         aApcCtx,
+  PIO_STATUS_BLOCK              aIoStatus,
+  FILE_SEGMENT_ELEMENT*         aSegments,
+  ULONG                         aLength,
+  PLARGE_INTEGER                aOffset,
+  PULONG                        aKey)
+{
+  // Report IO
+  const char* ref = "NtWriteFileGather";
+  WinIOAutoObservation timer(IOInterposeObserver::OpWrite, ref, aFileHandle);
+
+  // Something is badly wrong if this function is undefined
+  MOZ_ASSERT(gOriginalNtWriteFileGather);
+
+  // Execute original function
+  return gOriginalNtWriteFileGather(
+    aFileHandle,
+    aEvent,
+    aApc,
+    aApcCtx,
+    aIoStatus,
+    aSegments,
+    aLength,
+    aOffset,
+    aKey
+  );
+}
+
+} // anonymous namespace
+
+/******************************** IO Poisoning ********************************/
+
+// Windows DLL interceptor
+static WindowsDllInterceptor sNtDllInterceptor;
 
 namespace mozilla {
 
-intptr_t FileDescriptorToID(int aFd) {
-  return _get_osfhandle(aFd);
-}
+void InitPoisonIOInterposer() {
+  // Don't poison twice... as this function may only be invoked on the main
+  // thread when no other threads are running, it safe to allow multiple calls
+  // to InitPoisonIOInterposer() without complaining (ie. failing assertions).
+  if (sIOPoisoned) {
+    return;
+  }
+  sIOPoisoned = true;
 
-static WindowsDllInterceptor sNtDllInterceptor;
-
-void AbortOnBadWrite(HANDLE);
-bool ValidWriteAssert(bool ok);
+  // Stdout and Stderr are OK.
+  MozillaRegisterDebugFD(1);
+  MozillaRegisterDebugFD(2);
 
-typedef NTSTATUS (WINAPI* WriteFile_fn)(HANDLE, HANDLE, PIO_APC_ROUTINE,
-                                        void*, PIO_STATUS_BLOCK,
-                                        const void*, ULONG, PLARGE_INTEGER,
-                                        PULONG);
-WriteFile_fn gOriginalWriteFile;
-
-static NTSTATUS WINAPI
-patched_WriteFile(HANDLE aFile, HANDLE aEvent, PIO_APC_ROUTINE aApc,
-                  void* aApcUser, PIO_STATUS_BLOCK aIoStatus,
-                  const void* aBuffer, ULONG aLength,
-                  PLARGE_INTEGER aOffset, PULONG aKey)
-{
-  AbortOnBadWrite(aFile);
-  return gOriginalWriteFile(aFile, aEvent, aApc, aApcUser, aIoStatus,
-                            aBuffer, aLength, aOffset, aKey);
+  // Initialize dll interceptor and add hooks
+  sNtDllInterceptor.Init("ntdll.dll");
+  sNtDllInterceptor.AddHook(
+    "NtWriteFile",
+    reinterpret_cast<intptr_t>(InterposedNtWriteFile),
+    reinterpret_cast<void**>(&gOriginalNtWriteFile)
+  );
+  sNtDllInterceptor.AddHook(
+    "NtWriteFileGather",
+    reinterpret_cast<intptr_t>(InterposedNtWriteFileGather),
+    reinterpret_cast<void**>(&gOriginalNtWriteFileGather)
+  );
 }
 
-
-typedef NTSTATUS (WINAPI* WriteFileGather_fn)(HANDLE, HANDLE, PIO_APC_ROUTINE,
-                                              void*, PIO_STATUS_BLOCK,
-                                              FILE_SEGMENT_ELEMENT*,
-                                              ULONG, PLARGE_INTEGER, PULONG);
-WriteFileGather_fn gOriginalWriteFileGather;
-
-static NTSTATUS WINAPI
-patched_WriteFileGather(HANDLE aFile, HANDLE aEvent, PIO_APC_ROUTINE aApc,
-                        void* aApcUser, PIO_STATUS_BLOCK aIoStatus,
-                        FILE_SEGMENT_ELEMENT* aSegments, ULONG aLength,
-                        PLARGE_INTEGER aOffset, PULONG aKey)
-{
-  AbortOnBadWrite(aFile);
-  return gOriginalWriteFileGather(aFile, aEvent, aApc, aApcUser, aIoStatus,
-                                  aSegments, aLength, aOffset, aKey);
+void ClearPoisonIOInterposer() {
+  MOZ_ASSERT(false);
+  if (sIOPoisoned) {
+    // Destroy the DLL interceptor
+    sIOPoisoned = false;
+    sNtDllInterceptor = WindowsDllInterceptor();
+  }
 }
 
-void AbortOnBadWrite(HANDLE aFile)
-{
-  if (!PoisonWriteEnabled())
-    return;
-
-  // Debugging files are OK.
-  if (IsDebugFile(reinterpret_cast<intptr_t>(aFile)))
-    return;
-
-  ValidWriteAssert(false);
-}
-
-void PoisonWrite() {
-  // Quick sanity check that we don't poison twice.
-  static bool WritesArePoisoned = false;
-  MOZ_ASSERT(!WritesArePoisoned);
-  if (WritesArePoisoned)
-    return;
-  WritesArePoisoned = true;
-
-  if (!PoisonWriteEnabled())
-    return;
-
-  sNtDllInterceptor.Init("ntdll.dll");
-  sNtDllInterceptor.AddHook("NtWriteFile", reinterpret_cast<intptr_t>(patched_WriteFile), reinterpret_cast<void**>(&gOriginalWriteFile));
-  sNtDllInterceptor.AddHook("NtWriteFileGather", reinterpret_cast<intptr_t>(patched_WriteFileGather), reinterpret_cast<void**>(&gOriginalWriteFileGather));
-}
-}
-
+} // namespace mozilla
--- a/xpcom/build/moz.build
+++ b/xpcom/build/moz.build
@@ -12,54 +12,53 @@ EXPORTS += [
     'nsXPCOMCIDInternal.h',
     'nsXREAppData.h',
     'nsXULAppAPI.h',
     'xrecore.h',
 ]
 
 EXPORTS.mozilla += [
     'FileLocation.h',
-    'mozPoisonWrite.h',
+    'LateWriteChecks.h',
     'Omnijar.h',
+    'PoisonIOInterposer.h',
     'ServiceList.h',
     'Services.h',
     'XPCOM.h',
 ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     EXPORTS.mozilla += ['perfprobe.h']
-    SOURCES += [
-        'mozPoisonWriteBase.cpp',
-        'mozPoisonWriteWin.cpp',
-        'perfprobe.cpp',
-    ]
-elif CONFIG['OS_ARCH'] == 'Darwin':
-    SOURCES += [
-        'mozPoisonWriteBase.cpp',
-        'mozPoisonWriteMac.cpp',
-    ]
-else:
-    SOURCES += [
-        'mozPoisonWriteStub.cpp',
-    ]
 
 include('../glue/objs.mozbuild')
 
 SOURCES += xpcom_gluens_src_cppsrcs
 SOURCES += xpcom_glue_src_cppsrcs
 
 SOURCES += [
     'FileLocation.cpp',
     'FrozenFunctions.cpp',
+    'LateWriteChecks.cpp',
     'nsXPComInit.cpp',
     'nsXPCOMStrings.cpp',
     'Omnijar.cpp',
     'Services.cpp',
 ]
 
+if CONFIG['OS_TARGET'] == 'Darwin':
+    SOURCES += [
+        'PoisonIOInterposerBase.cpp',
+        'PoisonIOInterposerMac.cpp',
+    ]
+elif CONFIG['OS_TARGET'] == 'WINNT':
+    SOURCES += [
+        'PoisonIOInterposerBase.cpp',
+        'PoisonIOInterposerWin.cpp',
+    ]
+
 LIBXUL_LIBRARY = True
 
 MSVC_ENABLE_PGO = True
 
 LIBRARY_NAME = 'xpcom_core'
 
 EXPORT_LIBRARY = True
 
deleted file mode 100644
--- a/xpcom/build/mozPoisonWriteBase.h
+++ /dev/null
@@ -1,26 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 ci et: */
-/* 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/. */
-
-// Private interface for code shared between the platforms of mozPoisonWriteXXX
-
-#ifndef MOZPOISONWRITEBASE_H
-#define MOZPOISONWRITEBASE_H
-
-#include <stdio.h>
-#include <vector>
-#include "nspr.h"
-#include "mozilla/NullPtr.h"
-#include "mozilla/Util.h"
-#include "mozilla/Scoped.h"
-
-namespace mozilla {
-bool PoisonWriteEnabled();
-bool ValidWriteAssert(bool ok);
-bool IsDebugFile(intptr_t aFileID);
-intptr_t FileDescriptorToID(int aFd);
-}
-
-#endif
deleted file mode 100644
--- a/xpcom/build/mozPoisonWriteStub.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/* vim:set ts=4 sw=4 sts=4 ci et: */
-/* 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 <stdio.h>
-namespace mozilla {
-void PoisonWrite() {
-}
-void DisableWritePoisoning() {
-}
-void EnableWritePoisoning() {
-}
-void InitWritePoisoning() {
-}
-}
-extern "C" {
-    void MozillaRegisterDebugFD(int fd) {
-    }
-    void MozillaRegisterDebugFILE(FILE *f) {
-    }
-    void MozillaUnRegisterDebugFD(int fd) {
-    }
-    void MozillaUnRegisterDebugFILE(FILE *f) {
-    }
-}
--- a/xpcom/build/nsXPComInit.cpp
+++ b/xpcom/build/nsXPComInit.cpp
@@ -104,17 +104,19 @@ extern nsresult nsStringInputStreamConst
 #include <locale.h>
 #include "mozilla/Services.h"
 #include "mozilla/Omnijar.h"
 #include "mozilla/HangMonitor.h"
 #include "mozilla/Telemetry.h"
 
 #include "nsChromeRegistry.h"
 #include "nsChromeProtocolHandler.h"
-#include "mozilla/mozPoisonWrite.h"
+#include "mozilla/IOInterposer.h"
+#include "mozilla/PoisonIOInterposer.h"
+#include "mozilla/LateWriteChecks.h"
 
 #include "mozilla/scache/StartupCache.h"
 
 #include "base/at_exit.h"
 #include "base/command_line.h"
 #include "base/message_loop.h"
 
 #include "mozilla/ipc/BrowserProcessSubThread.h"
@@ -677,20 +679,20 @@ ShutdownXPCOM(nsIServiceManager* servMgr
         // all threads created using the thread manager (with the exception of
         // the main thread) have exited.
         nsThreadManager::get()->Shutdown();
 
         NS_ProcessPendingEvents(thread);
 
         HangMonitor::NotifyActivity();
 
-        // Write poisoning needs to find the profile directory, so it has to
+        // Late-write checks needs to find the profile directory, so it has to
         // be initialized before mozilla::services::Shutdown or (because of
         // xpcshell tests replacing the service) modules being unloaded.
-        InitWritePoisoning();
+        mozilla::InitLateWriteChecks();
 
         // We save the "xpcom-shutdown-loaders" observers to notify after
         // the observerservice is gone.
         if (observerService) {
             observerService->
                 EnumerateObservers(NS_XPCOM_SHUTDOWN_LOADERS_OBSERVER_ID,
                                    getter_AddRefs(moduleLoaders));
 
@@ -748,17 +750,25 @@ ShutdownXPCOM(nsIServiceManager* servMgr
         moduleLoaders = nullptr;
     }
 
     nsCycleCollector_shutdown();
 
     PROFILER_MARKER("Shutdown xpcom");
     // If we are doing any shutdown checks, poison writes.
     if (gShutdownChecks != SCM_NOTHING) {
-        mozilla::PoisonWrite();
+        // Calling InitIOInterposer or InitPoisonIOInterposer twice doesn't
+        // cause any problems, they'll safely abort the initialization on their
+        // own initiative.
+        mozilla::IOInterposer::Init();
+        mozilla::InitPoisonIOInterposer();
+#ifdef XP_MACOSX
+        mozilla::OnlyReportDirtyWrites();
+#endif /* XP_MACOSX */
+        mozilla::BeginLateWriteChecks();
     }
 
     // Shutdown nsLocalFile string conversion
     NS_ShutdownLocalFile();
 #ifdef XP_UNIX
     NS_ShutdownNativeCharsetUtils();
 #endif
 
--- a/xpcom/build/nsXULAppAPI.h
+++ b/xpcom/build/nsXULAppAPI.h
@@ -448,17 +448,17 @@ XRE_API(void,
 
 XRE_API(void,
         XRE_StartupTimelineRecord, (int aEvent, PRTime aWhen))
 
 XRE_API(void,
         XRE_InitOmnijar, (nsIFile* greOmni,
                           nsIFile* appOmni))
 XRE_API(void,
-        XRE_DisableWritePoisoning, (void))
+        XRE_StopLateWriteChecks, (void))
 
 #ifdef XP_WIN
 /**
  * Valid environment types for XRE_GetWindowsEnvironment.
  */
 enum WindowsEnvironmentType {
   WindowsEnvironmentType_Desktop = 0,
   WindowsEnvironmentType_Metro = 1