Backed out 7 changesets (bug 1420355) for mass failures on OS X and Android. r=backout on a CLOSED TREE
authorCsoregi Natalia <ncsoregi@mozilla.com>
Wed, 29 Nov 2017 03:08:46 +0200
changeset 704896 8cbc145b9f915eb5911141be9266c909f8ac26af
parent 704895 a7ed89e13a4cc4ce7b8ebe4dd99bce5160852003
child 704897 3c8385b19b24b182ddfdc35ffc746168a4cb9e83
push id91274
push userbmo:tlin@mozilla.com
push dateWed, 29 Nov 2017 03:36:32 +0000
reviewersbackout
bugs1420355
milestone59.0a1
backs outa7ed89e13a4cc4ce7b8ebe4dd99bce5160852003
fd6702e6e0a0a24e2d5d6c98b5467768cff9dbc2
0479dda078a2ab5a9be9ebf5b82705a332ba75bd
e69357ccca9e26deb32ef6c5a4e445673fe4ada9
3742a4b69ba20bdcc47ee8bfa7d56268be944ae1
451cd087922f1f1f0e66de9196b7b99e36c8072e
d80b5c4e1dd09776fdc85df09693ca76a0dc408b
Backed out 7 changesets (bug 1420355) for mass failures on OS X and Android. r=backout on a CLOSED TREE Backed out changeset a7ed89e13a4c (bug 1420355) Backed out changeset fd6702e6e0a0 (bug 1420355) Backed out changeset 0479dda078a2 (bug 1420355) Backed out changeset e69357ccca9e (bug 1420355) Backed out changeset 3742a4b69ba2 (bug 1420355) Backed out changeset 451cd087922f (bug 1420355) Backed out changeset d80b5c4e1dd0 (bug 1420355)
browser/installer/package-manifest.in
build/automation.py.in
build/mobile/remoteautomation.py
build/moz.configure/memory.configure
memory/build/moz.build
memory/build/mozjemalloc.cpp
memory/build/replace_malloc.h
memory/replace/dmd/DMD.cpp
memory/replace/dmd/moz.build
memory/replace/dmd/test/script-diff-dark-matter-expected.txt
memory/replace/dmd/test/script-diff-dark-matter2.json
memory/replace/dmd/test/script-ignore-alloc-fns-expected.txt
memory/replace/dmd/test/script-ignore-alloc-fns.json
memory/replace/dmd/test/test_dmd.js
memory/replace/logalloc/LogAlloc.cpp
memory/replace/logalloc/README
memory/replace/logalloc/logalloc.mozbuild
memory/replace/logalloc/moz.build
memory/replace/logalloc/replay/Makefile.in
memory/replace/logalloc/replay/moz.build
memory/replace/moz.build
python/mozbuild/mozbuild/mach_commands.py
testing/awsy/mach_commands.py
testing/mochitest/mochitest_options.py
testing/mochitest/runtests.py
testing/mochitest/runtestsremote.py
testing/mozbase/mozrunner/mozrunner/utils.py
testing/xpcshell/runxpcshelltests.py
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -63,16 +63,19 @@
 [xpcom]
 @RESPATH@/dependentlibs.list
 #ifdef MOZ_SHARED_MOZGLUE
 @BINPATH@/@DLL_PREFIX@mozglue@DLL_SUFFIX@
 #endif
 #ifndef MOZ_STATIC_JS
 @BINPATH@/@DLL_PREFIX@mozjs@DLL_SUFFIX@
 #endif
+#ifdef MOZ_DMD
+@BINPATH@/@DLL_PREFIX@dmd@DLL_SUFFIX@
+#endif
 #ifndef MOZ_SYSTEM_NSPR
 #ifndef MOZ_FOLD_LIBS
 @BINPATH@/@DLL_PREFIX@nspr4@DLL_SUFFIX@
 @BINPATH@/@DLL_PREFIX@plc4@DLL_SUFFIX@
 @BINPATH@/@DLL_PREFIX@plds4@DLL_SUFFIX@
 #endif
 #endif
 #ifdef XP_MACOSX
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -178,22 +178,44 @@ class Automation(object):
     def kill(self):
       if Automation().IS_WIN32:
         import platform
         pid = "%i" % self.pid
         subprocess.Popen(["taskkill", "/F", "/PID", pid]).wait()
       else:
         os.kill(self.pid, signal.SIGKILL)
 
-  def environment(self, env=None, xrePath=None, crashreporter=True, debugger=False, lsanPath=None, ubsanPath=None):
+  def environment(self, env=None, xrePath=None, crashreporter=True, debugger=False, dmdPath=None, lsanPath=None, ubsanPath=None):
     if xrePath == None:
       xrePath = self.DIST_BIN
     if env == None:
       env = dict(os.environ)
 
+    ldLibraryPath = os.path.abspath(os.path.join(SCRIPT_DIR, xrePath))
+    dmdLibrary = None
+    preloadEnvVar = None
+    if self.UNIXISH or self.IS_MAC:
+      envVar = "LD_LIBRARY_PATH"
+      preloadEnvVar = "LD_PRELOAD"
+      if self.IS_MAC:
+        envVar = "DYLD_LIBRARY_PATH"
+        dmdLibrary = "libdmd.dylib"
+      else: # unixish
+        dmdLibrary = "libdmd.so"
+      if envVar in env:
+        ldLibraryPath = ldLibraryPath + ":" + env[envVar]
+      env[envVar] = ldLibraryPath
+    elif self.IS_WIN32:
+      env["PATH"] = env["PATH"] + ";" + str(ldLibraryPath)
+      dmdLibrary = "dmd.dll"
+      preloadEnvVar = "MOZ_REPLACE_MALLOC_LIB"
+
+    if dmdPath and dmdLibrary and preloadEnvVar:
+      env[preloadEnvVar] = os.path.join(dmdPath, dmdLibrary)
+
     if crashreporter and not debugger:
       env['MOZ_CRASHREPORTER_NO_REPORT'] = '1'
       env['MOZ_CRASHREPORTER'] = '1'
     else:
       env['MOZ_CRASHREPORTER_DISABLE'] = '1'
 
     # Crash on non-local network connections by default.
     # MOZ_DISABLE_NONLOCAL_CONNECTIONS can be set to "0" to temporarily
--- a/build/mobile/remoteautomation.py
+++ b/build/mobile/remoteautomation.py
@@ -55,22 +55,25 @@ class RemoteAutomation(Automation):
 
     def setProduct(self, product):
         self._product = product
 
     def setRemoteLog(self, logfile):
         self._remoteLog = logfile
 
     # Set up what we need for the remote environment
-    def environment(self, env=None, xrePath=None, crashreporter=True, debugger=False, lsanPath=None, ubsanPath=None):
+    def environment(self, env=None, xrePath=None, crashreporter=True, debugger=False, dmdPath=None, lsanPath=None, ubsanPath=None):
         # Because we are running remote, we don't want to mimic the local env
         # so no copying of os.environ
         if env is None:
             env = {}
 
+        if dmdPath:
+            env['MOZ_REPLACE_MALLOC_LIB'] = os.path.join(dmdPath, 'libdmd.so')
+
         # Except for the mochitest results table hiding option, which isn't
         # passed to runtestsremote.py as an actual option, but through the
         # MOZ_HIDE_RESULTS_TABLE environment variable.
         if 'MOZ_HIDE_RESULTS_TABLE' in os.environ:
             env['MOZ_HIDE_RESULTS_TABLE'] = os.environ['MOZ_HIDE_RESULTS_TABLE']
 
         if crashreporter and not debugger:
             env['MOZ_CRASHREPORTER_NO_REPORT'] = '1'
