Bug 1356701 - Export unprefixed malloc and duplication functions on OSX. r=njn
authorMike Hommey <mh+mozilla@glandium.org>
Tue, 04 Jul 2017 15:01:50 +0900
changeset 367721 e84fd163bc20c66fbfef02700b6080a88e12409c
parent 367720 e62c8df6209b7e3f9f7d340d388c22a328817019
child 367722 6a629adbb62a299d7208373d1c6f375149d2afdb
push id32142
push usercbook@mozilla.com
push dateFri, 07 Jul 2017 08:34:50 +0000
treeherdermozilla-central@78ff4c023b6a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnjn
bugs1356701
milestone56.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 1356701 - Export unprefixed malloc and duplication functions on OSX. r=njn Going through the system zone allocator for every call to realloc/free on OSX is costly, because the zone allocator needs to first verify that the allocations do belong to the allocator it invokes (which ends up calling jemalloc's malloc_usable_size), which is unnecessary when we expect the allocations to belong to jemalloc. So, we export the malloc/realloc/free/etc. symbols from libmozglue.dylib, such that libraries and programs linked against it call directly into jemalloc instead of going through the system zone allocator, effectively shortcutting the allocator verification. The risk is that some things in Gecko try to realloc/free pointers it got from system libraries, if those were allocated with a system zone that is not jemalloc.
memory/build/mozmemory_wrap.c
memory/build/mozmemory_wrap.h
memory/build/zone.c
--- a/memory/build/mozmemory_wrap.c
+++ b/memory/build/mozmemory_wrap.c
@@ -63,17 +63,16 @@ mozmem_malloc_impl(_ZdaPvRKSt9nothrow_t)
 }
 #endif
 
 /* strndup and strdup may be defined as macros in string.h, which would
  * clash with the definitions below. */
 #undef strndup
 #undef strdup
 
