Home | History | Annotate | Download | only in allocator
      1 // Copyright 2016 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 <errno.h>
      8 
      9 #include <new>
     10 
     11 #include "base/atomicops.h"
     12 #include "base/logging.h"
     13 #include "base/macros.h"
     14 #include "base/process/process_metrics.h"
     15 #include "base/threading/platform_thread.h"
     16 #include "build/build_config.h"
     17 
     18 #if !defined(OS_WIN)
     19 #include <unistd.h>
     20 #else
     21 #include "base/allocator/winheap_stubs_win.h"
     22 #endif
     23 
     24 #if defined(OS_MACOSX)
     25 #include <malloc/malloc.h>
     26 
     27 #include "base/allocator/allocator_interception_mac.h"
     28 #endif
     29 
     30 // No calls to malloc / new in this file. They would would cause re-entrancy of
     31 // the shim, which is hard to deal with. Keep this code as simple as possible
     32 // and don't use any external C++ object here, not even //base ones. Even if
     33 // they are safe to use today, in future they might be refactored.
     34 
     35 namespace {
     36 
     37 using namespace base;
     38 
     39 subtle::AtomicWord g_chain_head = reinterpret_cast<subtle::AtomicWord>(
     40     &allocator::AllocatorDispatch::default_dispatch);
     41 
     42 bool g_call_new_handler_on_malloc_failure = false;
     43 
     44 #if !defined(OS_WIN)
     45 subtle::Atomic32 g_new_handler_lock = 0;
     46 #endif
     47 
     48 inline size_t GetCachedPageSize() {
     49   static size_t pagesize = 0;
     50   if (!pagesize)
     51     pagesize = base::GetPageSize();
     52   return pagesize;
     53 }
     54 
     55 // Calls the std::new handler thread-safely. Returns true if a new_handler was
     56 // set and called, false if no new_handler was set.
     57 bool CallNewHandler(size_t size) {
     58 #if defined(OS_WIN)
     59   return base::allocator::WinCallNewHandler(size);
     60 #else
     61   // TODO(primiano): C++11 has introduced ::get_new_handler() which is supposed
     62   // to be thread safe and would avoid the spinlock boilerplate here. However
     63   // it doesn't seem to be available yet in the Linux chroot headers yet.
     64   std::new_handler nh;
     65   {
     66     while (subtle::Acquire_CompareAndSwap(&g_new_handler_lock, 0, 1))
     67       PlatformThread::YieldCurrentThread();
     68     nh = std::set_new_handler(0);
     69     ignore_result(std::set_new_handler(nh));
     70     subtle::Release_Store(&g_new_handler_lock, 0);
     71   }
     72   if (!nh)
     73     return false;
     74   (*nh)();
     75   // Assume the new_handler will abort if it fails. Exception are disabled and
     76   // we don't support the case of a new_handler throwing std::bad_balloc.
     77   return true;
     78 #endif
     79 }
     80 
     81 inline const allocator::AllocatorDispatch* GetChainHead() {
     82   // TODO(primiano): Just use NoBarrier_Load once crbug.com/593344 is fixed.
     83   // Unfortunately due to that bug NoBarrier_Load() is mistakenly fully
     84   // barriered on Linux+Clang, and that causes visible perf regressons.
     85   return reinterpret_cast<const allocator::AllocatorDispatch*>(
     86 #if defined(OS_LINUX) && defined(__clang__)
     87       *static_cast<const volatile subtle::AtomicWord*>(&g_chain_head)
     88 #else
     89       subtle::NoBarrier_Load(&g_chain_head)
     90 #endif
     91   );
     92 }
     93 
     94 }  // namespace
     95 
     96 namespace base {
     97 namespace allocator {
     98 
     99 void SetCallNewHandlerOnMallocFailure(bool value) {
    100   g_call_new_handler_on_malloc_failure = value;
    101 }
    102 
    103 void* UncheckedAlloc(size_t size) {
    104   const allocator::AllocatorDispatch* const chain_head = GetChainHead();
    105   return chain_head->alloc_function(chain_head, size, nullptr);
    106 }
    107 
    108 void InsertAllocatorDispatch(AllocatorDispatch* dispatch) {
    109   // Loop in case of (an unlikely) race on setting the list head.
    110   size_t kMaxRetries = 7;
    111   for (size_t i = 0; i < kMaxRetries; ++i) {
    112     const AllocatorDispatch* chain_head = GetChainHead();
    113     dispatch->next = chain_head;
    114 
    115     // This function guarantees to be thread-safe w.r.t. concurrent
    116     // insertions. It also has to guarantee that all the threads always
    117     // see a consistent chain, hence the MemoryBarrier() below.
    118     // InsertAllocatorDispatch() is NOT a fastpath, as opposite to malloc(), so
    119     // we don't really want this to be a release-store with a corresponding
    120     // acquire-load during malloc().
    121     subtle::MemoryBarrier();
    122     subtle::AtomicWord old_value =
    123         reinterpret_cast<subtle::AtomicWord>(chain_head);
    124     // Set the chain head to the new dispatch atomically. If we lose the race,
    125     // the comparison will fail, and the new head of chain will be returned.
    126     if (subtle::NoBarrier_CompareAndSwap(
    127             &g_chain_head, old_value,
    128             reinterpret_cast<subtle::AtomicWord>(dispatch)) == old_value) {
    129       // Success.
    130       return;
    131     }
    132   }
    133 
    134   CHECK(false);  // Too many retries, this shouldn't happen.
    135 }
    136 
    137 void RemoveAllocatorDispatchForTesting(AllocatorDispatch* dispatch) {
    138   DCHECK_EQ(GetChainHead(), dispatch);
    139   subtle::NoBarrier_Store(&g_chain_head,
    140                           reinterpret_cast<subtle::AtomicWord>(dispatch->next));
    141 }
    142 
    143 }  // namespace allocator
    144 }  // namespace base
    145 
    146 // The Shim* functions below are the entry-points into the shim-layer and
    147 // are supposed to be invoked by the allocator_shim_override_*
    148 // headers to route the malloc / new symbols through the shim layer.
    149 // They are defined as ALWAYS_INLINE in order to remove a level of indirection
    150 // between the system-defined entry points and the shim implementations.
    151 extern "C" {
    152 
    153 // The general pattern for allocations is:
    154 // - Try to allocate, if succeded return the pointer.
    155 // - If the allocation failed:
    156 //   - Call the std::new_handler if it was a C++ allocation.
    157 //   - Call the std::new_handler if it was a malloc() (or calloc() or similar)
    158 //     AND SetCallNewHandlerOnMallocFailure(true).
    159 //   - If the std::new_handler is NOT set just return nullptr.
    160 //   - If the std::new_handler is set:
    161 //     - Assume it will abort() if it fails (very likely the new_handler will
    162 //       just suicide priting a message).
    163 //     - Assume it did succeed if it returns, in which case reattempt the alloc.
    164 
    165 ALWAYS_INLINE void* ShimCppNew(size_t size) {
    166   const allocator::AllocatorDispatch* const chain_head = GetChainHead();
    167   void* ptr;
    168   do {
    169     void* context = nullptr;
    170 #if defined(OS_MACOSX)
    171     context = malloc_default_zone();
    172 #endif
    173     ptr = chain_head->alloc_function(chain_head, size, context);
    174   } while (!ptr && CallNewHandler(size));
    175   return ptr;
    176 }
    177 
    178 ALWAYS_INLINE void ShimCppDelete(void* address) {
    179   void* context = nullptr;
    180 #if defined(OS_MACOSX)
    181   context = malloc_default_zone();
    182 #endif
    183   const allocator::AllocatorDispatch* const chain_head = GetChainHead();
    184   return chain_head->free_function(chain_head, address, context);
    185 }
    186 
    187 ALWAYS_INLINE void* ShimMalloc(size_t size, void* context) {
    188   const allocator::AllocatorDispatch* const chain_head = GetChainHead();
    189   void* ptr;
    190   do {
    191     ptr = chain_head->alloc_function(chain_head, size, context);
    192   } while (!ptr && g_call_new_handler_on_malloc_failure &&
    193            CallNewHandler(size));
    194   return ptr;
    195 }
    196 
    197 ALWAYS_INLINE void* ShimCalloc(size_t n, size_t size, void* context) {
    198   const allocator::AllocatorDispatch* const chain_head = GetChainHead();
    199   void* ptr;
    200   do {
    201     ptr = chain_head->alloc_zero_initialized_function(chain_head, n, size,
    202                                                       context);
    203   } while (!ptr && g_call_new_handler_on_malloc_failure &&
    204            CallNewHandler(size));
    205   return ptr;
    206 }
    207 
    208 ALWAYS_INLINE void* ShimRealloc(void* address, size_t size, void* context) {
    209   // realloc(size == 0) means free() and might return a nullptr. We should
    210   // not call the std::new_handler in that case, though.
    211   const allocator::AllocatorDispatch* const chain_head = GetChainHead();
    212   void* ptr;
    213   do {
    214     ptr = chain_head->realloc_function(chain_head, address, size, context);
    215   } while (!ptr && size && g_call_new_handler_on_malloc_failure &&
    216            CallNewHandler(size));
    217   return ptr;
    218 }
    219 
    220 ALWAYS_INLINE void* ShimMemalign(size_t alignment, size_t size, void* context) {
    221   const allocator::AllocatorDispatch* const chain_head = GetChainHead();
    222   void* ptr;
    223   do {
    224     ptr = chain_head->alloc_aligned_function(chain_head, alignment, size,
    225                                              context);
    226   } while (!ptr && g_call_new_handler_on_malloc_failure &&
    227            CallNewHandler(size));
    228   return ptr;
    229 }
    230 
    231 ALWAYS_INLINE int ShimPosixMemalign(void** res, size_t alignment, size_t size) {
    232   // posix_memalign is supposed to check the arguments. See tc_posix_memalign()
    233   // in tc_malloc.cc.
    234   if (((alignment % sizeof(void*)) != 0) ||
    235       ((alignment & (alignment - 1)) != 0) || (alignment == 0)) {
    236     return EINVAL;
    237   }
    238   void* ptr = ShimMemalign(alignment, size, nullptr);
    239   *res = ptr;
    240   return ptr ? 0 : ENOMEM;
    241 }
    242 
    243 ALWAYS_INLINE void* ShimValloc(size_t size, void* context) {
    244   return ShimMemalign(GetCachedPageSize(), size, context);
    245 }
    246 
    247 ALWAYS_INLINE void* ShimPvalloc(size_t size) {
    248   // pvalloc(0) should allocate one page, according to its man page.
    249   if (size == 0) {
    250     size = GetCachedPageSize();
    251   } else {
    252     size = (size + GetCachedPageSize() - 1) & ~(GetCachedPageSize() - 1);
    253   }
    254   // The third argument is nullptr because pvalloc is glibc only and does not
    255   // exist on OSX/BSD systems.
    256   return ShimMemalign(GetCachedPageSize(), size, nullptr);
    257 }
    258 
    259 ALWAYS_INLINE void ShimFree(void* address, void* context) {
    260   const allocator::AllocatorDispatch* const chain_head = GetChainHead();
    261   return chain_head->free_function(chain_head, address, context);
    262 }
    263 
    264 ALWAYS_INLINE size_t ShimGetSizeEstimate(const void* address, void* context) {
    265   const allocator::AllocatorDispatch* const chain_head = GetChainHead();
    266   return chain_head->get_size_estimate_function(
    267       chain_head, const_cast<void*>(address), context);
    268 }
    269 
    270 ALWAYS_INLINE unsigned ShimBatchMalloc(size_t size,
    271                                        void** results,
    272                                        unsigned num_requested,
    273                                        void* context) {
    274   const allocator::AllocatorDispatch* const chain_head = GetChainHead();
    275   return chain_head->batch_malloc_function(chain_head, size, results,
    276                                            num_requested, context);
    277 }
    278 
    279 ALWAYS_INLINE void ShimBatchFree(void** to_be_freed,
    280                                  unsigned num_to_be_freed,
    281                                  void* context) {
    282   const allocator::AllocatorDispatch* const chain_head = GetChainHead();
    283   return chain_head->batch_free_function(chain_head, to_be_freed,
    284                                          num_to_be_freed, context);
    285 }
    286 
    287 ALWAYS_INLINE void ShimFreeDefiniteSize(void* ptr, size_t size, void* context) {
    288   const allocator::AllocatorDispatch* const chain_head = GetChainHead();
    289   return chain_head->free_definite_size_function(chain_head, ptr, size,
    290                                                  context);
    291 }
    292 
    293 }  // extern "C"
    294 
    295 #if !defined(OS_WIN) && !defined(OS_MACOSX)
    296 // Cpp symbols (new / delete) should always be routed through the shim layer
    297 // except on Windows and macOS where the malloc intercept is deep enough that it
    298 // also catches the cpp calls.
    299 #include "base/allocator/allocator_shim_override_cpp_symbols.h"
    300 #endif
    301 
    302 #if defined(OS_ANDROID) || defined(ANDROID)
    303 // Android does not support symbol interposition. The way malloc symbols are
    304 // intercepted on Android is by using link-time -wrap flags.
    305 #include "base/allocator/allocator_shim_override_linker_wrapped_symbols.h"
    306 #elif defined(OS_WIN)
    307 // On Windows we use plain link-time overriding of the CRT symbols.
    308 #include "base/allocator/allocator_shim_override_ucrt_symbols_win.h"
    309 #elif defined(OS_MACOSX)
    310 #include "base/allocator/allocator_shim_default_dispatch_to_mac_zoned_malloc.h"
    311 #include "base/allocator/allocator_shim_override_mac_symbols.h"
    312 #else
    313 #include "base/allocator/allocator_shim_override_libc_symbols.h"
    314 #endif
    315 
    316 // In the case of tcmalloc we also want to plumb into the glibc hooks
    317 // to avoid that allocations made in glibc itself (e.g., strdup()) get
    318 // accidentally performed on the glibc heap instead of the tcmalloc one.
    319 #if defined(USE_TCMALLOC)
    320 #include "base/allocator/allocator_shim_override_glibc_weak_symbols.h"
    321 #endif
    322 
    323 #if defined(OS_MACOSX)
    324 namespace base {
    325 namespace allocator {
    326 void InitializeAllocatorShim() {
    327   // Prepares the default dispatch. After the intercepted malloc calls have
    328   // traversed the shim this will route them to the default malloc zone.
    329   InitializeDefaultDispatchToMacAllocator();
    330 
    331   MallocZoneFunctions functions = MallocZoneFunctionsToReplaceDefault();
    332 
    333   // This replaces the default malloc zone, causing calls to malloc & friends
    334   // from the codebase to be routed to ShimMalloc() above.
    335   base::allocator::ReplaceFunctionsForStoredZones(&functions);
    336 }
    337 }  // namespace allocator
    338 }  // namespace base
    339 #endif
    340 
    341 // Cross-checks.
    342 
    343 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
    344 #error The allocator shim should not be compiled when building for memory tools.
    345 #endif
    346 
    347 #if (defined(__GNUC__) && defined(__EXCEPTIONS)) || \
    348     (defined(_HAS_EXCEPTIONS) && _HAS_EXCEPTIONS)
    349 #error This code cannot be used when exceptions are turned on.
    350 #endif
    351