--- a/build/moz.configure/memory.configure
+++ b/build/moz.configure/memory.configure
@@ -57,19 +57,8 @@ def replace_malloc(value, jemalloc, mile
         return True
     if milestone.is_nightly and jemalloc and build_project != 'js':
         return True
 
 
 set_config('MOZ_REPLACE_MALLOC', replace_malloc)
 set_define('MOZ_REPLACE_MALLOC', replace_malloc)
 add_old_configure_assignment('MOZ_REPLACE_MALLOC', replace_malloc)
-
-
-@depends(replace_malloc, build_project)
-def replace_malloc_static(replace_malloc, build_project):
-    # Default to statically linking replace-malloc libraries that can be
-    # statically linked, except when building with --enable-project=memory.
-    if replace_malloc and build_project != 'memory':
-        return True
-
-
-set_config('MOZ_REPLACE_MALLOC_STATIC', replace_malloc_static)
--- a/memory/build/moz.build
+++ b/memory/build/moz.build
@@ -44,12 +44,9 @@ if CONFIG['OS_TARGET'] == 'Android' and 
     ]
 
 if CONFIG['MOZ_BUILD_APP'] != 'memory':
     FINAL_LIBRARY = 'mozglue'
 
 if CONFIG['_MSC_VER']:
     CXXFLAGS += ['-wd4273'] # inconsistent dll linkage (bug 558163)
 
-if CONFIG['MOZ_REPLACE_MALLOC_STATIC']:
-    DEFINES['MOZ_REPLACE_MALLOC_STATIC'] = True
-
 DisableStlWrapping()
--- a/memory/build/mozjemalloc.cpp
+++ b/memory/build/mozjemalloc.cpp
@@ -4869,64 +4869,36 @@ replace_malloc_handle()
 #define REPLACE_MALLOC_GET_INIT_FUNC(handle)                                   \
   (replace_init_impl_t*)dlsym(handle, "replace_init")
 
 #endif
 
 static void
 replace_malloc_init_funcs();
 
-#ifdef MOZ_REPLACE_MALLOC_STATIC
-extern "C" void
-logalloc_init(malloc_table_t*, ReplaceMallocBridge**);
-
-extern "C" void
-dmd_init(malloc_table_t*, ReplaceMallocBridge**);
-#endif
-
-bool
-Equals(malloc_table_t& aTable1, malloc_table_t& aTable2)
-{
-  return memcmp(&aTable1, &aTable2, sizeof(malloc_table_t)) == 0;
-}
-
 // Below is the malloc implementation overriding jemalloc and calling the
 // replacement functions if they exist.
 static bool gReplaceMallocInitialized = false;
 static ReplaceMallocBridge* gReplaceMallocBridge = nullptr;
 static void
 init()
 {
-#ifdef MOZ_REPLACE_MALLOC_STATIC
-  malloc_table_t initialTable = gReplaceMallocTable;
-#endif
-
 #ifdef MOZ_DYNAMIC_REPLACE_INIT
   replace_malloc_handle_t handle = replace_malloc_handle();
   if (handle) {
     replace_init = REPLACE_MALLOC_GET_INIT_FUNC(handle);
   }
 #endif
 
   // Set this *before* calling replace_init, otherwise if replace_init calls
   // malloc() we'll get an infinite loop.
   gReplaceMallocInitialized = true;
   if (replace_init) {
     replace_init(&gReplaceMallocTable, &gReplaceMallocBridge);
   }
-#ifdef MOZ_REPLACE_MALLOC_STATIC
-  if (Equals(initialTable, gReplaceMallocTable)) {
-    logalloc_init(&gReplaceMallocTable, &gReplaceMallocBridge);
-  }
-#ifdef MOZ_DMD
-  if (Equals(initialTable, gReplaceMallocTable)) {
-    dmd_init(&gReplaceMallocTable, &gReplaceMallocBridge);
-  }
-#endif
-#endif
   replace_malloc_init_funcs();
 }
 
 #define MALLOC_DECL(name, return_type, ...)                                    \
   template<>                                                                   \
   inline return_type ReplaceMalloc::name(                                      \
     ARGS_HELPER(TYPED_ARGS, ##__VA_ARGS__))                                    \
   {                                                                            \
--- a/memory/build/replace_malloc.h
+++ b/memory/build/replace_malloc.h
@@ -71,36 +71,30 @@
 
 #define REPLACE_MALLOC_IMPL
 
 #include "replace_malloc_bridge.h"
 
 // Implementing a replace-malloc library is incompatible with using mozalloc.
 #define MOZ_NO_MOZALLOC 1
 
-#include "mozilla/MacroArgs.h"
 #include "mozilla/Types.h"
 
 MOZ_BEGIN_EXTERN_C
 
 // MOZ_REPLACE_WEAK is only defined in mozjemalloc.cpp. Normally including
 // this header will add function definitions.
 #ifndef MOZ_REPLACE_WEAK
 #define MOZ_REPLACE_WEAK
 #endif
 
-// When building a replace-malloc library for static linking, we want
-// each to have a different name for their "public" functions.
-// The build system defines MOZ_REPLACE_MALLOC_PREFIX in that case.
-#ifdef MOZ_REPLACE_MALLOC_PREFIX
-#define replace_init MOZ_CONCAT(MOZ_REPLACE_MALLOC_PREFIX, _init)
-#define MOZ_REPLACE_PUBLIC
-#else
-#define MOZ_REPLACE_PUBLIC MOZ_EXPORT
-#endif
+// Replace-malloc library initialization function. See top of this file
+MOZ_EXPORT void
+replace_init(malloc_table_t*, struct ReplaceMallocBridge**) MOZ_REPLACE_WEAK;
 
-// Replace-malloc library initialization function. See top of this file
-MOZ_REPLACE_PUBLIC void
-replace_init(malloc_table_t*, struct ReplaceMallocBridge**) MOZ_REPLACE_WEAK;
+// Define the replace_* functions as not exported.
+#define MALLOC_DECL(name, return_type, ...)                                    \
+  return_type replace_##name(__VA_ARGS__);
+#include "malloc_decls.h"
 
 MOZ_END_EXTERN_C
 
 #endif // replace_malloc_h
--- a/memory/replace/dmd/DMD.cpp
+++ b/memory/replace/dmd/DMD.cpp
@@ -86,16 +86,19 @@ StatusMsg(const char* aFmt, ...)
 #ifndef DISALLOW_COPY_AND_ASSIGN
 #define DISALLOW_COPY_AND_ASSIGN(T) \
   T(const T&);                      \
   void operator=(const T&)
 #endif
 
 static malloc_table_t gMallocTable;
 
+// Whether DMD finished initializing.
+static bool gIsDMDInitialized = false;
+
 // This provides infallible allocations (they abort on OOM).  We use it for all
 // of DMD's own allocations, which fall into the following three cases.
 //
 // - Direct allocations (the easy case).
 //
 // - Indirect allocations in js::{Vector,HashSet,HashMap} -- this class serves
 //   as their AllocPolicy.
 //
@@ -1251,59 +1254,85 @@ FreeCallback(void* aPtr, Thread* aT, Dea
     GCStackTraces();
   }
 }
 
 //---------------------------------------------------------------------------
 // malloc/free interception
 //---------------------------------------------------------------------------
 
-static bool Init(malloc_table_t* aMallocTable);
+static void Init(malloc_table_t* aMallocTable);
 
 } // namespace dmd
 } // namespace mozilla
 
