Home | History | Annotate | Download | only in allocator
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "base/allocator/allocator_shim.h"
      6 
      7 #include <config.h>
      8 #include "base/allocator/allocator_extension_thunks.h"
      9 #include "base/profiler/alternate_timer.h"
     10 #include "base/sysinfo.h"
     11 
     12 // This shim make it possible to use different allocators via an environment
     13 // variable set before running the program. This may reduce the
     14 // amount of inlining that we get with malloc/free/etc.
     15 
     16 // TODO(mbelshe): Ensure that all calls to tcmalloc have the proper call depth
     17 // from the "user code" so that debugging tools (HeapChecker) can work.
     18 
     19 // __THROW is defined in glibc systems.  It means, counter-intuitively,
     20 // "This function will never throw an exception."  It's an optional
     21 // optimization tool, but we may need to use it to match glibc prototypes.
     22 #ifndef __THROW    // I guess we're not on a glibc system
     23 # define __THROW   // __THROW is just an optimization, so ok to make it ""
     24 #endif
     25 
     26 // new_mode behaves similarly to MSVC's _set_new_mode.
     27 // If flag is 0 (default), calls to malloc will behave normally.
     28 // If flag is 1, calls to malloc will behave like calls to new,
     29 // and the std_new_handler will be invoked on failure.
     30 // Can be set by calling _set_new_mode().
     31 static int new_mode = 0;
     32 
     33 typedef enum {
     34   TCMALLOC,    // TCMalloc is the default allocator.
     35   WINHEAP,     // Windows Heap (standard Windows allocator).
     36   WINLFH,      // Windows LFH Heap.
     37 } Allocator;
     38 
     39 // This is the default allocator. This value can be changed at startup by
     40 // specifying environment variables shown below it.
     41 // See SetupSubprocessAllocator() to specify a default secondary (subprocess)
     42 // allocator.
     43 // TODO(jar): Switch to using TCMALLOC for the renderer as well.
     44 #if defined(SYZYASAN)
     45 // SyzyASan requires the use of "WINHEAP".
     46 static Allocator allocator = WINHEAP;
     47 #else
     48 static Allocator allocator = TCMALLOC;
     49 #endif
     50 // The names of the environment variables that can optionally control the
     51 // selection of the allocator.  The primary may be used to control overall
     52 // allocator selection, and the secondary can be used to specify an allocator
     53 // to use in sub-processes.
     54 static const char primary_name[] = "CHROME_ALLOCATOR";
     55 static const char secondary_name[] = "CHROME_ALLOCATOR_2";
     56 
     57 // We include tcmalloc and the win_allocator to get as much inlining as
     58 // possible.
     59 #include "debugallocation_shim.cc"
     60 #include "win_allocator.cc"
     61 
     62 // Call the new handler, if one has been set.
     63 // Returns true on successfully calling the handler, false otherwise.
     64 inline bool call_new_handler(bool nothrow) {
     65   // Get the current new handler.  NB: this function is not
     66   // thread-safe.  We make a feeble stab at making it so here, but
     67   // this lock only protects against tcmalloc interfering with
     68   // itself, not with other libraries calling set_new_handler.
     69   std::new_handler nh;
     70   {
     71     SpinLockHolder h(&set_new_handler_lock);
     72     nh = std::set_new_handler(0);
     73     (void) std::set_new_handler(nh);
     74   }
     75 #if (defined(__GNUC__) && !defined(__EXCEPTIONS)) || \
     76     (defined(_HAS_EXCEPTIONS) && !_HAS_EXCEPTIONS)
     77   if (!nh)
     78     return false;
     79   // Since exceptions are disabled, we don't really know if new_handler
     80   // failed.  Assume it will abort if it fails.
     81   (*nh)();
     82   return false;  // break out of the retry loop.
     83 #else
     84   // If no new_handler is established, the allocation failed.
     85   if (!nh) {
     86     if (nothrow)
     87       return false;
     88     throw std::bad_alloc();
     89   }
     90   // Otherwise, try the new_handler.  If it returns, retry the
     91   // allocation.  If it throws std::bad_alloc, fail the allocation.
     92   // if it throws something else, don't interfere.
     93   try {
     94     (*nh)();
     95   } catch (const std::bad_alloc&) {
     96     if (!nothrow)
     97       throw;
     98     return true;
     99   }
    100 #endif  // (defined(__GNUC__) && !defined(__EXCEPTIONS)) || (defined(_HAS_EXCEPTIONS) && !_HAS_EXCEPTIONS)
    101   return false;
    102 }
    103 
    104 extern "C" {
    105 void* malloc(size_t size) __THROW {
    106   void* ptr;
    107   for (;;) {
    108     switch (allocator) {
    109       case WINHEAP:
    110       case WINLFH:
    111         ptr = win_heap_malloc(size);
    112         break;
    113       case TCMALLOC:
    114       default:
    115         ptr = do_malloc(size);
    116         break;
    117     }
    118     if (ptr)
    119       return ptr;
    120 
    121     if (!new_mode || !call_new_handler(true))
    122       break;
    123   }
    124   return ptr;
    125 }
    126 
    127 void free(void* p) __THROW {
    128   switch (allocator) {
    129     case WINHEAP:
    130     case WINLFH:
    131       win_heap_free(p);
    132       return;
    133     case TCMALLOC:
    134       do_free(p);
    135       return;
    136   }
    137 }
    138 
    139 void* realloc(void* ptr, size_t size) __THROW {
    140   // Webkit is brittle for allocators that return NULL for malloc(0).  The
    141   // realloc(0, 0) code path does not guarantee a non-NULL return, so be sure
    142   // to call malloc for this case.
    143   if (!ptr)
    144     return malloc(size);
    145 
    146   void* new_ptr;
    147   for (;;) {
    148     switch (allocator) {
    149       case WINHEAP:
    150       case WINLFH:
    151         new_ptr = win_heap_realloc(ptr, size);
    152         break;
    153       case TCMALLOC:
    154       default:
    155         new_ptr = do_realloc(ptr, size);
    156         break;
    157     }
    158 
    159     // Subtle warning:  NULL return does not alwas indicate out-of-memory.  If
    160     // the requested new size is zero, realloc should free the ptr and return
    161     // NULL.
    162     if (new_ptr || !size)
    163       return new_ptr;
    164     if (!new_mode || !call_new_handler(true))
    165       break;
    166   }
    167   return new_ptr;
    168 }
    169 
    170 // TODO(mbelshe): Implement this for other allocators.
    171 void malloc_stats(void) __THROW {
    172   switch (allocator) {
    173     case WINHEAP:
    174     case WINLFH:
    175       // No stats.
    176       return;
    177     case TCMALLOC:
    178       tc_malloc_stats();
    179       return;
    180   }
    181 }
    182 
    183 #ifdef WIN32
    184 
    185 extern "C" size_t _msize(void* p) {
    186   switch (allocator) {
    187     case WINHEAP:
    188     case WINLFH:
    189       return win_heap_msize(p);
    190   }
    191 
    192   // TCMALLOC
    193   return MallocExtension::instance()->GetAllocatedSize(p);
    194 }
    195 
    196 // This is included to resolve references from libcmt.
    197 extern "C" intptr_t _get_heap_handle() {
    198   return 0;
    199 }
    200 
    201 static bool get_allocator_waste_size_thunk(size_t* size) {
    202   switch (allocator) {
    203     case WINHEAP:
    204     case WINLFH:
    205       // TODO(alexeif): Implement for allocators other than tcmalloc.
    206       return false;
    207   }
    208   size_t heap_size, allocated_bytes, unmapped_bytes;
    209   MallocExtension* ext = MallocExtension::instance();
    210   if (ext->GetNumericProperty("generic.heap_size", &heap_size) &&
    211       ext->GetNumericProperty("generic.current_allocated_bytes",
    212                               &allocated_bytes) &&
    213       ext->GetNumericProperty("tcmalloc.pageheap_unmapped_bytes",
    214                               &unmapped_bytes)) {
    215     *size = heap_size - allocated_bytes - unmapped_bytes;
    216     return true;
    217   }
    218   return false;
    219 }
    220 
    221 static void get_stats_thunk(char* buffer, int buffer_length) {
    222   MallocExtension::instance()->GetStats(buffer, buffer_length);
    223 }
    224 
    225 static void release_free_memory_thunk() {
    226   MallocExtension::instance()->ReleaseFreeMemory();
    227 }
    228 
    229 // The CRT heap initialization stub.
    230 extern "C" int _heap_init() {
    231 // Don't use the environment variable if SYZYASAN is defined, as the
    232 // implementation requires Winheap to be the allocator.
    233 #if !defined(SYZYASAN)
    234   const char* environment_value = GetenvBeforeMain(primary_name);
    235   if (environment_value) {
    236     if (!stricmp(environment_value, "winheap"))
    237       allocator = WINHEAP;
    238     else if (!stricmp(environment_value, "winlfh"))
    239       allocator = WINLFH;
    240     else if (!stricmp(environment_value, "tcmalloc"))
    241       allocator = TCMALLOC;
    242   }
    243 #endif
    244 
    245   switch (allocator) {
    246     case WINHEAP:
    247       return win_heap_init(false) ? 1 : 0;
    248     case WINLFH:
    249       return win_heap_init(true) ? 1 : 0;
    250     case TCMALLOC:
    251     default:
    252       // fall through
    253       break;
    254   }
    255 
    256   // Initializing tcmalloc.
    257   // We intentionally leak this object.  It lasts for the process
    258   // lifetime.  Trying to teardown at _heap_term() is so late that
    259   // you can't do anything useful anyway.
    260   new TCMallocGuard();
    261 
    262   // Provide optional hook for monitoring allocation quantities on a per-thread
    263   // basis.  Only set the hook if the environment indicates this needs to be
    264   // enabled.
    265   const char* profiling =
    266       GetenvBeforeMain(tracked_objects::kAlternateProfilerTime);
    267   if (profiling && *profiling == '1') {
    268     tracked_objects::SetAlternateTimeSource(
    269         tcmalloc::ThreadCache::GetBytesAllocatedOnCurrentThread,
    270         tracked_objects::TIME_SOURCE_TYPE_TCMALLOC);
    271   }
    272 
    273   base::allocator::thunks::SetGetAllocatorWasteSizeFunction(
    274       get_allocator_waste_size_thunk);
    275   base::allocator::thunks::SetGetStatsFunction(get_stats_thunk);
    276   base::allocator::thunks::SetReleaseFreeMemoryFunction(
    277       release_free_memory_thunk);
    278 
    279   return 1;
    280 }
    281 
    282 // The CRT heap cleanup stub.
    283 extern "C" void _heap_term() {}
    284 
    285 // We set this to 1 because part of the CRT uses a check of _crtheap != 0
    286 // to test whether the CRT has been initialized.  Once we've ripped out
    287 // the allocators from libcmt, we need to provide this definition so that
    288 // the rest of the CRT is still usable.
    289 extern "C" void* _crtheap = reinterpret_cast<void*>(1);
    290 
    291 // Provide support for aligned memory through Windows only _aligned_malloc().
    292 void* _aligned_malloc(size_t size, size_t alignment) {
    293   // _aligned_malloc guarantees parameter validation, so do so here.  These
    294   // checks are somewhat stricter than _aligned_malloc() since we're effectively
    295   // using memalign() under the hood.
    296   DCHECK_GT(size, 0U);
    297   DCHECK_EQ(alignment & (alignment - 1), 0U);
    298   DCHECK_EQ(alignment % sizeof(void*), 0U);
    299 
    300   void* ptr;
    301   for (;;) {
    302     switch (allocator) {
    303       case WINHEAP:
    304       case WINLFH:
    305         ptr = win_heap_memalign(alignment, size);
    306         break;
    307       case TCMALLOC:
    308       default:
    309         ptr = tc_memalign(alignment, size);
    310         break;
    311     }
    312 
    313     if (ptr) {
    314       // Sanity check alignment.
    315       DCHECK_EQ(reinterpret_cast<uintptr_t>(ptr) & (alignment - 1), 0U);
    316       return ptr;
    317     }
    318 
    319     if (!new_mode || !call_new_handler(true))
    320       break;
    321   }
    322   return ptr;
    323 }
    324 
    325 void _aligned_free(void* p) {
    326   // TCMalloc returns pointers from memalign() that are safe to use with free().
    327   // Pointers allocated with win_heap_memalign() MUST be freed via
    328   // win_heap_memalign_free() since the aligned pointer is not the real one.
    329   switch (allocator) {
    330     case WINHEAP:
    331     case WINLFH:
    332       win_heap_memalign_free(p);
    333       return;
    334     case TCMALLOC:
    335       do_free(p);
    336   }
    337 }
    338 
    339 #endif  // WIN32
    340 
    341 #include "generic_allocators.cc"
    342 
    343 }  // extern C
    344 
    345 namespace base {
    346 namespace allocator {
    347 
    348 void SetupSubprocessAllocator() {
    349   size_t primary_length = 0;
    350   getenv_s(&primary_length, NULL, 0, primary_name);
    351 
    352   size_t secondary_length = 0;
    353   char buffer[20];
    354   getenv_s(&secondary_length, buffer, sizeof(buffer), secondary_name);
    355   DCHECK_GT(sizeof(buffer), secondary_length);
    356   buffer[sizeof(buffer) - 1] = '\0';
    357 
    358   if (secondary_length || !primary_length) {
    359 // Don't use the environment variable if SYZYASAN is defined, as the
    360 // implementation require Winheap to be the allocator.
    361 #if !defined(SYZYASAN)
    362     const char* secondary_value = secondary_length ? buffer : "TCMALLOC";
    363     // Force renderer (or other subprocesses) to use secondary_value.
    364 #else
    365     const char* secondary_value = "WINHEAP";
    366 #endif
    367     int ret_val = _putenv_s(primary_name, secondary_value);
    368     DCHECK_EQ(0, ret_val);
    369   }
    370 }
    371 
    372 void* TCMallocDoMallocForTest(size_t size) {
    373   return do_malloc(size);
    374 }
    375 
    376 void TCMallocDoFreeForTest(void* ptr) {
    377   do_free(ptr);
    378 }
    379 
    380 size_t ExcludeSpaceForMarkForTest(size_t size) {
    381   return ExcludeSpaceForMark(size);
    382 }
    383 
    384 }  // namespace allocator.
    385 }  // namespace base.
    386