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(). 24static int new_mode = 0; 25 26typedef 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". 39static Allocator allocator = WINHEAP; 40#else 41static 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. 47static const char primary_name[] = "CHROME_ALLOCATOR"; 48static 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. 57inline 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 97extern "C" { 98void* 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 120void 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 132void* 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. 164void 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 178extern "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. 190extern "C" intptr_t _get_heap_handle() { 191 return 0; 192} 193 194static 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 214static void get_stats_thunk(char* buffer, int buffer_length) { 215 MallocExtension::instance()->GetStats(buffer, buffer_length); 216} 217 218static void release_free_memory_thunk() { 219 MallocExtension::instance()->ReleaseFreeMemory(); 220} 221 222// The CRT heap initialization stub. 223extern "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. 276extern "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. 282extern "C" void* _crtheap = reinterpret_cast<void*>(1); 283 284// Provide support for aligned memory through Windows only _aligned_malloc(). 285void* _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 318void _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 338namespace base { 339namespace allocator { 340 341void 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 365void* TCMallocDoMallocForTest(size_t size) { 366 return do_malloc(size); 367} 368 369void TCMallocDoFreeForTest(void* ptr) { 370 do_free(ptr); 371} 372 373size_t ExcludeSpaceForMarkForTest(size_t size) { 374 return ExcludeSpaceForMark(size); 375} 376 377} // namespace allocator. 378} // namespace base. 379