-static void*
+void
+replace_init(malloc_table_t* aMallocTable, ReplaceMallocBridge** aBridge)
+{
+  mozilla::dmd::Init(aMallocTable);
+#define MALLOC_FUNCS MALLOC_FUNCS_MALLOC_BASE
+#define MALLOC_DECL(name, ...) aMallocTable->name = replace_ ## name;
+#include "malloc_decls.h"
+  *aBridge = mozilla::dmd::gDMDBridge;
+}
+
+void*
 replace_malloc(size_t aSize)
 {
   using namespace mozilla::dmd;
 
+  if (!gIsDMDInitialized) {
+    // DMD hasn't started up, either because it wasn't enabled by the user, or
+    // we're still in Init() and something has indirectly called malloc.  Do a
+    // vanilla malloc.  (In the latter case, if it fails we'll crash.  But
+    // OOM is highly unlikely so early on.)
+    return gMallocTable.malloc(aSize);
+  }
+
   Thread* t = Thread::Fetch();
   if (t->InterceptsAreBlocked()) {
     // Intercepts are blocked, which means this must be a call to malloc
     // triggered indirectly by DMD (e.g. via MozStackWalk).  Be infallible.
     return InfallibleAllocPolicy::malloc_(aSize);
   }
 
   // This must be a call to malloc from outside DMD.  Intercept it.
   void* ptr = gMallocTable.malloc(aSize);
   AllocCallback(ptr, aSize, t);
   return ptr;
 }
 
-static void*
+void*
 replace_calloc(size_t aCount, size_t aSize)
 {
   using namespace mozilla::dmd;
 
+  if (!gIsDMDInitialized) {
+    return gMallocTable.calloc(aCount, aSize);
+  }
+
   Thread* t = Thread::Fetch();
   if (t->InterceptsAreBlocked()) {
     return InfallibleAllocPolicy::calloc_(aCount * aSize);
   }
 
   void* ptr = gMallocTable.calloc(aCount, aSize);
   AllocCallback(ptr, aCount * aSize, t);
   return ptr;
 }
 
