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