// -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- // Copyright (c) 2014, gperftools Contributors // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // #include "config.h" #include "emergency_malloc.h" #include // for ENOMEM, errno #include // for memset #include "base/basictypes.h" #include "base/logging.h" #include "base/low_level_alloc.h" #include "base/spinlock.h" #include "internal_logging.h" namespace tcmalloc { __attribute__ ((visibility("internal"))) char *emergency_arena_start; __attribute__ ((visibility("internal"))) uintptr_t emergency_arena_start_shifted; static CACHELINE_ALIGNED SpinLock emergency_malloc_lock(base::LINKER_INITIALIZED); static char *emergency_arena_end; static LowLevelAlloc::Arena *emergency_arena; class EmergencyArenaPagesAllocator : public LowLevelAlloc::PagesAllocator { ~EmergencyArenaPagesAllocator() {} void *MapPages(int32 flags, size_t size) { char *new_end = emergency_arena_end + size; if (new_end > emergency_arena_start + kEmergencyArenaSize) { RAW_LOG(FATAL, "Unable to allocate %" PRIuS " bytes in emergency zone.", size); } char *rv = emergency_arena_end; emergency_arena_end = new_end; return static_cast(rv); } void UnMapPages(int32 flags, void *addr, size_t size) { RAW_LOG(FATAL, "UnMapPages is not implemented for emergency arena"); } }; static union { char bytes[sizeof(EmergencyArenaPagesAllocator)]; void *ptr; } pages_allocator_place; static void InitEmergencyMalloc(void) { const int32 flags = LowLevelAlloc::kAsyncSignalSafe; void *arena = LowLevelAlloc::GetDefaultPagesAllocator()->MapPages(flags, kEmergencyArenaSize * 2); uintptr_t arena_ptr = reinterpret_cast(arena); uintptr_t ptr = (arena_ptr + kEmergencyArenaSize - 1) & ~(kEmergencyArenaSize-1); emergency_arena_end = emergency_arena_start = reinterpret_cast(ptr); EmergencyArenaPagesAllocator *allocator = new (pages_allocator_place.bytes) EmergencyArenaPagesAllocator(); emergency_arena = LowLevelAlloc::NewArenaWithCustomAlloc(0, LowLevelAlloc::DefaultArena(), allocator); emergency_arena_start_shifted = reinterpret_cast(emergency_arena_start) >> kEmergencyArenaShift; uintptr_t head_unmap_size = ptr - arena_ptr; CHECK_CONDITION(head_unmap_size < kEmergencyArenaSize); if (head_unmap_size != 0) { LowLevelAlloc::GetDefaultPagesAllocator()->UnMapPages(flags, arena, ptr - arena_ptr); } uintptr_t tail_unmap_size = kEmergencyArenaSize - head_unmap_size; void *tail_start = reinterpret_cast(arena_ptr + head_unmap_size + kEmergencyArenaSize); LowLevelAlloc::GetDefaultPagesAllocator()->UnMapPages(flags, tail_start, tail_unmap_size); } PERFTOOLS_DLL_DECL void *EmergencyMalloc(size_t size) { SpinLockHolder l(&emergency_malloc_lock); if (emergency_arena_start == NULL) { InitEmergencyMalloc(); CHECK_CONDITION(emergency_arena_start != NULL); } void *rv = LowLevelAlloc::AllocWithArena(size, emergency_arena); if (rv == NULL) { errno = ENOMEM; } return rv; } PERFTOOLS_DLL_DECL void EmergencyFree(void *p) { SpinLockHolder l(&emergency_malloc_lock); if (emergency_arena_start == NULL) { InitEmergencyMalloc(); CHECK_CONDITION(emergency_arena_start != NULL); free(p); return; } CHECK_CONDITION(emergency_arena_start); LowLevelAlloc::Free(p); } PERFTOOLS_DLL_DECL void *EmergencyRealloc(void *_old_ptr, size_t new_size) { if (_old_ptr == NULL) { return EmergencyMalloc(new_size); } if (new_size == 0) { EmergencyFree(_old_ptr); return NULL; } SpinLockHolder l(&emergency_malloc_lock); CHECK_CONDITION(emergency_arena_start); char *old_ptr = static_cast(_old_ptr); CHECK_CONDITION(old_ptr <= emergency_arena_end); CHECK_CONDITION(emergency_arena_start <= old_ptr); // NOTE: we don't know previous size of old_ptr chunk. So instead // of trying to figure out right size of copied memory, we just // copy largest possible size. We don't care about being slow. size_t old_ptr_size = emergency_arena_end - old_ptr; size_t copy_size = (new_size < old_ptr_size) ? new_size : old_ptr_size; void *new_ptr = LowLevelAlloc::AllocWithArena(new_size, emergency_arena); if (new_ptr == NULL) { errno = ENOMEM; return NULL; } memcpy(new_ptr, old_ptr, copy_size); LowLevelAlloc::Free(old_ptr); return new_ptr; } PERFTOOLS_DLL_DECL void *EmergencyCalloc(size_t n, size_t elem_size) { // Overflow check const size_t size = n * elem_size; if (elem_size != 0 && size / elem_size != n) return NULL; void *rv = EmergencyMalloc(size); if (rv != NULL) { memset(rv, 0, size); } return rv; } };