-static void*
+void*
 replace_realloc(void* aOldPtr, size_t aSize)
 {
   using namespace mozilla::dmd;
 
+  if (!gIsDMDInitialized) {
+    return gMallocTable.realloc(aOldPtr, aSize);
+  }
+
   Thread* t = Thread::Fetch();
   if (t->InterceptsAreBlocked()) {
     return InfallibleAllocPolicy::realloc_(aOldPtr, aSize);
   }
 
   // If |aOldPtr| is nullptr, the call is equivalent to |malloc(aSize)|.
   if (!aOldPtr) {
     return replace_malloc(aSize);
@@ -1326,61 +1355,59 @@ replace_realloc(void* aOldPtr, size_t aS
     // block will end up looking like it was allocated for the first time here,
     // which is untrue, and the slop bytes will be zero, which may be untrue.
     // But this case is rare and doing better isn't worth the effort.
     AllocCallback(aOldPtr, gMallocTable.malloc_usable_size(aOldPtr), t);
   }
   return ptr;
 }
 
-static void*
+void*
 replace_memalign(size_t aAlignment, size_t aSize)
 {
   using namespace mozilla::dmd;
 
+  if (!gIsDMDInitialized) {
+    return gMallocTable.memalign(aAlignment, aSize);
+  }
+
   Thread* t = Thread::Fetch();
   if (t->InterceptsAreBlocked()) {
     return InfallibleAllocPolicy::memalign_(aAlignment, aSize);
   }
 
   void* ptr = gMallocTable.memalign(aAlignment, aSize);
   AllocCallback(ptr, aSize, t);
   return ptr;
 }
 
-static void
+void
 replace_free(void* aPtr)
 {
   using namespace mozilla::dmd;
 
+  if (!gIsDMDInitialized) {
+    gMallocTable.free(aPtr);
+    return;
+  }
+
   Thread* t = Thread::Fetch();
   if (t->InterceptsAreBlocked()) {
     return InfallibleAllocPolicy::free_(aPtr);
   }
 
   // Do the actual free after updating the table.  Otherwise, another thread
   // could call malloc and get the freed block and update the table, and then
   // our update here would remove the newly-malloc'd block.
   DeadBlock db;
   FreeCallback(aPtr, t, &db);
   MaybeAddToDeadBlockTable(db);
   gMallocTable.free(aPtr);
 }
 
-void
-replace_init(malloc_table_t* aMallocTable, ReplaceMallocBridge** aBridge)
-{
-  if (mozilla::dmd::Init(aMallocTable)) {
-#define MALLOC_FUNCS MALLOC_FUNCS_MALLOC_BASE
-#define MALLOC_DECL(name, ...) aMallocTable->name = replace_ ## name;
-#include "malloc_decls.h"
-    *aBridge = mozilla::dmd::gDMDBridge;
-  }
-}
-
 namespace mozilla {
 namespace dmd {
 
 //---------------------------------------------------------------------------
 // Options (Part 2)
 //---------------------------------------------------------------------------
 
 // Given an |aOptionName| like "foo", succeed if |aArg| has the form "foo=blah"
@@ -1435,16 +1462,19 @@ Options::GetBool(const char* aArg, const
 
 Options::Options(const char* aDMDEnvVar)
   : mDMDEnvVar(aDMDEnvVar ? InfallibleAllocPolicy::strdup_(aDMDEnvVar)
                           : nullptr)
   , mMode(Mode::DarkMatter)
   , mStacks(Stacks::Partial)
   , mShowDumpStats(false)
 {
+  // It's no longer necessary to set the DMD env var to "1" if you want default
+  // options (you can leave it undefined) but we still accept "1" for
+  // backwards compatibility.
   char* e = mDMDEnvVar;
   if (e && strcmp(e, "1") != 0) {
     bool isEnd = false;
     while (!isEnd) {
       // Consume leading whitespace.
       while (isspace(*e)) {
         e++;
       }
@@ -1544,42 +1574,41 @@ postfork()
     gStateLock->Unlock();
   }
 }
 
 // WARNING: this function runs *very* early -- before all static initializers
 // have run.  For this reason, non-scalar globals such as gStateLock and
 // gStackTraceTable are allocated dynamically (so we can guarantee their
 // construction in this function) rather than statically.
-static bool
+static void
 Init(malloc_table_t* aMallocTable)
 {
-  // DMD is controlled by the |DMD| environment variable.
-  const char* e = getenv("DMD");
-
-  if (!e) {
-    return false;
-  }
-  // Initialize the function table first, because StatusMsg uses
-  // InfallibleAllocPolicy::malloc_, which uses it.
   gMallocTable = *aMallocTable;
-
-  StatusMsg("$DMD = '%s'\n", e);
-
   gDMDBridge = InfallibleAllocPolicy::new_<DMDBridge>();
 
 #ifndef XP_WIN
   // Avoid deadlocks when forking by acquiring our state lock prior to forking
   // and releasing it after forking. See |LogAlloc|'s |replace_init| for
   // in-depth details.
   //
   // Note: This must run after attempting an allocation so as to give the
   // system malloc a chance to insert its own atfork handler.
   pthread_atfork(prefork, postfork, postfork);
 #endif
+
+  // DMD is controlled by the |DMD| environment variable.
+  const char* e = getenv("DMD");
+
+  if (e) {
+    StatusMsg("$DMD = '%s'\n", e);
+  } else {
+    StatusMsg("$DMD is undefined\n");
+  }
+
   // Parse $DMD env var.
   gOptions = InfallibleAllocPolicy::new_<Options>(e);
 
   gStateLock = InfallibleAllocPolicy::new_<Mutex>();
 
   gBernoulli = (FastBernoulliTrial*)
     InfallibleAllocPolicy::malloc_(sizeof(FastBernoulliTrial));
   ResetBernoulli();
@@ -1598,17 +1627,17 @@ Init(malloc_table_t* aMallocTable)
     // Create this even if the mode isn't Cumulative (albeit with a small
     // size), in case the mode is changed later on (as is done by SmokeDMD.cpp,
     // for example).
     gDeadBlockTable = InfallibleAllocPolicy::new_<DeadBlockTable>();
     size_t tableSize = gOptions->IsCumulativeMode() ? 8192 : 4;
     MOZ_ALWAYS_TRUE(gDeadBlockTable->init(tableSize));
   }
 
-  return true;
+  gIsDMDInitialized = true;
 }
 
 //---------------------------------------------------------------------------
 // Block reporting and unreporting
 //---------------------------------------------------------------------------
 
 static void
 ReportHelper(const void* aPtr, bool aReportedOnAlloc)
--- a/memory/replace/dmd/moz.build
+++ b/memory/replace/dmd/moz.build
@@ -4,28 +4,24 @@
 # 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/.
 
 EXPORTS += [
     'DMD.h',
 ]
 
 UNIFIED_SOURCES += [
+    '../../../mfbt/HashFunctions.cpp',
+    '../../../mfbt/JSONWriter.cpp',
+    '../../../mfbt/Poison.cpp',
+    '../../../mozglue/misc/StackWalk.cpp',
     'DMD.cpp',
 ]
 
-if not CONFIG['MOZ_REPLACE_MALLOC_STATIC']:
-    UNIFIED_SOURCES += [
-        '/mfbt/HashFunctions.cpp',
-        '/mfbt/JSONWriter.cpp',
-        '/mfbt/Poison.cpp',
-        '/mozglue/misc/StackWalk.cpp',
-    ]
-
-ReplaceMalloc('dmd')
+SharedLibrary('dmd')
 
 DEFINES['MOZ_NO_MOZALLOC'] = True
 DEFINES['IMPL_MFBT'] = True
 DEFINES['XPCOM_GLUE'] = True
 
 if CONFIG['MOZ_OPTIMIZE']:
     DEFINES['MOZ_OPTIMIZE'] = True
 
--- a/memory/replace/dmd/test/script-diff-dark-matter-expected.txt
+++ b/memory/replace/dmd/test/script-diff-dark-matter-expected.txt
@@ -2,17 +2,17 @@
 # dmd.py --filter-stacks-for-testing -o script-diff-dark-matter-actual.txt script-diff-dark-matter1.json script-diff-dark-matter2.json
 
 Invocation 1 {
   $DMD = '--mode=dark-matter'
   Mode = 'dark-matter'
 }
 
 Invocation 2 {
-  $DMD = '1'
+  $DMD is undefined
   Mode = 'dark-matter'
 }
 
 #-----------------------------------------------------------------
 
 Twice-reported {
   -1 blocks in heap block record 1 of 1
   -1,088 bytes (-1,064 requested / -24 slop)
--- a/memory/replace/dmd/test/script-diff-dark-matter2.json
+++ b/memory/replace/dmd/test/script-diff-dark-matter2.json
@@ -1,12 +1,12 @@
 {
  "version": 5,
  "invocation": {
-  "dmdEnvVar": "1",
+  "dmdEnvVar": null,
   "mode": "dark-matter"
  },
  "blockList": [
   {"req": 4096, "alloc": "A", "num": 4},
 
   {"req": 8192, "alloc": "B"},
   {"req": 8192, "alloc": "B"},
 
--- a/memory/replace/dmd/test/script-ignore-alloc-fns-expected.txt
+++ b/memory/replace/dmd/test/script-ignore-alloc-fns-expected.txt
@@ -1,13 +1,13 @@
 #-----------------------------------------------------------------
 # dmd.py --filter-stacks-for-testing -o script-ignore-alloc-fns-actual.txt --ignore-alloc-fns script-ignore-alloc-fns.json
 
 Invocation {
-  $DMD = '1'
+  $DMD is undefined
   Mode = 'dark-matter'
 }
 
 #-----------------------------------------------------------------
 
 # no twice-reported heap blocks
 
 #-----------------------------------------------------------------
--- a/memory/replace/dmd/test/script-ignore-alloc-fns.json
+++ b/memory/replace/dmd/test/script-ignore-alloc-fns.json
@@ -1,12 +1,12 @@
 {
  "version": 5,
  "invocation": {
-  "dmdEnvVar": "1",
+  "dmdEnvVar": null,
   "mode": "dark-matter"
  },
  "blockList": [
   {"req": 1048576,           "alloc": "A"},
   {"req": 65536,             "alloc": "B"},
   {"req": 8000, "slop": 192, "alloc": "C"},
   {"req": 2500,              "alloc": "D"}
  ],
--- a/memory/replace/dmd/test/test_dmd.js
+++ b/memory/replace/dmd/test/test_dmd.js
@@ -131,17 +131,17 @@ function run_test() {
   // These tests do complete end-to-end testing of DMD, i.e. both the C++ code
   // that generates the JSON output, and the script that post-processes that
   // output.
   //
   // Run these synchronously, because test() updates the complete*.json files
   // in-place (to fix stacks) when it runs dmd.py, and that's not safe to do
   // asynchronously.
 
-  gEnv.set('DMD', '1');
+  gEnv.set(gEnv.get("DMD_PRELOAD_VAR"), gEnv.get("DMD_PRELOAD_VALUE"));
 
   runProcess(gDmdTestFile, []);
 
   function test2(aTestName, aMode) {
     let name = "complete-" + aTestName + "-" + aMode;
     jsonFile = FileUtils.getFile("CurWorkD", [name + ".json"]);
     test(name, [jsonFile.path]);
     jsonFile.remove(true);
--- a/memory/replace/logalloc/LogAlloc.cpp
+++ b/memory/replace/logalloc/LogAlloc.cpp
@@ -68,128 +68,65 @@ class LogAllocBridge : public ReplaceMal
 {
   virtual void InitDebugFd(mozilla::DebugFdRegistry& aRegistry) override {
     if (!sStdoutOrStderr) {
       aRegistry.RegisterHandle(sFd);
     }
   }
 };
 
-/* Do a simple, text-form, log of all calls to replace-malloc functions.
- * Use locking to guarantee that an allocation that did happen is logged
- * before any other allocation/free happens.
- */
-
-static void*
-replace_malloc(size_t aSize)
-{
-  AutoLock lock(sLock);
-  void* ptr = sFuncs.malloc(aSize);
-  if (ptr) {
-    FdPrintf(sFd, "%zu %zu malloc(%zu)=%p\n", GetPid(), GetTid(), aSize, ptr);
-  }
-  return ptr;
-}
-
-#ifndef LOGALLOC_MINIMAL
-static int
-replace_posix_memalign(void** aPtr, size_t aAlignment, size_t aSize)
-{
-  AutoLock lock(sLock);
-  int ret = sFuncs.posix_memalign(aPtr, aAlignment, aSize);
-  if (ret == 0) {
-    FdPrintf(sFd, "%zu %zu posix_memalign(%zu,%zu)=%p\n", GetPid(), GetTid(),
-             aAlignment, aSize, *aPtr);
-  }
-  return ret;
-}
-
-static void*
-replace_aligned_alloc(size_t aAlignment, size_t aSize)
-{
-  AutoLock lock(sLock);
-  void* ptr = sFuncs.aligned_alloc(aAlignment, aSize);
-  if (ptr) {
-    FdPrintf(sFd, "%zu %zu aligned_alloc(%zu,%zu)=%p\n", GetPid(), GetTid(),
-             aAlignment, aSize, ptr);
-  }
-  return ptr;
-}
-#endif
-
-static void*
-replace_calloc(size_t aNum, size_t aSize)
-{
-  AutoLock lock(sLock);
-  void* ptr = sFuncs.calloc(aNum, aSize);
-  if (ptr) {
-    FdPrintf(sFd, "%zu %zu calloc(%zu,%zu)=%p\n", GetPid(), GetTid(), aNum,
-             aSize, ptr);
-  }
-  return ptr;
-}
-
-static void*
-replace_realloc(void* aPtr, size_t aSize)
-{
-  AutoLock lock(sLock);
-  void* new_ptr = sFuncs.realloc(aPtr, aSize);
-  if (new_ptr || !aSize) {
-    FdPrintf(sFd, "%zu %zu realloc(%p,%zu)=%p\n", GetPid(), GetTid(), aPtr,
-             aSize, new_ptr);
-  }
-  return new_ptr;
-}
-
-static void
-replace_free(void* aPtr)
-{
-  AutoLock lock(sLock);
-  if (aPtr) {
-    FdPrintf(sFd, "%zu %zu free(%p)\n", GetPid(), GetTid(), aPtr);
-  }
-  sFuncs.free(aPtr);
-}
-
-static void*
-replace_memalign(size_t aAlignment, size_t aSize)
-{
-  AutoLock lock(sLock);
-  void* ptr = sFuncs.memalign(aAlignment, aSize);
-  if (ptr) {
-    FdPrintf(sFd, "%zu %zu memalign(%zu,%zu)=%p\n", GetPid(), GetTid(),
-             aAlignment, aSize, ptr);
-  }
-  return ptr;
-}
-
-#ifndef LOGALLOC_MINIMAL
-static void*
-replace_valloc(size_t aSize)
-{
-  AutoLock lock(sLock);
-  void* ptr = sFuncs.valloc(aSize);
-  if (ptr) {
-    FdPrintf(sFd, "%zu %zu valloc(%zu)=%p\n", GetPid(), GetTid(), aSize, ptr);
-  }
-  return ptr;
-}
-#endif
-
-static void
-replace_jemalloc_stats(jemalloc_stats_t* aStats)
-{
-  AutoLock lock(sLock);
-  sFuncs.jemalloc_stats(aStats);
-  FdPrintf(sFd, "%zu %zu jemalloc_stats()\n", GetPid(), GetTid());
-}
-
 void
 replace_init(malloc_table_t* aTable, ReplaceMallocBridge** aBridge)
 {
+  static LogAllocBridge bridge;
+  sFuncs = *aTable;
+#define MALLOC_FUNCS MALLOC_FUNCS_MALLOC_BASE
+#define MALLOC_DECL(name, ...) aTable->name = replace_ ## name;
+#include "malloc_decls.h"
+  aTable->jemalloc_stats = replace_jemalloc_stats;
+#ifndef LOGALLOC_MINIMAL
+  aTable->posix_memalign = replace_posix_memalign;
+  aTable->aligned_alloc = replace_aligned_alloc;
+  aTable->valloc = replace_valloc;
+#endif
+  *aBridge = &bridge;
+
+#ifndef _WIN32
+  /* When another thread has acquired a lock before forking, the child
+   * process will inherit the lock state but the thread, being nonexistent
+   * in the child process, will never release it, leading to a dead-lock
+   * whenever the child process gets the lock. We thus need to ensure no
+   * other thread is holding the lock before forking, by acquiring it
+   * ourselves, and releasing it after forking, both in the parent and child
+   * processes.
+   * Windows doesn't have this problem since there is no fork().
+   * The real allocator, however, might be doing the same thing (jemalloc
+   * does). But pthread_atfork `prepare` handlers (first argument) are
+   * processed in reverse order they were established. But replace_init
+   * runs before the real allocator has had any chance to initialize and
+   * call pthread_atfork itself. This leads to its prefork running before
+   * ours. This leads to a race condition that can lead to a deadlock like
+   * the following:
+   *   - thread A forks.
+   *   - libc calls real allocator's prefork, so thread A holds the real
+   *     allocator lock.
+   *   - thread B calls malloc, which calls our replace_malloc.
+   *   - consequently, thread B holds our lock.
+   *   - thread B then proceeds to call the real allocator's malloc, and
+   *     waits for the real allocator's lock, which thread A holds.
+   *   - libc calls our prefork, so thread A waits for our lock, which
+   *     thread B holds.
+   * To avoid this race condition, the real allocator's prefork must be
+   * called after ours, which means it needs to be registered before ours.
+   * So trick the real allocator into initializing itself without more side
+   * effects by calling malloc with a size it can't possibly allocate. */
+  sFuncs.malloc(-1);
+  pthread_atfork(prefork, postfork, postfork);
+#endif
+
   /* Initialize output file descriptor from the MALLOC_LOG environment
    * variable. Numbers up to 9999 are considered as a preopened file
    * descriptor number. Other values are considered as a file name. */
   char* log = getenv("MALLOC_LOG");
   if (log && *log) {
     int fd = 0;
     const char *fd_num = log;
     while (*fd_num) {
@@ -226,60 +163,118 @@ replace_init(malloc_table_t* aTable, Rep
     if (fd == -1) {
       fd = open(log, O_WRONLY | O_CREAT | O_APPEND, 0644);
     }
     if (fd > 0) {
       sFd = fd;
     }
 #endif
   }
+}
 
-  // Don't initialize if we weren't passed a valid MALLOC_LOG.
-  if (sFd == 0) {
-    return;
+/* Do a simple, text-form, log of all calls to replace-malloc functions.
+ * Use locking to guarantee that an allocation that did happen is logged
+ * before any other allocation/free happens.
+ */
+
+void*
+replace_malloc(size_t aSize)
+{
+  AutoLock lock(sLock);
+  void* ptr = sFuncs.malloc(aSize);
+  if (ptr) {
+    FdPrintf(sFd, "%zu %zu malloc(%zu)=%p\n", GetPid(), GetTid(), aSize, ptr);
   }
+  return ptr;
+}
 
-  static LogAllocBridge bridge;
-  sFuncs = *aTable;
-#define MALLOC_FUNCS MALLOC_FUNCS_MALLOC_BASE
-#define MALLOC_DECL(name, ...) aTable->name = replace_ ## name;
-#include "malloc_decls.h"
-  aTable->jemalloc_stats = replace_jemalloc_stats;
 #ifndef LOGALLOC_MINIMAL
-  aTable->posix_memalign = replace_posix_memalign;
-  aTable->aligned_alloc = replace_aligned_alloc;
-  aTable->valloc = replace_valloc;
+int
+replace_posix_memalign(void** aPtr, size_t aAlignment, size_t aSize)
+{
+  AutoLock lock(sLock);
+  int ret = sFuncs.posix_memalign(aPtr, aAlignment, aSize);
+  if (ret == 0) {
+    FdPrintf(sFd, "%zu %zu posix_memalign(%zu,%zu)=%p\n", GetPid(), GetTid(),
+             aAlignment, aSize, *aPtr);
+  }
+  return ret;
+}
+
+void*
+replace_aligned_alloc(size_t aAlignment, size_t aSize)
+{
+  AutoLock lock(sLock);
+  void* ptr = sFuncs.aligned_alloc(aAlignment, aSize);
+  if (ptr) {
+    FdPrintf(sFd, "%zu %zu aligned_alloc(%zu,%zu)=%p\n", GetPid(), GetTid(),
+             aAlignment, aSize, ptr);
+  }
+  return ptr;
+}
 #endif
-  *aBridge = &bridge;
 
-#ifndef _WIN32
-  /* When another thread has acquired a lock before forking, the child
-   * process will inherit the lock state but the thread, being nonexistent
-   * in the child process, will never release it, leading to a dead-lock
-   * whenever the child process gets the lock. We thus need to ensure no
-   * other thread is holding the lock before forking, by acquiring it
-   * ourselves, and releasing it after forking, both in the parent and child
-   * processes.
-   * Windows doesn't have this problem since there is no fork().
-   * The real allocator, however, might be doing the same thing (jemalloc
-   * does). But pthread_atfork `prepare` handlers (first argument) are
-   * processed in reverse order they were established. But replace_init
-   * runs before the real allocator has had any chance to initialize and
-   * call pthread_atfork itself. This leads to its prefork running before
-   * ours. This leads to a race condition that can lead to a deadlock like
-   * the following:
-   *   - thread A forks.
-   *   - libc calls real allocator's prefork, so thread A holds the real
-   *     allocator lock.
-   *   - thread B calls malloc, which calls our replace_malloc.
-   *   - consequently, thread B holds our lock.
-   *   - thread B then proceeds to call the real allocator's malloc, and
-   *     waits for the real allocator's lock, which thread A holds.
-   *   - libc calls our prefork, so thread A waits for our lock, which
-   *     thread B holds.
-   * To avoid this race condition, the real allocator's prefork must be
-   * called after ours, which means it needs to be registered before ours.
-   * So trick the real allocator into initializing itself without more side
-   * effects by calling malloc with a size it can't possibly allocate. */
-  sFuncs.malloc(-1);
-  pthread_atfork(prefork, postfork, postfork);
+void*
+replace_calloc(size_t aNum, size_t aSize)
+{
+  AutoLock lock(sLock);
+  void* ptr = sFuncs.calloc(aNum, aSize);
+  if (ptr) {
+    FdPrintf(sFd, "%zu %zu calloc(%zu,%zu)=%p\n", GetPid(), GetTid(), aNum,
+             aSize, ptr);
+  }
+  return ptr;
+}
+
+void*
+replace_realloc(void* aPtr, size_t aSize)
+{
+  AutoLock lock(sLock);
+  void* new_ptr = sFuncs.realloc(aPtr, aSize);
+  if (new_ptr || !aSize) {
+    FdPrintf(sFd, "%zu %zu realloc(%p,%zu)=%p\n", GetPid(), GetTid(), aPtr,
+             aSize, new_ptr);
+  }
+  return new_ptr;
+}
+
+void
+replace_free(void* aPtr)
+{
+  AutoLock lock(sLock);
+  if (aPtr) {
+    FdPrintf(sFd, "%zu %zu free(%p)\n", GetPid(), GetTid(), aPtr);
+  }
+  sFuncs.free(aPtr);
+}
+
+void*
+replace_memalign(size_t aAlignment, size_t aSize)
+{
+  AutoLock lock(sLock);
+  void* ptr = sFuncs.memalign(aAlignment, aSize);
+  if (ptr) {
+    FdPrintf(sFd, "%zu %zu memalign(%zu,%zu)=%p\n", GetPid(), GetTid(),
+             aAlignment, aSize, ptr);
+  }
+  return ptr;
+}
+
+#ifndef LOGALLOC_MINIMAL
+void*
+replace_valloc(size_t aSize)
+{
+  AutoLock lock(sLock);
+  void* ptr = sFuncs.valloc(aSize);
+  if (ptr) {
+    FdPrintf(sFd, "%zu %zu valloc(%zu)=%p\n", GetPid(), GetTid(), aSize, ptr);
+  }
+  return ptr;
+}
 #endif
+
+void
+replace_jemalloc_stats(jemalloc_stats_t* aStats)
+{
+  AutoLock lock(sLock);
+  sFuncs.jemalloc_stats(aStats);
+  FdPrintf(sFd, "%zu %zu jemalloc_stats()\n", GetPid(), GetTid());
 }
--- a/memory/replace/logalloc/README
+++ b/memory/replace/logalloc/README
@@ -1,17 +1,29 @@
 Logalloc is a replace-malloc library for Firefox (see
 memory/build/replace_malloc.h) that dumps a log of memory allocations to a
 given file descriptor or file name. That log can then be replayed against
 Firefox's default memory allocator independently or through another
 replace-malloc library, allowing the testing of other allocators under the
 exact same workload.
 
-To get an allocation log the following environment variable when starting
-Firefox:
+To get an allocation log the following environment variables need to be set
+when starting Firefox:
+- on Linux:
+  LD_PRELOAD=/path/to/liblogalloc.so
+- on Mac OSX:
+  DYLD_INSERT_LIBRARIES=/path/to/liblogalloc.dylib
+- on Windows:
+  MOZ_REPLACE_MALLOC_LIB=/path/to/logalloc.dll
+- on Android:
+  MOZ_REPLACE_MALLOC_LIB=/path/to/liblogalloc.so
+  (see https://wiki.mozilla.org/Mobile/Fennec/Android#Arguments_and_Environment_Variables
+  for how to pass environment variables to Firefox for Android)
+
+- on all platforms:
   MALLOC_LOG=/path/to/log-file
   or
   MALLOC_LOG=number
 
 When MALLOC_LOG is a number below 10000, it is considered as a file
 descriptor number that is fed to Firefox when it is started. Otherwise,
 it is considered as a file name.
 
--- a/memory/replace/logalloc/logalloc.mozbuild
+++ b/memory/replace/logalloc/logalloc.mozbuild
@@ -5,16 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 SOURCES += [
     'FdPrintf.cpp',
     'LogAlloc.cpp',
 ]
 
 DisableStlWrapping()
+USE_STATIC_LIBS = True
 NO_PGO = True
 DEFINES['MOZ_NO_MOZALLOC'] = True
 # Avoid Lock_impl code depending on mozilla::Logger.
 DEFINES['NDEBUG'] = True
 DEFINES['DEBUG'] = False
 
 # Use locking code from the chromium stack.
 if CONFIG['OS_TARGET'] == 'WINNT':
@@ -24,12 +25,12 @@ if CONFIG['OS_TARGET'] == 'WINNT':
 else:
     SOURCES += [
         '../../../ipc/chromium/src/base/lock_impl_posix.cc',
     ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 # Android doesn't have pthread_atfork, but we have our own in mozglue.
-if CONFIG['OS_TARGET'] == 'Android' and FORCE_SHARED_LIB:
+if CONFIG['OS_TARGET'] == 'Android':
     USE_LIBS += [
         'mozglue',
     ]
--- a/memory/replace/logalloc/moz.build
+++ b/memory/replace/logalloc/moz.build
@@ -1,14 +1,14 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
-ReplaceMalloc('logalloc')
+SharedLibrary('logalloc')
 
 include('logalloc.mozbuild')
 
 DIRS += [
     'minimal',
     'replay',
 ]
--- a/memory/replace/logalloc/replay/Makefile.in
+++ b/memory/replace/logalloc/replay/Makefile.in
@@ -11,19 +11,17 @@ LOGALLOC_VAR = MOZ_REPLACE_MALLOC_LIB
 else
 ifeq ($(OS_TARGET),Darwin)
 LOGALLOC_VAR = DYLD_INSERT_LIBRARIES
 else
 LOGALLOC_VAR = LD_PRELOAD
 endif
 endif
 
-ifndef MOZ_REPLACE_MALLOC_STATIC
 LOGALLOC = $(LOGALLOC_VAR)=$(CURDIR)/../$(DLL_PREFIX)logalloc$(DLL_SUFFIX)
-endif
 LOGALLOC_MINIMAL = $(LOGALLOC_VAR)=$(CURDIR)/../minimal/$(DLL_PREFIX)logalloc_minimal$(DLL_SUFFIX)
 
 expected_output.log: $(srcdir)/replay.log
 # The logalloc-replay program will only replay entries from the first pid,
 # so the expected output only contains entries beginning with "1 "
 	grep "^1 " $< > $@
 
 check:: $(srcdir)/replay.log expected_output.log $(srcdir)/expected_output_minimal.log
--- a/memory/replace/logalloc/replay/moz.build
+++ b/memory/replace/logalloc/replay/moz.build
@@ -2,34 +2,22 @@
 # vim: set filetype=python:
 # 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/.
 
 Program('logalloc-replay')
 
 SOURCES += [
+    '../FdPrintf.cpp',
     '/mfbt/Assertions.cpp',
     '/mfbt/Unused.cpp',
     'Replay.cpp',
 ]
 
-if CONFIG['MOZ_REPLACE_MALLOC_STATIC'] and CONFIG['MOZ_DMD']:
-    UNIFIED_SOURCES += [
-        '/mfbt/HashFunctions.cpp',
-        '/mfbt/JSONWriter.cpp',
-        '/mfbt/Poison.cpp',
-        '/mozglue/misc/StackWalk.cpp',
-    ]
-
-if not CONFIG['MOZ_REPLACE_MALLOC_STATIC']:
-    SOURCES += [
-        '../FdPrintf.cpp',
-    ]
-
 LOCAL_INCLUDES += [
     '..',
 ]
 
 # Link replace-malloc and the default allocator.
 USE_LIBS += [
     'memory',
 ]
--- a/memory/replace/moz.build
+++ b/memory/replace/moz.build
@@ -1,20 +1,12 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
-@template
-def ReplaceMalloc(name):
-    if CONFIG['MOZ_REPLACE_MALLOC_STATIC']:
-        DEFINES['MOZ_REPLACE_MALLOC_PREFIX'] = name.replace('-', '_')
-        FINAL_LIBRARY = 'memory'
-    else:
-        SharedLibrary(name)
-
 DIRS += [
     'logalloc',
 ]
 
 if CONFIG['MOZ_DMD']:
     DIRS += ['dmd']
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -915,20 +915,43 @@ class RunProgram(MachCommandBase):
 
             if mode:
                 dmd_params.append('--mode=' + mode)
             if stacks:
                 dmd_params.append('--stacks=' + stacks)
             if show_dump_stats:
                 dmd_params.append('--show-dump-stats=yes')
 
+            bin_dir = os.path.dirname(binpath)
+            lib_name = self.substs['DLL_PREFIX'] + 'dmd' + self.substs['DLL_SUFFIX']
+            dmd_lib = os.path.join(bin_dir, lib_name)
+            if not os.path.exists(dmd_lib):
+                print("Please build with |--enable-dmd| to use DMD.")
+                return 1
+
+            env_vars = {
+                "Darwin": {
+                    "DYLD_INSERT_LIBRARIES": dmd_lib,
+                    "LD_LIBRARY_PATH": bin_dir,
+                },
+                "Linux": {
+                    "LD_PRELOAD": dmd_lib,
+                    "LD_LIBRARY_PATH": bin_dir,
+                },
+                "WINNT": {
+                    "MOZ_REPLACE_MALLOC_LIB": dmd_lib,
+                },
+            }
+
+            arch = self.substs['OS_ARCH']
+
             if dmd_params:
-                extra_env['DMD'] = ' '.join(dmd_params)
-            else:
-                extra_env['DMD'] = '1'
+                env_vars[arch]["DMD"] = " ".join(dmd_params)
+
+            extra_env.update(env_vars.get(arch, {}))
 
         return self.run_process(args=args, ensure_exit_code=False,
             pass_thru=True, append_env=extra_env)
 
 @CommandProvider
 class Buildsymbols(MachCommandBase):
     """Produce a package of debug symbols suitable for use with Breakpad."""
 
--- a/testing/awsy/mach_commands.py
+++ b/testing/awsy/mach_commands.py
@@ -139,20 +139,42 @@ class MachCommands(MachCommandBase):
             self.run_process(**unzip_args)
 
         # If '--preferences' was not specified supply our default set.
         if not kwargs['prefs_files']:
             kwargs['prefs_files'] = [os.path.join(awsy_source_dir, 'conf', 'prefs.json')]
 
         # Setup DMD env vars if necessary.
         if kwargs['dmd']:
+            dmd_params = []
+
             bin_dir = os.path.dirname(binary)
+            lib_name = self.substs['DLL_PREFIX'] + 'dmd' + self.substs['DLL_SUFFIX']
+            dmd_lib = os.path.join(bin_dir, lib_name)
+            if not os.path.exists(dmd_lib):
+                print("Please build with |--enable-dmd| to use DMD.")
+                return 1
 
-            if 'DMD' not in os.environ:
-                os.environ['DMD'] = '1'
+            env_vars = {
+                "Darwin": {
+                    "DYLD_INSERT_LIBRARIES": dmd_lib,
+                    "LD_LIBRARY_PATH": bin_dir,
+                },
+                "Linux": {
+                    "LD_PRELOAD": dmd_lib,
+                    "LD_LIBRARY_PATH": bin_dir,
+                },
+                "WINNT": {
+                    "MOZ_REPLACE_MALLOC_LIB": dmd_lib,
+                },
+            }
+
+            arch = self.substs['OS_ARCH']
+            for k, v in env_vars[arch].iteritems():
+                os.environ[k] = v
 
             # Also add the bin dir to the python path so we can use dmd.py
             if bin_dir not in sys.path:
                 sys.path.append(bin_dir)
 
         for k, v in kwargs.iteritems():
             setattr(args, k, v)
 
--- a/testing/mochitest/mochitest_options.py
+++ b/testing/mochitest/mochitest_options.py
@@ -429,16 +429,22 @@ class MochitestArguments(ArgumentContain
           "default": False,
           "help": "Run tests with nested_oop preferences and test filtering enabled.",
           }],
         [["--dmd"],
          {"action": "store_true",
           "default": False,
           "help": "Run tests with DMD active.",
           }],
+        [["--dmd-path"],
+         {"default": None,
+          "dest": "dmdPath",
+          "help": "Specifies the path to the directory containing the shared library for DMD.",
+          "suppress": True,
+          }],
         [["--dump-output-directory"],
          {"default": None,
           "dest": "dumpOutputDirectory",
           "help": "Specifies the directory in which to place dumped memory reports.",
           }],
         [["--dump-about-memory-after-test"],
          {"action": "store_true",
           "default": False,
@@ -690,16 +696,26 @@ class MochitestArguments(ArgumentContain
 
         # allow relative paths
         if options.xrePath:
             options.xrePath = self.get_full_path(options.xrePath, parser.oldcwd)
 
         if options.profilePath:
             options.profilePath = self.get_full_path(options.profilePath, parser.oldcwd)
 
+        if options.dmdPath:
+            options.dmdPath = self.get_full_path(options.dmdPath, parser.oldcwd)
+
+        if options.dmd and not options.dmdPath:
+            if build_obj:
+                options.dmdPath = build_obj.bindir
+            else:
+                parser.error(
+                    "could not find dmd libraries, specify them with --dmd-path")
+
         if options.utilityPath:
             options.utilityPath = self.get_full_path(options.utilityPath, parser.oldcwd)
 
         if options.certPath:
             options.certPath = self.get_full_path(options.certPath, parser.oldcwd)
         elif build_obj:
             options.certPath = os.path.join(build_obj.topsrcdir, 'build', 'pgo', 'certs')
 
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -1617,30 +1617,28 @@ toolbar#nav-bar {
             ubsanPath = SCRIPT_DIR
         else:
             ubsanPath = None
 
         browserEnv = self.environment(
             xrePath=options.xrePath,
             env=env,
             debugger=debugger,
+            dmdPath=options.dmdPath,
             lsanPath=lsanPath,
             ubsanPath=ubsanPath)
 
         if hasattr(options, "topsrcdir"):
             browserEnv["MOZ_DEVELOPER_REPO_DIR"] = options.topsrcdir
         if hasattr(options, "topobjdir"):
             browserEnv["MOZ_DEVELOPER_OBJ_DIR"] = options.topobjdir
 
         if options.headless:
             browserEnv["MOZ_HEADLESS"] = '1'
 
-        if options.dmd:
-            browserEnv["DMD"] = os.environ.get('DMD', '1')
-
         # These variables are necessary for correct application startup; change
         # via the commandline at your own risk.
         browserEnv["XPCOM_DEBUG_BREAK"] = "stack"
 
         # interpolate environment passed with options
         try:
             browserEnv.update(
                 dict(
--- a/testing/mochitest/runtestsremote.py
+++ b/testing/mochitest/runtestsremote.py
@@ -275,18 +275,16 @@ class MochiRemote(MochitestDesktop):
         # remove desktop environment not used on device
         if "XPCOM_MEM_BLOAT_LOG" in browserEnv:
             del browserEnv["XPCOM_MEM_BLOAT_LOG"]
         # override mozLogs to avoid processing in MochitestDesktop base class
         self.mozLogs = None
         browserEnv["MOZ_LOG_FILE"] = os.path.join(
             self.remoteMozLog,
             self.mozLogName)
-        if options.dmd:
-            browserEnv['DMD'] = '1'
         return browserEnv
 
     def runApp(self, *args, **kwargs):
         """front-end automation.py's `runApp` functionality until FennecRunner is written"""
 
         # automation.py/remoteautomation `runApp` takes the profile path,
         # whereas runtest.py's `runApp` takes a mozprofile object.
         if 'profileDir' not in kwargs and 'profile' in kwargs:
@@ -351,16 +349,23 @@ def run_test_harness(parser, options):
     # can be conditional on android_version.
     androidVersion = dm.shellCheckOutput(['getprop', 'ro.build.version.sdk'])
     log.info(
         "Android sdk version '%s'; will use this to filter manifests" %
         str(androidVersion))
     mozinfo.info['android_version'] = androidVersion
 
     deviceRoot = dm.deviceRoot
+    if options.dmdPath:
+        dmdLibrary = "libdmd.so"
+        dmdPathOnDevice = os.path.join(deviceRoot, dmdLibrary)
+        dm.removeFile(dmdPathOnDevice)
+        dm.pushFile(os.path.join(options.dmdPath, dmdLibrary), dmdPathOnDevice)
+        options.dmdPath = deviceRoot
+
     options.dumpOutputDirectory = deviceRoot
 
     procName = options.app.split('/')[-1]
     dm.killProcess(procName)
     if dm.processExist(procName):
         log.warning("unable to kill %s before running tests!" % procName)
 
     mochitest.mozLogName = "moz.log"
--- a/testing/mozbase/mozrunner/mozrunner/utils.py
+++ b/testing/mozbase/mozrunner/mozrunner/utils.py
@@ -79,33 +79,57 @@ def _find_marionette_in_args(*args, **kw
 
 
 def _raw_log():
     import logging
     return logging.getLogger(__name__)
 
 
 def test_environment(xrePath, env=None, crashreporter=True, debugger=False,
-                     lsanPath=None, ubsanPath=None, log=None):
+                     dmdPath=None, lsanPath=None, ubsanPath=None, log=None):
     """
     populate OS environment variables for mochitest and reftests.
 
     Originally comes from automationutils.py. Don't use that for new code.
     """
 
     env = os.environ.copy() if env is None else env
     log = log or _raw_log()
 
     assert os.path.isabs(xrePath)
 
     if mozinfo.isMac:
         ldLibraryPath = os.path.join(os.path.dirname(xrePath), "MacOS")
     else:
         ldLibraryPath = xrePath
 
+    envVar = None
+    dmdLibrary = None
+    preloadEnvVar = None
+    if mozinfo.isUnix:
+        envVar = "LD_LIBRARY_PATH"
+        dmdLibrary = "libdmd.so"
+        preloadEnvVar = "LD_PRELOAD"
+    elif mozinfo.isMac:
+        envVar = "DYLD_LIBRARY_PATH"
+        dmdLibrary = "libdmd.dylib"
+        preloadEnvVar = "DYLD_INSERT_LIBRARIES"
+    elif mozinfo.isWin:
+        envVar = "PATH"
+        dmdLibrary = "dmd.dll"
+        preloadEnvVar = "MOZ_REPLACE_MALLOC_LIB"
+    if envVar:
+        envValue = ((env.get(envVar), str(ldLibraryPath))
+                    if mozinfo.isWin
+                    else (ldLibraryPath, dmdPath, env.get(envVar)))
+        env[envVar] = os.path.pathsep.join([path for path in envValue if path])
+
+    if dmdPath and dmdLibrary and preloadEnvVar:
+        env[preloadEnvVar] = os.path.join(dmdPath, dmdLibrary)
+
     # crashreporter
     env['GNOME_DISABLE_CRASH_DIALOG'] = '1'
     env['XRE_NO_WINDOWS_CRASH_DIALOG'] = '1'
 
     if crashreporter and not debugger:
         env['MOZ_CRASHREPORTER_NO_REPORT'] = '1'
         env['MOZ_CRASHREPORTER'] = '1'
     else:
--- a/testing/xpcshell/runxpcshelltests.py
+++ b/testing/xpcshell/runxpcshelltests.py
@@ -648,18 +648,33 @@ class XPCShellTestThread(Thread):
         cmdC = ['-e', 'const _JSCOV_DIR = null']
         if self.jscovdir:
             cmdC = ['-e', 'const _JSCOV_DIR = "%s"' % self.jscovdir.replace('\\', '/')]
             self.complete_command = cmdH + cmdT + cmdI + cmdC + args
         else:
             self.complete_command = cmdH + cmdT + cmdI + args
 
         if self.test_object.get('dmd') == 'true':
+            if sys.platform.startswith('linux'):
+                preloadEnvVar = 'LD_PRELOAD'
+                libdmd = os.path.join(self.xrePath, 'libdmd.so')
+            elif sys.platform == 'osx' or sys.platform == 'darwin':
+                preloadEnvVar = 'DYLD_INSERT_LIBRARIES'
+                # self.xrePath is <prefix>/Contents/Resources.
+                # We need <prefix>/Contents/MacOS/libdmd.dylib.
+                contents_dir = os.path.dirname(self.xrePath)
+                libdmd = os.path.join(contents_dir, 'MacOS', 'libdmd.dylib')
+            elif sys.platform == 'win32':
+                preloadEnvVar = 'MOZ_REPLACE_MALLOC_LIB'
+                libdmd = os.path.join(self.xrePath, 'dmd.dll')
+
             self.env['PYTHON'] = sys.executable
             self.env['BREAKPAD_SYMBOLS_PATH'] = self.symbolsPath
+            self.env['DMD_PRELOAD_VAR'] = preloadEnvVar
+            self.env['DMD_PRELOAD_VALUE'] = libdmd
 
         if self.test_object.get('subprocess') == 'true':
             self.env['PYTHON'] = sys.executable
 
         if self.test_object.get('headless', False):
             self.env["MOZ_HEADLESS"] = '1'
             self.env["DISPLAY"] = '77'  # Set a fake display.