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