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.
--- 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;