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