Bug 1356701 - Export unprefixed malloc and duplication functions on OSX. r=njn draft
authorMike Hommey <mh+mozilla@glandium.org>
Tue, 04 Jul 2017 15:01:50 +0900
changeset 604513 4300a0558887a2c0f2db6dc0c30c52fabc4a9c7a
parent 604512 26ef16bf474476db63f50b3249cf2d2f6f7cff34
child 636222 d32d0196accf80b2b7d79c0c7df5d3c2c1a9b6de
push id67106
push userbmo:mh+mozilla@glandium.org
push dateThu, 06 Jul 2017 02:35:44 +0000
reviewersnjn
bugs1356701
milestone56.0a1
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;