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