Bug 1083686 - Tool to capture and reproduce Firefox's memory allocations. r=njn,r=mshal
authorMike Hommey <mh+mozilla@glandium.org>
Fri, 24 Oct 2014 13:08:01 +0900
changeset 224327 ef34805b3a70686e317a4823cae16b194118d0af
parent 224326 3c91b6abb29b5517eaf01b59623565d970456e39
child 224328 4e80b95a9fd902ff8cd37d2ee341240ec9090389
push idunknown
push userunknown
push dateunknown
reviewersnjn, mshal
bugs1083686
milestone36.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 1083686 - Tool to capture and reproduce Firefox's memory allocations. r=njn,r=mshal
memory/replace/logalloc/FdPrintf.cpp
memory/replace/logalloc/FdPrintf.h
memory/replace/logalloc/LogAlloc.cpp
memory/replace/logalloc/Makefile.in
memory/replace/logalloc/README
memory/replace/logalloc/moz.build
memory/replace/logalloc/replay/Makefile.in
memory/replace/logalloc/replay/Replay.cpp
memory/replace/logalloc/replay/logalloc_munge.py
memory/replace/logalloc/replay/moz.build
memory/replace/logalloc/replay/replay.log
memory/replace/moz.build
mozglue/build/Makefile.in
mozglue/build/replace_malloc.mk
new file mode 100644
--- /dev/null
+++ b/memory/replace/logalloc/FdPrintf.cpp
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 <cstdarg>
+
+#ifdef _WIN32
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+#include <cstring>
+#include "mozilla/Assertions.h"
+
+/* Template class allowing a limited number of increments on a value */
+template <typename T>
+class CheckedIncrement
+{
+public:
+  CheckedIncrement(T aValue, size_t aMaxIncrement)
+    : mValue(aValue), mMaxIncrement(aMaxIncrement)
+  {}
+
+  T operator ++(int)
+  {
+    if (!mMaxIncrement) {
+      MOZ_CRASH("overflow detected");
+    }
+    mMaxIncrement--;
+    return mValue++;
+  }
+
+  T& operator ++()
+  {
+    (*this)++;
+    return mValue;
+  }
+
+  operator T() { return mValue; }
+
+private:
+  T mValue;
+  size_t mMaxIncrement;
+};
+
+void
+FdPrintf(int aFd, const char* aFormat, ...)
+{
+  if (aFd == -1) {
+    return;
+  }
+  char buf[256];
+  CheckedIncrement<char*> b(buf, sizeof(buf));
+  CheckedIncrement<const char*> f(aFormat, strlen(aFormat) + 1);
+  va_list ap;
+  va_start(ap, aFormat);
+  while (true) {
+    switch (*f) {
+      case '\0':
+        goto out;
+
+      case '%':
+        switch (*++f) {
+          case 'z': {
+            if (*(++f) == 'u') {
+              size_t i = va_arg(ap, size_t);
+              size_t x = 1;
+              // Compute the number of digits.
+              while (x <= i / 10) {
+                x *= 10;
+              }
+              // Write the digits into the buffer.
+              do {
+                *(b++) = "0123456789"[(i / x) % 10];
+                x /= 10;
+              } while (x > 0);
+            } else {
+              // Write out the format specifier if it's unknown.
+              *(b++) = '%';
+              *(b++) = 'z';
+              *(b++) = *f;
+            }
+            break;
+          }
+
+          case 'p': {
+            intptr_t ptr = va_arg(ap, intptr_t);
+            *(b++) = '0';
+            *(b++) = 'x';
+            int x = sizeof(intptr_t) * 8;
+            bool wrote_msb = false;
+            do {
+              x -= 4;
+              size_t hex_digit = ptr >> x & 0xf;
+              if (hex_digit || wrote_msb) {
+                *(b++) = "0123456789abcdef"[hex_digit];
+                wrote_msb = true;
+              }
+            } while (x > 0);
+            if (!wrote_msb) {
+              *(b++) = '0';
+            }
+            break;
+          }
+
+          default:
+            // Write out the format specifier if it's unknown.
+            *(b++) = '%';
+            *(b++) = *f;
+            break;
+        }
+        break;
+
+      default:
+        *(b++) = *f;
+        break;
+    }
+    f++;
+  }
+out:
+  write(aFd, buf, b - buf);
+  va_end(ap);
+}
new file mode 100644
--- /dev/null
+++ b/memory/replace/logalloc/FdPrintf.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 __FdPrintf_h__
+#define __FdPrintf_h__
+
+/* We can't use libc's (f)printf because it would reenter in replace_malloc,
+ * So use a custom and simplified version.
+ * Only %p and %z are supported.
+ * /!\ This function used a fixed-size internal buffer. The caller is
+ * expected to not use a format string that may overflow.
+ */
+extern void FdPrintf(int aFd, const char* aFormat, ...)
+#ifdef __GNUC__
+__attribute__((format(printf, 2, 3)))
+#endif
+;
+
+#endif /* __FdPrintf_h__ */
new file mode 100644
--- /dev/null
+++ b/memory/replace/logalloc/LogAlloc.cpp
@@ -0,0 +1,201 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 <cstdlib>
+#include <cstdio>
+#include <fcntl.h>
+
+#ifdef _WIN32
+#include <windows.h>
+#include <io.h>
+#include <process.h>
+#else
+#include <unistd.h>
+#include <pthread.h>
+#endif
+
+#include "replace_malloc.h"
+#include "FdPrintf.h"
+#include "mozilla/NullPtr.h"
+
+#include "base/lock.h"
+
+static const malloc_table_t* sFuncs = nullptr;
+static int sFd = -1;
+
+static Lock sLock;
+
+static void
+prefork() {
+  sLock.Acquire();
+}
+
+static void
+postfork() {
+  sLock.Release();
+}
+
+#ifdef ANDROID
+/* See mozglue/android/APKOpen.cpp */
+extern "C" MOZ_EXPORT __attribute__((weak))
+void* __dso_handle;
+
+/* Android doesn't have pthread_atfork defined in pthread.h */
+extern "C" MOZ_EXPORT
+int pthread_atfork(void (*)(void), void (*)(void), void (*)(void));
+#endif
+
+void
+replace_init(const malloc_table_t* aTable)
+{
+  sFuncs = aTable;
+
+#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(). */
+  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) {
+    sFd = 0;
+    const char *fd_num = log;
+    while (*fd_num) {
+      /* Reject non digits. */
+      if (*fd_num < '0' || *fd_num > '9') {
+        sFd = -1;
+        break;
+      }
+      sFd = sFd * 10 + (*fd_num - '0');
+      /* Reject values >= 10000. */
+      if (sFd >= 10000) {
+        sFd = -1;
+        break;
+      }
+      fd_num++;
+    }
+    if (sFd == -1) {
+      sFd = open(log, O_WRONLY | O_CREAT | O_APPEND, 0644);
+    }
+  }
+}
+
+/* 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.
+ * TODO: Add a thread id to the log: different allocators, or even different
+ * configurations of jemalloc behave differently when allocations are coming
+ * from different threads. Reproducing those multi-threaded workloads would be
+ * useful to test those differences.
+ */
+
+void*
+replace_malloc(size_t aSize)
+{
+  AutoLock lock(sLock);
+  void* ptr = sFuncs->malloc(aSize);
+  if (ptr) {
+    FdPrintf(sFd, "%zu malloc(%zu)=%p\n", size_t(getpid()), aSize, ptr);
+  }
+  return ptr;
+}
+
+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 posix_memalign(%zu,%zu)=%p\n", size_t(getpid()),
+             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 aligned_alloc(%zu,%zu)=%p\n", size_t(getpid()),
+             aAlignment, aSize, ptr);
+  }
+  return ptr;
+}
+
+void*
+replace_calloc(size_t aNum, size_t aSize)
+{
+  AutoLock lock(sLock);
+  void* ptr = sFuncs->calloc(aNum, aSize);
+  if (ptr) {
+    FdPrintf(sFd, "%zu calloc(%zu,%zu)=%p\n", size_t(getpid()), 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 realloc(%p,%zu)=%p\n", size_t(getpid()), aPtr, aSize,
+             new_ptr);
+  }
+  return new_ptr;
+}
+
+void
+replace_free(void* aPtr)
+{
+  AutoLock lock(sLock);
+  if (aPtr) {
+    FdPrintf(sFd, "%zu free(%p)\n", size_t(getpid()), 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 memalign(%zu,%zu)=%p\n", size_t(getpid()), aAlignment,
+             aSize, ptr);
+  }
+  return ptr;
+}
+
+void*
+replace_valloc(size_t aSize)
+{
+  AutoLock lock(sLock);
+  void* ptr = sFuncs->valloc(aSize);
+  if (ptr) {
+    FdPrintf(sFd, "%zu valloc(%zu)=%p\n", size_t(getpid()), aSize, ptr);
+  }
+  return ptr;
+}
+
+void
+replace_jemalloc_stats(jemalloc_stats_t* aStats)
+{
+  AutoLock lock(sLock);
+  sFuncs->jemalloc_stats(aStats);
+  FdPrintf(sFd, "%zu jemalloc_stats()\n", size_t(getpid()));
+}
new file mode 100644
--- /dev/null
+++ b/memory/replace/logalloc/Makefile.in
@@ -0,0 +1,10 @@
+# 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/.
+
+# Disable mozglue.
+WRAP_LDFLAGS 	=
+MOZ_GLUE_LDFLAGS=
+
+# Avoid Lock_impl code depending on mozilla::Logger
+MOZ_DEBUG_ENABLE_DEFS=
new file mode 100644
--- /dev/null
+++ b/memory/replace/logalloc/README
@@ -0,0 +1,107 @@
+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 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.
+
+As those allocation logs can grow large quite quickly, it can be useful
+to pipe the output to a compression tool.
+
+MALLOC_LOG=1 would send to Firefox's stdout, MALLOC_LOG=2 would send to
+its stderr. Since in both cases that could be mixed with other output
+from Firefox, it is usually better to use another file descriptor
+by shell redirections, such as:
+
+  MALLOC_LOG=3 firefox 3>&1 1>&2 | gzip -c > log.gz
+
+(3>&1 copies the `| gzip` pipe file descriptor to file descriptor #3, 1>&2
+then copies stderr to stdout. This leads to: fd1 and fd2 sending to stderr
+of the parent process (the shell), and fd3 sending to gzip.)
+
+Each line of the allocations log is formatted as follows:
+  <pid> <function>([<args>])[=<result>]
+where <args> is a comma separated list of values. The number of <args> and
+the presence of <result> depend on the <function>.
+
+Example log:
+  18545 malloc(32)=0x7f90495120e0
+  18545 calloc(1,148)=0x7f9049537480
+  18545 realloc(0x7f90495120e0,64)=0x7f9049536680
+  18545 posix_memalign(256,240)=0x7f9049583300
+  18545 jemalloc_stats()
+  18545 free(0x7f9049536680)
+
+This log can be replayed with the logalloc-replay tool in
+memory/replace/logalloc/replay. However, as the goal of that tool is to
+reproduce the recorded memory allocations, it needs to avoid as much as
+possible doing its own allocations for bookkeeping. Reading the logs as
+they are would require data structures and memory allocations. As a
+consequence, the logs need to be preprocessed beforehand.
+
+The logalloc_munge.py script is responsible for that preprocessing. It simply
+takes a raw log on its stdin, and outputs the preprocessed log on its stdout.
+It replaces pointer addresses with indexes the logalloc-replay tool can use
+in a large (almost) linear array of allocation tracking slots (prefixed with
+'#'). It also replaces the pids with numbers starting from 1 (such as the
+first seen pid number is 1, the second is 2, etc.).
+
+The above example log would become the following, once preprocessed:
+  1 malloc(32)=#1
+  1 calloc(1,148)=#2
+  1 realloc(#1,64)=#1
+  1 posix_memalign(256,240)=#3
+  1 jemalloc_stats()
+  1 free(#1)
+
+The logalloc-replay tool then takes the preprocessed log on its stdin and
+replays the allocations printed there, but will only replay those with the
+same process id as the first line (which normally is 1).
+
+As the log files are simple text files, though, it is easy to separate out
+the different processes log with e.g. grep, and feed the separate processes
+logs to logalloc-replay.
+
+The logalloc-replay program won't output anything unless jemalloc_stats
+records appears in the log. You can expect those to be recorded when going
+to about:memory in Firefox, but they can also be added after preprocessing.
+
+Here is an example of what one can do:
+
+  gunzip -c log.gz | python logalloc_munge.py | \
+  awk '$1 == "2" { print $0 } !(NR % 10000) { print "2 jemalloc_stats()" }' | \
+    ./logalloc-replay
+
+The above command replays the allocations of process #2, with some stats
+output every 10000 records.
+
+The logalloc-replay tool itself being hooked with replace-malloc, it is possible
+to set LD_PRELOAD/DYLD_INSERT_LIBRARIES/MOZ_REPLACE_MALLOC_LIB and replay a log
+through a different allocator. For example:
+
+  LD_PRELOAD=libreplace_jemalloc.so logalloc-replay < log
+
+Will replay the log against jemalloc3 (which is, as of writing, what
+libreplace_jemalloc.so contains).
new file mode 100644
--- /dev/null
+++ b/memory/replace/logalloc/moz.build
@@ -0,0 +1,40 @@
+# -*- Mode: python; c-basic-offset: 4; 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/.
+
+SharedLibrary('logalloc')
+
+SOURCES += [
+    'FdPrintf.cpp',
+    'LogAlloc.cpp',
+]
+
+DISABLE_STL_WRAPPING = True
+USE_STATIC_LIBS = True
+DEFINES['MOZ_NO_MOZALLOC'] = True
+# Avoid Lock_impl code depending on mozilla::Logger.
+DEFINES['NDEBUG'] = True
+
+# Use locking code from the chromium stack.
+if CONFIG['OS_TARGET'] == 'WINNT':
+    SOURCES += [
+        '../../../ipc/chromium/src/base/lock_impl_win.cc',
+    ]
+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':
+    USE_LIBS += [
+        'mozglue',
+    ]
+
+DIRS += [
+    'replay',
+]
new file mode 100644
--- /dev/null
+++ b/memory/replace/logalloc/replay/Makefile.in
@@ -0,0 +1,37 @@
+# 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/.
+
+# Disable mozglue.
+WRAP_LDFLAGS 	=
+MOZ_GLUE_LDFLAGS=
+MOZ_GLUE_PROGRAM_LDFLAGS=
+
+include $(topsrcdir)/mozglue/build/replace_malloc.mk
+
+ifndef CROSS_COMPILE
+
+ifeq ($(OS_TARGET),WINNT)
+LOGALLOC = MOZ_REPLACE_MALLOC_LIB=$(CURDIR)/../logalloc.dll
+else
+ifeq ($(OS_TARGET),Darwin)
+LOGALLOC = DYLD_INSERT_LIBRARIES=$(CURDIR)/../liblogalloc.dylib
+else
+LOGALLOC = LD_PRELOAD=$(CURDIR)/../$(DLL_PREFIX)logalloc$(DLL_SUFFIX)
+endif
+endif
+
+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
+# Test with MALLOC_LOG as a file descriptor number
+	MALLOC_LOG=1 $(LOGALLOC) ./$(PROGRAM) < $< | $(PYTHON) $(srcdir)/logalloc_munge.py | diff -w - expected_output.log
+# Test with MALLOC_LOG as a file name
+	$(RM) test_output.log
+	MALLOC_LOG=test_output.log $(LOGALLOC) ./$(PROGRAM) < $<
+	$(PYTHON) $(srcdir)/logalloc_munge.py < test_output.log | diff -w - expected_output.log
+
+endif
new file mode 100644
--- /dev/null
+++ b/memory/replace/logalloc/replay/Replay.cpp
@@ -0,0 +1,561 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#define MOZ_MEMORY_IMPL
+#include "mozmemory_wrap.h"
+
+#ifdef _WIN32
+/* windef.h, which windows.h includes, #defines min and max, which
+ * breaks std::min. Defining NOMINMAX prevents those #defines. */
+#define NOMINMAX
+#include <windows.h>
+#include <io.h>
+typedef int ssize_t;
+#else
+#include <sys/mman.h>
+#include <unistd.h>
+#endif
+#include <algorithm>
+#include <cstdio>
+#include <cstring>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/NullPtr.h"
+#include "FdPrintf.h"
+
+static void
+die(const char* message)
+{
+  /* Here, it doesn't matter that fprintf may allocate memory. */
+  fprintf(stderr, "%s\n", message);
+  exit(1);
+}
+
+/* We don't want to be using malloc() to allocate our internal tracking
+ * data, because that would change the parameters of what is being measured,
+ * so we want to use data types that directly use mmap/VirtualAlloc. */
+template <typename T, size_t Len>
+class MappedArray
+{
+public:
+  MappedArray(): mPtr(nullptr) {}
+
+  ~MappedArray()
+  {
+    if (mPtr) {
+#ifdef _WIN32
+      VirtualFree(mPtr, sizeof(T) * Len, MEM_RELEASE);
+#else
+      munmap(mPtr, sizeof(T) * Len);
+#endif
+    }
+  }
+
+  T& operator[] (size_t aIndex) const
+  {
+    if (mPtr) {
+      return mPtr[aIndex];
+    }
+
+#ifdef _WIN32
+    mPtr = reinterpret_cast<T*>(VirtualAlloc(nullptr, sizeof(T) * Len,
+             MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
+    if (mPtr == nullptr) {
+      die("VirtualAlloc error");
+    }
+#else
+    mPtr = reinterpret_cast<T*>(mmap(nullptr, sizeof(T) * Len,
+             PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0));
+    if (mPtr == MAP_FAILED) {
+      die("Mmap error");
+    }
+#endif
+    return mPtr[aIndex];
+  }
+
+private:
+  mutable T* mPtr;
+};
+
+/* Type for records of allocations. */
+struct MemSlot
+{
+  void* mPtr;
+  size_t mSize;
+};
+
+/* An almost infinite list of slots.
+ * In essence, this is a linked list of arrays of groups of slots.
+ * Each group is 1MB. On 64-bits, one group allows to store 64k allocations.
+ * Each MemSlotList instance can store 1023 such groups, which means more
+ * than 65M allocations. In case more would be needed, we chain to another
+ * MemSlotList, and so on.
+ * Using 1023 groups makes the MemSlotList itself page sized on 32-bits
+ * and 2 pages-sized on 64-bits.
+ */
+class MemSlotList
+{
+  static const size_t kGroups = 1024 - 1;
+  static const size_t kGroupSize = (1024 * 1024) / sizeof(MemSlot);
+
+  MappedArray<MemSlot, kGroupSize> mSlots[kGroups];
+  MappedArray<MemSlotList, 1> mNext;
+
+public:
+  MemSlot& operator[] (size_t aIndex) const
+  {
+    if (aIndex < kGroupSize * kGroups) {
+      return mSlots[aIndex / kGroupSize][aIndex % kGroupSize];
+    }
+    aIndex -= kGroupSize * kGroups;
+    return mNext[0][aIndex];
+  }
+};
+
+/* Helper class for memory buffers */
+class Buffer
+{
+public:
+  Buffer() : mBuf(nullptr), mLength(0) {}
+
+  Buffer(const void* aBuf, size_t aLength)
+    : mBuf(reinterpret_cast<const char*>(aBuf)), mLength(aLength)
+  {}
+
+  /* Constructor for string literals. */
+  template <size_t Size>
+  Buffer(const char (&aStr)[Size])
+    : mBuf(aStr), mLength(Size - 1)
+  {}
+
+  /* Returns a sub-buffer up-to but not including the given aNeedle character.
+   * The "parent" buffer itself is altered to begin after the aNeedle
+   * character.
+   * If the aNeedle character is not found, return the entire buffer, and empty
+   * the "parent" buffer. */
+  Buffer SplitChar(char aNeedle)
+  {
+    char* buf = const_cast<char*>(mBuf);
+    char* c = reinterpret_cast<char*>(memchr(buf, aNeedle, mLength));
+    if (!c) {
+      return Split(mLength);
+    }
+
+    Buffer result = Split(c - buf);
+    // Remove the aNeedle character itself.
+    Split(1);
+    return result;
+  }
+
+  /* Returns a sub-buffer of at most aLength characters. The "parent" buffer is
+   * amputated of those aLength characters. If the "parent" buffer is smaller
+   * than aLength, then its length is used instead. */
+  Buffer Split(size_t aLength)
+  {
+    Buffer result(mBuf, std::min(aLength, mLength));
+    mLength -= result.mLength;
+    mBuf += result.mLength;
+    return result;
+  }
+
+  /* Move the buffer (including its content) to the memory address of the aOther
+   * buffer. */
+  void Slide(Buffer aOther)
+  {
+    memmove(const_cast<char*>(aOther.mBuf), mBuf, mLength);
+    mBuf = aOther.mBuf;
+  }
+
+  /* Returns whether the two involved buffers have the same content. */
+  bool operator ==(Buffer aOther)
+  {
+    return mLength == aOther.mLength && (mBuf == aOther.mBuf ||
+                                         !strncmp(mBuf, aOther.mBuf, mLength));
+  }
+
+  /* Returns whether the buffer is empty. */
+  operator bool() { return mLength; }
+
+  /* Returns the memory location of the buffer. */
+  const char* get() { return mBuf; }
+
+  /* Returns the memory location of the end of the buffer (technically, the
+   * first byte after the buffer). */
+  const char* GetEnd() { return mBuf + mLength; }
+
+  /* Extend the buffer over the content of the other buffer, assuming it is
+   * adjacent. */
+  void Extend(Buffer aOther)
+  {
+    MOZ_ASSERT(aOther.mBuf == GetEnd());
+    mLength += aOther.mLength;
+  }
+
+private:
+  const char* mBuf;
+  size_t mLength;
+};
+
+/* Helper class to read from a file descriptor line by line. */
+class FdReader {
+public:
+  FdReader(int aFd)
+    : mFd(aFd)
+    , mData(&mRawBuf, 0)
+    , mBuf(&mRawBuf, sizeof(mRawBuf))
+  {}
+
+  /* Read a line from the file descriptor and returns it as a Buffer instance */
+  Buffer ReadLine()
+  {
+    while (true) {
+      Buffer result = mData.SplitChar('\n');
+
+      /* There are essentially three different cases here:
+       * - '\n' was found "early". In this case, the end of the result buffer
+       *   is before the beginning of the mData buffer (since SplitChar
+       *   amputated it).
+       * - '\n' was found as the last character of mData. In this case, mData
+       *   is empty, but still points at the end of mBuf. result points to what
+       *   used to be in mData, without the last character.
+       * - '\n' was not found. In this case too, mData is empty and points at
+       *   the end of mBuf. But result points to the entire buffer that used to
+       *   be pointed by mData.
+       * Only in the latter case do both result and mData's end match, and it's
+       * the only case where we need to refill the buffer.
+       */
+      if (result.GetEnd() != mData.GetEnd()) {
+        return result;
+      }
+
+      /* Since SplitChar emptied mData, make it point to what it had before. */
+      mData = result;
+
+      /* And move it to the beginning of the read buffer. */
+      mData.Slide(mBuf);
+
+      FillBuffer();
+
+      if (!mData) {
+        return Buffer();
+      }
+    }
+  }
+
+private:
+  /* Fill the read buffer. */
+  void FillBuffer()
+  {
+    size_t size = mBuf.GetEnd() - mData.GetEnd();
+    Buffer remainder(mData.GetEnd(), size);
+
+    ssize_t len = 1;
+    while (remainder && len > 0) {
+      len = ::read(mFd, const_cast<char*>(remainder.get()), size);
+      if (len < 0) {
+        die("Read error");
+      }
+      size -= len;
+      mData.Extend(remainder.Split(len));
+    }
+  }
+
+  /* File descriptor to read from. */
+  int mFd;
+  /* Part of data that was read from the file descriptor but not returned with
+   * ReadLine yet. */
+  Buffer mData;
+  /* Buffer representation of mRawBuf */
+  Buffer mBuf;
+  /* read() buffer */
+  char mRawBuf[4096];
+};
+
+MOZ_BEGIN_EXTERN_C
+
+/* Function declarations for all the replace_malloc _impl functions.
+ * See memory/build/replace_malloc.c */
+#define MALLOC_DECL(name, return_type, ...) \
+  return_type name ## _impl(__VA_ARGS__);
+#define MALLOC_FUNCS MALLOC_FUNCS_MALLOC
+#include "malloc_decls.h"
+
+#define MALLOC_DECL(name, return_type, ...) \
+  return_type name ## _impl(__VA_ARGS__);
+#define MALLOC_FUNCS MALLOC_FUNCS_JEMALLOC
+#include "malloc_decls.h"
+
+/* mozjemalloc relies on DllMain to initialize, but DllMain is not invoked
+ * for executables, so manually invoke mozjemalloc initialization. */
+#if defined(_WIN32) && !defined(MOZ_JEMALLOC3)
+void malloc_init_hard(void);
+#endif
+
+#ifdef ANDROID
+/* mozjemalloc uses MozTagAnonymousMemory, which doesn't have an inline
+ * implementation on Android */
+void
+MozTagAnonymousMemory(const void* aPtr, size_t aLength, const char* aTag) {}
+
+/* mozjemalloc and jemalloc use pthread_atfork, which Android doesn't have.
+ * While gecko has one in libmozglue, the replay program can't use that.
+ * Since we're not going to fork anyways, make it a dummy function. */
+int
+pthread_atfork(void (*aPrepare)(void), void (*aParent)(void),
+               void (*aChild)(void))
+{
+  return 0;
+}
+#endif
+
+#ifdef MOZ_NUWA_PROCESS
+#include <pthread.h>
+
+/* NUWA builds have jemalloc built with
+ * -Dpthread_mutex_lock=__real_pthread_mutex_lock */
+int
+__real_pthread_mutex_lock(pthread_mutex_t* aMutex)
+{
+  return pthread_mutex_lock(aMutex);
+}
+#endif
+
+MOZ_END_EXTERN_C
+
+size_t parseNumber(Buffer aBuf)
+{
+  if (!aBuf) {
+    die("Malformed input");
+  }
+
+  size_t result = 0;
+  for (const char* c = aBuf.get(), *end = aBuf.GetEnd(); c < end; c++) {
+    if (*c < '0' || *c > '9') {
+      die("Malformed input");
+    }
+    result *= 10;
+    result += *c - '0';
+  }
+  return result;
+}
+
+/* Class to handle dispatching the replay function calls to replace-malloc. */
+class Replay
+{
+public:
+  Replay(): mOps(0) {}
+
+  MemSlot& operator[] (size_t index) const
+  {
+    return mSlots[index];
+  }
+
+  void malloc(MemSlot& aSlot, Buffer& aArgs)
+  {
+    mOps++;
+    size_t size = parseNumber(aArgs);
+    aSlot.mPtr = ::malloc_impl(size);
+    aSlot.mSize = size;
+    Commit(aSlot);
+  }
+
+  void posix_memalign(MemSlot& aSlot, Buffer& aArgs)
+  {
+    mOps++;
+    size_t alignment = parseNumber(aArgs.SplitChar(','));
+    size_t size = parseNumber(aArgs);
+    void* ptr;
+    if (::posix_memalign_impl(&ptr, alignment, size) == 0) {
+      aSlot.mPtr = ptr;
+      aSlot.mSize = size;
+    } else {
+      aSlot.mPtr = nullptr;
+      aSlot.mSize = 0;
+    }
+    Commit(aSlot);
+  }
+
+  void aligned_alloc(MemSlot& aSlot, Buffer& aArgs)
+  {
+    mOps++;
+    size_t alignment = parseNumber(aArgs.SplitChar(','));
+    size_t size = parseNumber(aArgs);
+    aSlot.mPtr = ::aligned_alloc_impl(alignment, size);
+    aSlot.mSize = size;
+    Commit(aSlot);
+  }
+
+  void calloc(MemSlot& aSlot, Buffer& aArgs)
+  {
+    mOps++;
+    size_t num = parseNumber(aArgs.SplitChar(','));
+    size_t size = parseNumber(aArgs);
+    aSlot.mPtr = ::calloc_impl(num, size);
+    aSlot.mSize = size;
+    Commit(aSlot);
+  }
+
+  void realloc(MemSlot& aSlot, Buffer& aArgs)
+  {
+    mOps++;
+    Buffer dummy = aArgs.SplitChar('#');
+    if (dummy) {
+      die("Malformed input");
+    }
+    size_t slot_id = parseNumber(aArgs.SplitChar(','));
+    size_t size = parseNumber(aArgs);
+    MemSlot& old_slot = (*this)[slot_id];
+    void* old_ptr = old_slot.mPtr;
+    old_slot.mPtr = nullptr;
+    old_slot.mSize = 0;
+    aSlot.mPtr = ::realloc_impl(old_ptr, size);
+    aSlot.mSize = size;
+    Commit(aSlot);
+  }
+
+  void free(Buffer& aArgs)
+  {
+    mOps++;
+    Buffer dummy = aArgs.SplitChar('#');
+    if (dummy) {
+      die("Malformed input");
+    }
+    size_t slot_id = parseNumber(aArgs);
+    MemSlot& slot = (*this)[slot_id];
+    ::free_impl(slot.mPtr);
+    slot.mPtr = nullptr;
+    slot.mSize = 0;
+  }
+
+  void memalign(MemSlot& aSlot, Buffer& aArgs)
+  {
+    mOps++;
+    size_t alignment = parseNumber(aArgs.SplitChar(','));
+    size_t size = parseNumber(aArgs);
+    aSlot.mPtr = ::memalign_impl(alignment, size);
+    aSlot.mSize = size;
+    Commit(aSlot);
+  }
+
+  void valloc(MemSlot& aSlot, Buffer& aArgs)
+  {
+    mOps++;
+    size_t size = parseNumber(aArgs);
+    aSlot.mPtr = ::valloc_impl(size);
+    aSlot.mSize = size;
+    Commit(aSlot);
+  }
+
+  void jemalloc_stats(Buffer& aArgs)
+  {
+    if (aArgs) {
+      die("Malformed input");
+    }
+    jemalloc_stats_t stats;
+    ::jemalloc_stats_impl(&stats);
+    FdPrintf(2, "#%zu mapped: %zu; allocated: %zu; waste: %zu; dirty: %zu; "
+                "bookkeep: %zu; binunused: %zu\n", mOps, stats.mapped,
+                stats.allocated, stats.waste, stats.page_cache,
+                stats.bookkeeping, stats.bin_unused);
+    /* TODO: Add more data, like actual RSS as measured by OS, but compensated
+     * for the replay internal data. */
+  }
+
+private:
+  void Commit(MemSlot& aSlot)
+  {
+    memset(aSlot.mPtr, 0x5a, aSlot.mSize);
+  }
+
+  size_t mOps;
+  MemSlotList mSlots;
+};
+
+
+int
+main()
+{
+  size_t first_pid = 0;
+  FdReader reader(0);
+  Replay replay;
+
+#if defined(_WIN32) && !defined(MOZ_JEMALLOC3)
+  malloc_init_hard();
+#endif
+
+  /* Read log from stdin and dispatch function calls to the Replay instance.
+   * The log format is essentially:
+   *   <pid> <function>([<args>])[=<result>]
+   * <args> is a comma separated list of arguments.
+   *
+   * The logs are expected to be preprocessed so that allocations are
+   * attributed a tracking slot. The input is trusted not to have crazy
+   * values for these slot numbers.
+   *
+   * <result>, as well as some of the args to some of the function calls are
+   * such slot numbers.
+   */
+  while (true) {
+    Buffer line = reader.ReadLine();
+
+    if (!line) {
+      break;
+    }
+
+    size_t pid = parseNumber(line.SplitChar(' '));
+    if (!first_pid) {
+      first_pid = pid;
+    }
+
+    /* The log may contain data for several processes, only entries for the
+     * very first that appears are treated. */
+    if (first_pid != pid) {
+      continue;
+    }
+
+    Buffer func = line.SplitChar('(');
+    Buffer args = line.SplitChar(')');
+
+    /* jemalloc_stats and free are functions with no result. */
+    if (func == Buffer("jemalloc_stats")) {
+      replay.jemalloc_stats(args);
+      continue;
+    } else if (func == Buffer("free")) {
+      replay.free(args);
+      continue;
+    }
+
+    /* Parse result value and get the corresponding slot. */
+    Buffer dummy = line.SplitChar('=');
+    Buffer dummy2 = line.SplitChar('#');
+    if (dummy || dummy2) {
+      die("Malformed input");
+    }
+
+    size_t slot_id = parseNumber(line);
+    MemSlot& slot = replay[slot_id];
+
+    if (func == Buffer("malloc")) {
+      replay.malloc(slot, args);
+    } else if (func == Buffer("posix_memalign")) {
+      replay.posix_memalign(slot, args);
+    } else if (func == Buffer("aligned_alloc")) {
+      replay.aligned_alloc(slot, args);
+    } else if (func == Buffer("calloc")) {
+      replay.calloc(slot, args);
+    } else if (func == Buffer("realloc")) {
+      replay.realloc(slot, args);
+    } else if (func == Buffer("memalign")) {
+      replay.memalign(slot, args);
+    } else if (func == Buffer("valloc")) {
+      replay.valloc(slot, args);
+    } else {
+      die("Malformed input");
+    }
+  }
+
+  return 0;
+}
new file mode 100644
--- /dev/null
+++ b/memory/replace/logalloc/replay/logalloc_munge.py
@@ -0,0 +1,135 @@
+# 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/.
+
+"""
+This script takes a log from the replace-malloc logalloc library on stdin
+and munges it so that it can be used with the logalloc-replay tool.
+
+Given the following output:
+  13663 malloc(42)=0x7f0c33502040
+  13663 malloc(24)=0x7f0c33503040
+  13663 free(0x7f0c33502040)
+The resulting output is:
+  1 malloc(42)=#1
+  1 malloc(24)=#2
+  1 free(#1)
+
+See README for more details.
+"""
+
+from __future__ import print_function
+import sys
+from collections import (
+    defaultdict,
+    deque,
+)
+
+class IdMapping(object):
+    """Class to map values to ids.
+
+    Each value is associated to an increasing id, starting from 1.
+    When a value is removed, its id is recycled and will be reused for
+    subsequent values.
+    """
+    def __init__(self):
+        self.id = 1
+        self._values = {}
+        self._recycle = deque()
+
+    def __getitem__(self, value):
+        if value not in self._values:
+            if self._recycle:
+                self._values[value] = self._recycle.popleft()
+            else:
+                self._values[value] = self.id
+                self.id += 1
+        return self._values[value]
+
+    def __delitem__(self, value):
+        if value == 0:
+            return
+        self._recycle.append(self._values[value])
+        del self._values[value]
+
+    def __contains__(self, value):
+        return value == 0 or value in self._values
+
+
+class Ignored(Exception): pass
+
+
+def split_log_line(line):
+    try:
+        pid, func_call = line.split(' ', 1)
+        # func_call format is <function>([<args>])[=<result>]
+        call, result = func_call.split(')')
+        func, args = call.split('(')
+        args = args.split(',') if args else []
+        if result:
+            if result[0] != '=':
+                raise Ignored('Malformed input')
+            result = result[1:]
+        return pid, func, args, result
+    except:
+        raise Ignored('Malformed input')
+
+
+NUM_ARGUMENTS = {
+    'jemalloc_stats': 0,
+    'free': 1,
+    'malloc': 1,
+    'posix_memalign': 2,
+    'aligned_alloc': 2,
+    'calloc': 2,
+    'realloc': 2,
+    'memalign': 2,
+    'valloc': 1,
+}
+
+
+def main():
+    process_pointers = defaultdict(IdMapping)
+    pids = IdMapping()
+    for line in sys.stdin:
+        line = line.strip()
+
+        try:
+            pid, func, args, result = split_log_line(line)
+
+            # Replace pid with an id.
+            pid = pids[int(pid)]
+
+            pointers = process_pointers[pid]
+
+            if func not in NUM_ARGUMENTS:
+                raise Ignored('Unknown function')
+
+            if len(args) != NUM_ARGUMENTS[func]:
+                raise Ignored('Malformed input')
+
+            if func in ('jemalloc_stats', 'free') and result:
+                raise Ignored('Malformed input')
+
+            if func in ('free', 'realloc'):
+                ptr = int(args[0], 16)
+                if ptr and ptr not in pointers:
+                    raise Ignored('Did not see an alloc for pointer')
+                args[0] = "#%d" % pointers[ptr]
+                del pointers[ptr]
+
+            if result:
+                result = int(result, 16)
+                if not result:
+                    raise Ignored('Result is NULL')
+                result = "#%d" % pointers[result]
+
+            print('%d %s(%s)%s' % (pid, func, ','.join(args),
+                '=%s' % result if result else ''))
+
+        except Exception as e:
+            print('Ignored "%s": %s' % (line, e.message), file=sys.stderr)
+
+
+if __name__ == '__main__':
+    main()
new file mode 100644
--- /dev/null
+++ b/memory/replace/logalloc/replay/moz.build
@@ -0,0 +1,25 @@
+# -*- Mode: python; c-basic-offset: 4; 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/.
+
+Program('logalloc-replay')
+
+SOURCES += [
+    '../FdPrintf.cpp',
+    'Replay.cpp',
+]
+
+LOCAL_INCLUDES += [
+    '..',
+]
+
+# Link replace-malloc and the default allocator.
+USE_LIBS += [
+    'memory',
+]
+
+DISABLE_STL_WRAPPING = True
+
+DEFINES['MOZ_REPLACE_MALLOC'] = True
new file mode 100644
--- /dev/null
+++ b/memory/replace/logalloc/replay/replay.log
@@ -0,0 +1,17 @@
+1 malloc(42)=#1
+1 malloc(24)=#2
+2 malloc(42)=#1
+1 free(#1)
+1 posix_memalign(4096,1024)=#1
+1 calloc(4,42)=#3
+1 free(#2)
+1 realloc(#3,84)=#2
+1 aligned_alloc(512,1024)=#3
+1 memalign(512,1024)=#4
+1 valloc(1024)=#5
+1 jemalloc_stats()
+1 free(#5)
+1 free(#4)
+1 free(#3)
+1 free(#2)
+1 free(#1)
--- a/memory/replace/moz.build
+++ b/memory/replace/moz.build
@@ -1,14 +1,16 @@
 # -*- Mode: python; c-basic-offset: 4; 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/.
 
+DIRS += ['logalloc']
+
 # Build jemalloc3 as a replace-malloc lib when building with mozjemalloc
 if not CONFIG['MOZ_JEMALLOC3']:
     DIRS += ['jemalloc']
 
 if CONFIG['MOZ_REPLACE_MALLOC_LINKAGE'] == 'dummy library':
     DIRS += ['dummy']
 
 if CONFIG['MOZ_DMD']:
--- a/mozglue/build/Makefile.in
+++ b/mozglue/build/Makefile.in
@@ -18,41 +18,17 @@ GARBAGE += mozglue.def
 
 ifneq (,$(filter -DEFAULTLIB:mozcrt,$(MOZ_GLUE_LDFLAGS)))
 # Don't install the import library if we use mozcrt
 NO_INSTALL_IMPORT_LIBRARY = 1
 endif
 
 endif
 
-ifeq (Darwin_1,$(OS_TARGET)_$(MOZ_REPLACE_MALLOC))
-OS_LDFLAGS += \
-  -Wl,-U,_replace_init \
-  -Wl,-U,_replace_malloc \
-  -Wl,-U,_replace_posix_memalign \
-  -Wl,-U,_replace_aligned_alloc \
-  -Wl,-U,_replace_calloc \
-  -Wl,-U,_replace_realloc \
-  -Wl,-U,_replace_free \
-  -Wl,-U,_replace_memalign \
-  -Wl,-U,_replace_valloc \
-  -Wl,-U,_replace_malloc_usable_size \
-  -Wl,-U,_replace_malloc_good_size \
-  -Wl,-U,_replace_jemalloc_stats \
-  -Wl,-U,_replace_jemalloc_purge_freed_pages \
-  -Wl,-U,_replace_jemalloc_free_dirty_pages \
-  $(NULL)
-
-ifneq ($(MOZ_REPLACE_MALLOC_LINKAGE),compiler support)
-OS_LDFLAGS += -flat_namespace
-endif
-ifeq ($(MOZ_REPLACE_MALLOC_LINKAGE),dummy library)
-OS_LDFLAGS += -Wl,-weak_library,$(DEPTH)/memory/replace/dummy/$(DLL_PREFIX)replace_malloc$(DLL_SUFFIX)
-endif
-endif
+include $(topsrcdir)/mozglue/build/replace_malloc.mk
 
 ifdef MOZ_LINKER
 ifeq (arm, $(TARGET_CPU))
 OS_LDFLAGS += -Wl,-version-script,$(srcdir)/arm-eabi-filter
 endif
 
 endif
 
copy from mozglue/build/Makefile.in
copy to mozglue/build/replace_malloc.mk
--- a/mozglue/build/Makefile.in
+++ b/mozglue/build/replace_malloc.mk
@@ -1,33 +1,12 @@
-#
 # 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/.
 
-DIST_INSTALL = 1
-
-# For FORCE_SHARED_LIB
-include $(topsrcdir)/config/config.mk
-
-MOZ_GLUE_LDFLAGS = # Don't link against ourselves
-
-ifeq (WINNT,$(OS_TARGET))
-mozglue.def: mozglue.def.in $(GLOBAL_DEPS)
-	$(call py_action,preprocessor,$(if $(MOZ_REPLACE_MALLOC),-DMOZ_REPLACE_MALLOC) $(ACDEFINES) $< -o $@)
-
-GARBAGE += mozglue.def
-
-ifneq (,$(filter -DEFAULTLIB:mozcrt,$(MOZ_GLUE_LDFLAGS)))
-# Don't install the import library if we use mozcrt
-NO_INSTALL_IMPORT_LIBRARY = 1
-endif
-
-endif
-
 ifeq (Darwin_1,$(OS_TARGET)_$(MOZ_REPLACE_MALLOC))
 OS_LDFLAGS += \
   -Wl,-U,_replace_init \
   -Wl,-U,_replace_malloc \
   -Wl,-U,_replace_posix_memalign \
   -Wl,-U,_replace_aligned_alloc \
   -Wl,-U,_replace_calloc \
   -Wl,-U,_replace_realloc \
@@ -43,102 +22,8 @@ OS_LDFLAGS += \
 
 ifneq ($(MOZ_REPLACE_MALLOC_LINKAGE),compiler support)
 OS_LDFLAGS += -flat_namespace
 endif
 ifeq ($(MOZ_REPLACE_MALLOC_LINKAGE),dummy library)
 OS_LDFLAGS += -Wl,-weak_library,$(DEPTH)/memory/replace/dummy/$(DLL_PREFIX)replace_malloc$(DLL_SUFFIX)
 endif
 endif
-
-ifdef MOZ_LINKER
-ifeq (arm, $(TARGET_CPU))
-OS_LDFLAGS += -Wl,-version-script,$(srcdir)/arm-eabi-filter
-endif
-
-endif
-
-ifeq (Android, $(OS_TARGET))
-WRAP_LDFLAGS := $(filter -Wl%,$(WRAP_LDFLAGS))
-endif
-
-include $(topsrcdir)/config/rules.mk
-
-ifdef MOZ_MEMORY
-ifeq (WINNT,$(OS_TARGET))
-# Roll our own custom logic here for the import library
-
-###############################################################################
-#
-# Linking Mozilla itself to jemalloc is not particularly difficult.  To do this
-# we avoid linking directly to the Microsoft-provided CRT import libraries.
-# Instead, we link to our own import library which we generate here.  To
-# replace the CRT's malloc/free/other memory management symbols we export
-# our own versions out of jemalloc.dll.  We then take the import library that
-# the compiler generates for jemalloc.dll and combine it with the MS CRT import
-# libraries.  We put our library on the command line first, and the CRT symbols
-# are discarded in favor of our versions!
-#
-# Unfortunately that was too easy.  The CRT import library is not a standard
-# import library that contains a list of symbols and whatnot.  It also includes
-# object files that are linked into generated programs.  One of these,
-# crtdll.obj is (as one might expect) linked into all DLLs that link against
-# the CRT.  This file does things like run static C++ constructors when the
-# DLL is attached, call DllMain, etc.
-#
-# In the CRT source all malloc/free calls are made to malloc_crt and free_crt.
-# In debug builds these are both defined to malloc_dbg and free_dbg.  In opt
-# builds malloc_crt is an actual function, implemented and exposed from the
-# CRT.  free_crt is, however, defined to be just plain old free.  This works
-# fine inside the CRT where malloc_crt and free operate on the same heap.
-# Outside the CRT malloc_crt is in the CRT's heap, but free is in jemalloc's
-# heap.  This causes much pain at shutdown :-(
-#
-# The obvious solution here is to override malloc_crt too.  Unfortunately,
-# that doesn't work because the CRT expects to be able to call msize on this
-# piece of memory deep inside the CRT, which will fail because it'll call the
-# CRT's msize on a pointer in jemalloc's heap.
-#
-# Our solution to this is quite devious.  We take apart the CRT's import lib
-# and remove the problematic object file.  We then poke at the object file's
-# symbol table and replace '__imp__free' (which means grab free from some
-# other DLL) with '__imp__frex'.  Then we define our own dummy no-op function
-# in jemalloc.dll and export it as frex.  Then we put the CRT import lib
-# back together with the patched crtdll.obj, glue it to the end of jemalloc's
-# import library and link the rest of Mozilla to that.
-#
-# The result?  A binary that uses jemalloc, doesn't crash, and leaks a tiny
-# amount of memory (32 words per DLL in the 2010 CRT) at shutdown.
-#
-###############################################################################
-
-target:: mozcrt.lib
-	$(INSTALL) $(IFLAGS2) mozcrt.lib $(DIST)/lib
-
-# And finally combine that with the jemalloc import library to get an import
-# library that has our malloc/free/etc and the CRT's everything else
-mozcrt.lib: $(IMPORT_LIBRARY) msvc_modified.lib
-	lib -OUT:$@ $^
-
-# Put the fixed object file back in
-msvc_modified.lib: msvc_removed.lib crtdll_fixed.obj
-	lib -OUT:$@ $^
-
-# Fix the object file
-crtdll_fixed.obj: crtdll.obj
-	$(PYTHON) $(srcdir)/fixcrt.py
-
-# Find the path of crtdll.obj
-CRTDLL_FULLPATH=$(subst \,\\,$(shell lib -list msvc_combined.lib | grep crtdll\\.obj))
-
-# Remove the broken object file, only after we have extracted it
-msvc_removed.lib: msvc_combined.lib crtdll.obj
-	lib -OUT:$@ msvc_combined.lib -REMOVE:$(CRTDLL_FULLPATH)
-
-# Extract the broken object file out of the combined library
-crtdll.obj: msvc_combined.lib
-	lib -OUT:$@ $^ -EXTRACT:$(CRTDLL_FULLPATH)
-
-# Grab both CRT libraries and combine them into one library to simplify things
-msvc_combined.lib:
-	lib -OUT:$@ $(WIN32_CRT_LIBS)
-endif
-endif # MOZ_MEMORY