-#ifndef XP_DARWIN
 MOZ_MEMORY_API char *
 strndup_impl(const char *src, size_t len)
 {
   char* dst = (char*) malloc_impl(len + 1);
   if (dst) {
     strncpy(dst, src, len);
     dst[len] = '\0';
   }
@@ -81,17 +80,16 @@ strndup_impl(const char *src, size_t len
 }
 
 MOZ_MEMORY_API char *
 strdup_impl(const char *src)
 {
   size_t len = strlen(src);
   return strndup_impl(src, len);
 }
-#endif /* XP_DARWIN */
 
 #ifdef ANDROID
 #include <stdarg.h>
 #include <stdio.h>
 
 MOZ_MEMORY_API int
 vasprintf_impl(char **str, const char *fmt, va_list ap)
 {
--- a/memory/build/mozmemory_wrap.h
+++ b/memory/build/mozmemory_wrap.h
@@ -48,21 +48,20 @@
  *   specific functions are left unprefixed. All these functions are however
  *   aliased when exporting them, such that the resulting mozglue.dll exports
  *   them unprefixed (see $(topsrcdir)/mozglue/build/mozglue.def.in). The
  *   prefixed malloc implementation and duplication functions are not
  *   exported.
  *
  * - On MacOSX, the system libc has a zone allocator, which allows us to
  *   hook custom malloc implementation functions without exporting them.
- *   The malloc implementation functions are all prefixed with "je_" and used
- *   this way from the custom zone allocator. They are not exported.
- *   Duplication functions are not included, since they will call the custom
- *   zone allocator anyways. Jemalloc-specific functions are also left
- *   unprefixed.
+ *   However, since we want things in Firefox to skip the system zone
+ *   allocator, the malloc implementation functions are all exported
+ *   unprefixed, as well as duplication functions.
+ *   Jemalloc-specific functions are also left unprefixed.
  *
  * - On Android and Gonk, all functions are left unprefixed. Additionally,
  *   C++ allocation functions (operator new/delete) are also exported and
  *   unprefixed.
  *
  * - On other systems (mostly Linux), all functions are left unprefixed.
  *
  * Only Android and Gonk add C++ allocation functions.
@@ -129,17 +128,17 @@
 #endif
 
 #ifdef MOZ_MEMORY_IMPL
 #  if defined(MOZ_JEMALLOC_IMPL) && defined(MOZ_REPLACE_MALLOC)
 #    define mozmem_malloc_impl(a)     je_ ## a
 #    define mozmem_jemalloc_impl(a)   je_ ## a
 #  else
 #    define MOZ_JEMALLOC_API MOZ_EXTERN_C MFBT_API
-#    if (defined(XP_WIN) || defined(XP_DARWIN))
+#    if defined(XP_WIN)
 #      if defined(MOZ_REPLACE_MALLOC)
 #        define mozmem_malloc_impl(a)   a ## _impl
 #      else
 #        define mozmem_malloc_impl(a)   je_ ## a
 #      endif
 #    else
 #      define MOZ_MEMORY_API MOZ_EXTERN_C MFBT_API
 #      if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
--- a/memory/build/zone.c
+++ b/memory/build/zone.c
@@ -89,16 +89,21 @@ extern kern_return_t malloc_get_all_zone
 extern malloc_zone_t *malloc_default_zone(void);
 
 extern void malloc_zone_register(malloc_zone_t *zone);
 
 extern void malloc_zone_unregister(malloc_zone_t *zone);
 
 extern malloc_zone_t *malloc_default_purgeable_zone(void);
 
+extern malloc_zone_t* malloc_zone_from_ptr(const void* ptr);
+
+extern void malloc_zone_free(malloc_zone_t* zone, void* ptr);
+
+extern void* malloc_zone_realloc(malloc_zone_t* zone, void* ptr, size_t size);
 
 /*
  * The following is a OSX zone allocator implementation.
  * /!\ WARNING. It assumes the underlying malloc implementation's
  * malloc_usable_size returns 0 when the given pointer is not owned by
  * the allocator. Sadly, OSX does call zone_size with pointers not
  * owned by the allocator.
  */
@@ -121,39 +126,66 @@ zone_calloc(malloc_zone_t *zone, size_t 
   return calloc_impl(num, size);
 }
 
 static void *
 zone_realloc(malloc_zone_t *zone, void *ptr, size_t size)
 {
   if (malloc_usable_size_impl(ptr))
     return realloc_impl(ptr, size);
-  return realloc(ptr, size);
+
+  // Sometimes, system libraries call malloc_zone_* functions with the wrong
+  // zone (e.g. CoreFoundation does). In that case, we need to find the real
+  // one. We can't call libSystem's realloc directly because we're exporting
+  // realloc from libmozglue and we'd pick that one, so we manually find the
+  // right zone and realloc with it.
+  malloc_zone_t* real_zone = malloc_zone_from_ptr(ptr);
+  // The system allocator crashes voluntarily by default when a pointer can't
+  // be traced back to a zone. Do the same.
+  MOZ_RELEASE_ASSERT(real_zone);
+  MOZ_RELEASE_ASSERT(real_zone != zone);
+  return malloc_zone_realloc(real_zone, ptr, size);
+}
+
+static void
+other_zone_free(malloc_zone_t* original_zone, void* ptr)
+{
+  // Sometimes, system libraries call malloc_zone_* functions with the wrong
+  // zone (e.g. CoreFoundation does). In that case, we need to find the real
+  // one. We can't call libSystem's free directly because we're exporting
+  // free from libmozglue and we'd pick that one, so we manually find the
+  // right zone and free with it.
+  malloc_zone_t* zone = malloc_zone_from_ptr(ptr);
+  // The system allocator crashes voluntarily by default when a pointer can't
+  // be traced back to a zone. Do the same.
+  MOZ_RELEASE_ASSERT(zone);
+  MOZ_RELEASE_ASSERT(zone != original_zone);
+  return malloc_zone_free(zone, ptr);
 }
 
 static void
 zone_free(malloc_zone_t *zone, void *ptr)
 {
   if (malloc_usable_size_impl(ptr)) {
     free_impl(ptr);
     return;
   }
-  free(ptr);
+  other_zone_free(zone, ptr);
 }
 
 static void
 zone_free_definite_size(malloc_zone_t *zone, void *ptr, size_t size)
 {
   size_t current_size = malloc_usable_size_impl(ptr);
   if (current_size) {
     MOZ_ASSERT(current_size == size);
     free_impl(ptr);
     return;
   }
-  free(ptr);
+  other_zone_free(zone, ptr);
 }
 
 static void *
 zone_memalign(malloc_zone_t *zone, size_t alignment, size_t size)
 {
   void *ptr;
   if (posix_memalign_impl(&ptr, alignment, size) == 0)
     return ptr;