ssize_t debug_malloc_backtrace(void* pointer, uintptr_t* frames, size_t frame_count) { if (DebugCallsDisabled() || pointer == nullptr) { return 0; } ScopedDisableDebugCalls disable; if (g_debug->need_header()) { Header* header; if (g_debug->config().options & TRACK_ALLOCS) { header = g_debug->GetHeader(pointer); if (!g_debug->track->Contains(header)) { return 0; } } else { header = reinterpret_cast<Header*>(pointer); } if (header->tag != DEBUG_TAG) { return 0; } if (g_debug->config().options & BACKTRACE) { BacktraceHeader* back_header = g_debug->GetAllocBacktrace(header); if (back_header->num_frames > 0) { if (frame_count > back_header->num_frames) { frame_count = back_header->num_frames; } memcpy(frames, &back_header->frames[0], frame_count * sizeof(uintptr_t)); return frame_count; } } } return 0; }
size_t debug_malloc_usable_size(void* pointer) { if (DebugCallsDisabled() || pointer == nullptr) { return g_dispatch->malloc_usable_size(pointer); } ScopedDisableDebugCalls disable; return internal_malloc_usable_size(pointer); }
void debug_free(void* pointer) { if (DebugCallsDisabled() || pointer == nullptr) { return g_dispatch->free(pointer); } ScopedDisableDebugCalls disable; internal_free(pointer); }
void* debug_malloc(size_t size) { if (DebugCallsDisabled()) { return g_dispatch->malloc(size); } ScopedDisableDebugCalls disable; return internal_malloc(size); }
extern "C" void* chk_pvalloc(size_t bytes) { if (DebugCallsDisabled()) { return g_malloc_dispatch->pvalloc(bytes); } size_t pagesize = getpagesize(); size_t size = BIONIC_ALIGN(bytes, pagesize); if (size < bytes) { // Overflow return NULL; } return chk_memalign(pagesize, size); }
int debug_posix_memalign(void** memptr, size_t alignment, size_t size) { if (DebugCallsDisabled()) { return g_dispatch->posix_memalign(memptr, alignment, size); } if (!powerof2(alignment)) { return EINVAL; } int saved_errno = errno; *memptr = debug_memalign(alignment, size); errno = saved_errno; return (*memptr != nullptr) ? 0 : ENOMEM; }
extern "C" int chk_posix_memalign(void** memptr, size_t alignment, size_t size) { if (DebugCallsDisabled()) { return g_malloc_dispatch->posix_memalign(memptr, alignment, size); } if (!powerof2(alignment)) { return EINVAL; } int saved_errno = errno; *memptr = chk_memalign(alignment, size); errno = saved_errno; return (*memptr != NULL) ? 0 : ENOMEM; }
void* debug_pvalloc(size_t bytes) { if (DebugCallsDisabled()) { return g_dispatch->pvalloc(bytes); } size_t pagesize = getpagesize(); size_t size = BIONIC_ALIGN(bytes, pagesize); if (size < bytes) { // Overflow errno = ENOMEM; return nullptr; } return debug_memalign(pagesize, size); }
extern "C" size_t chk_malloc_usable_size(const void* ptr) { if (DebugCallsDisabled()) { return g_malloc_dispatch->malloc_usable_size(ptr); } // malloc_usable_size returns 0 for NULL and unknown blocks. if (ptr == NULL) return 0; const hdr_t* hdr = const_meta(ptr); // The sentinel tail is written just after the request block bytes // so there is no extra room we can report here. return hdr->size; }
void* debug_calloc(size_t nmemb, size_t bytes) { if (DebugCallsDisabled()) { return g_dispatch->calloc(nmemb, bytes); } ScopedDisableDebugCalls disable; size_t size; if (__builtin_mul_overflow(nmemb, bytes, &size)) { // Overflow errno = ENOMEM; return nullptr; } if (size == 0) { size = 1; } size_t real_size; if (__builtin_add_overflow(size, g_debug->extra_bytes(), &real_size)) { // Overflow. errno = ENOMEM; return nullptr; } if (g_debug->need_header()) { // The above check will guarantee the multiply will not overflow. if (size > Header::max_size()) { errno = ENOMEM; return nullptr; } // Need to guarantee the alignment of the header. Header* header = reinterpret_cast<Header*>( g_dispatch->memalign(MINIMUM_ALIGNMENT_BYTES, real_size)); if (header == nullptr) { return nullptr; } memset(header, 0, g_dispatch->malloc_usable_size(header)); return InitHeader(header, header, size); } else { return g_dispatch->calloc(1, real_size); } }
extern "C" void* chk_malloc(size_t bytes) { // log_message("%s: %s\n", __FILE__, __FUNCTION__); if (DebugCallsDisabled()) { return g_malloc_dispatch->malloc(bytes); } size_t size = sizeof(hdr_t) + bytes + sizeof(ftr_t); if (size < bytes) { // Overflow errno = ENOMEM; return NULL; } hdr_t* hdr = static_cast<hdr_t*>(g_malloc_dispatch->malloc(size)); if (hdr) { hdr->base = hdr; hdr->bt_depth = GET_BACKTRACE(hdr->bt, MAX_BACKTRACE_DEPTH); add(hdr, bytes); return user(hdr); } return NULL; }
extern "C" void chk_free(void* ptr) { // log_message("%s: %s\n", __FILE__, __FUNCTION__); if (DebugCallsDisabled()) { return g_malloc_dispatch->free(ptr); } if (!ptr) /* ignore free(NULL) */ return; hdr_t* hdr = meta(ptr); if (del(hdr) < 0) { uintptr_t bt[MAX_BACKTRACE_DEPTH]; int depth = GET_BACKTRACE(bt, MAX_BACKTRACE_DEPTH); if (hdr->tag == BACKLOG_TAG) { log_message("+++ ALLOCATION %p SIZE %d BYTES MULTIPLY FREED!\n", user(hdr), hdr->size); if (g_backtrace_enabled) { log_message("+++ ALLOCATION %p SIZE %d ALLOCATED HERE:\n", user(hdr), hdr->size); log_backtrace(hdr->bt, hdr->bt_depth); /* hdr->freed_bt_depth should be nonzero here */ log_message("+++ ALLOCATION %p SIZE %d FIRST FREED HERE:\n", user(hdr), hdr->size); log_backtrace(hdr->freed_bt, hdr->freed_bt_depth); log_message("+++ ALLOCATION %p SIZE %d NOW BEING FREED HERE:\n", user(hdr), hdr->size); log_backtrace(bt, depth); } } else { log_message("+++ ALLOCATION %p IS CORRUPTED OR NOT ALLOCATED VIA TRACKER!\n", user(hdr)); if (g_backtrace_enabled) { log_backtrace(bt, depth); } } } else { hdr->freed_bt_depth = GET_BACKTRACE(hdr->freed_bt, MAX_BACKTRACE_DEPTH); add_to_backlog(hdr); } }
extern "C" void* chk_memalign(size_t alignment, size_t bytes) { if (DebugCallsDisabled()) { return g_malloc_dispatch->memalign(alignment, bytes); } if (alignment <= MALLOC_ALIGNMENT) { return chk_malloc(bytes); } // Make the alignment a power of two. if (!powerof2(alignment)) { alignment = BIONIC_ROUND_UP_POWER_OF_2(alignment); } // here, alignment is at least MALLOC_ALIGNMENT<<1 bytes // we will align by at least MALLOC_ALIGNMENT bytes // and at most alignment-MALLOC_ALIGNMENT bytes size_t size = (alignment-MALLOC_ALIGNMENT) + bytes; if (size < bytes) { // Overflow. return NULL; } void* base = g_malloc_dispatch->malloc(sizeof(hdr_t) + size + sizeof(ftr_t)); if (base != NULL) { // Check that the actual pointer that will be returned is aligned // properly. uintptr_t ptr = reinterpret_cast<uintptr_t>(user(reinterpret_cast<hdr_t*>(base))); if ((ptr % alignment) != 0) { // Align the pointer. ptr += ((-ptr) % alignment); } hdr_t* hdr = meta(reinterpret_cast<void*>(ptr)); hdr->base = base; hdr->bt_depth = GET_BACKTRACE(hdr->bt, MAX_BACKTRACE_DEPTH); add(hdr, bytes); return user(hdr); } return base; }
void* debug_valloc(size_t size) { if (DebugCallsDisabled()) { return g_dispatch->valloc(size); } return debug_memalign(getpagesize(), size); }
extern "C" void* chk_realloc(void* ptr, size_t bytes) { // log_message("%s: %s\n", __FILE__, __FUNCTION__); if (DebugCallsDisabled()) { return g_malloc_dispatch->realloc(ptr, bytes); } if (!ptr) { return chk_malloc(bytes); } #ifdef REALLOC_ZERO_BYTES_FREE if (!bytes) { chk_free(ptr); return NULL; } #endif hdr_t* hdr = meta(ptr); if (del(hdr) < 0) { uintptr_t bt[MAX_BACKTRACE_DEPTH]; int depth = GET_BACKTRACE(bt, MAX_BACKTRACE_DEPTH); if (hdr->tag == BACKLOG_TAG) { log_message("+++ REALLOCATION %p SIZE %d OF FREED MEMORY!\n", user(hdr), bytes, hdr->size); if (g_backtrace_enabled) { log_message("+++ ALLOCATION %p SIZE %d ALLOCATED HERE:\n", user(hdr), hdr->size); log_backtrace(hdr->bt, hdr->bt_depth); /* hdr->freed_bt_depth should be nonzero here */ log_message("+++ ALLOCATION %p SIZE %d FIRST FREED HERE:\n", user(hdr), hdr->size); log_backtrace(hdr->freed_bt, hdr->freed_bt_depth); log_message("+++ ALLOCATION %p SIZE %d NOW BEING REALLOCATED HERE:\n", user(hdr), hdr->size); log_backtrace(bt, depth); } /* We take the memory out of the backlog and fall through so the * reallocation below succeeds. Since we didn't really free it, we * can default to this behavior. */ del_from_backlog(hdr); } else { log_message("+++ REALLOCATION %p SIZE %d IS CORRUPTED OR NOT ALLOCATED VIA TRACKER!\n", user(hdr), bytes); if (g_backtrace_enabled) { log_backtrace(bt, depth); } // just get a whole new allocation and leak the old one return g_malloc_dispatch->realloc(0, bytes); // return realloc(user(hdr), bytes); // assuming it was allocated externally } } size_t size = sizeof(hdr_t) + bytes + sizeof(ftr_t); if (size < bytes) { // Overflow errno = ENOMEM; return NULL; } if (hdr->base != hdr) { // An allocation from memalign, so create another allocation and // copy the data out. void* newMem = g_malloc_dispatch->malloc(size); if (newMem == NULL) { return NULL; } memcpy(newMem, hdr, sizeof(hdr_t) + hdr->size); g_malloc_dispatch->free(hdr->base); hdr = static_cast<hdr_t*>(newMem); } else { hdr = static_cast<hdr_t*>(g_malloc_dispatch->realloc(hdr, size)); } if (hdr) { hdr->base = hdr; hdr->bt_depth = GET_BACKTRACE(hdr->bt, MAX_BACKTRACE_DEPTH); add(hdr, bytes); return user(hdr); } return NULL; }
extern "C" void* chk_valloc(size_t size) { if (DebugCallsDisabled()) { return g_malloc_dispatch->valloc(size); } return chk_memalign(getpagesize(), size); }
void* debug_realloc(void* pointer, size_t bytes) { if (DebugCallsDisabled()) { return g_dispatch->realloc(pointer, bytes); } ScopedDisableDebugCalls disable; if (pointer == nullptr) { return internal_malloc(bytes); } if (bytes == 0) { internal_free(pointer); return nullptr; } size_t real_size = bytes; if (g_debug->config().options & EXPAND_ALLOC) { real_size += g_debug->config().expand_alloc_bytes; if (real_size < bytes) { // Overflow. errno = ENOMEM; return nullptr; } } void* new_pointer; size_t prev_size; if (g_debug->need_header()) { if (bytes > Header::max_size()) { errno = ENOMEM; return nullptr; } Header* header = g_debug->GetHeader(pointer); if (header->tag != DEBUG_TAG) { LogTagError(header, pointer, "realloc"); return nullptr; } // Same size, do nothing. if (real_size == header->real_size()) { // Do not bother recording, this is essentially a nop. return pointer; } // Allocation is shrinking. if (real_size < header->usable_size) { header->size = real_size; if (*g_malloc_zygote_child) { header->set_zygote(); } if (g_debug->config().options & REAR_GUARD) { // Don't bother allocating a smaller pointer in this case, simply // change the header usable_size and reset the rear guard. header->usable_size = header->real_size(); memset(g_debug->GetRearGuard(header), g_debug->config().rear_guard_value, g_debug->config().rear_guard_bytes); } // Do not bother recording, this is essentially a nop. return pointer; } // Allocate the new size. new_pointer = internal_malloc(bytes); if (new_pointer == nullptr) { errno = ENOMEM; return nullptr; } prev_size = header->usable_size; memcpy(new_pointer, pointer, prev_size); internal_free(pointer); } else { prev_size = g_dispatch->malloc_usable_size(pointer); new_pointer = g_dispatch->realloc(pointer, real_size); if (new_pointer == nullptr) { return nullptr; } } if (g_debug->config().options & FILL_ON_ALLOC) { size_t bytes = internal_malloc_usable_size(new_pointer); if (bytes > g_debug->config().fill_on_alloc_bytes) { bytes = g_debug->config().fill_on_alloc_bytes; } if (bytes > prev_size) { memset(reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(new_pointer) + prev_size), g_debug->config().fill_alloc_value, bytes - prev_size); } } return new_pointer; }
void* debug_memalign(size_t alignment, size_t bytes) { if (DebugCallsDisabled()) { return g_dispatch->memalign(alignment, bytes); } ScopedDisableDebugCalls disable; if (bytes == 0) { bytes = 1; } void* pointer; if (g_debug->need_header()) { if (bytes > Header::max_size()) { errno = ENOMEM; return nullptr; } // Make the alignment a power of two. if (!powerof2(alignment)) { alignment = BIONIC_ROUND_UP_POWER_OF_2(alignment); } // Force the alignment to at least MINIMUM_ALIGNMENT_BYTES to guarantee // that the header is aligned properly. if (alignment < MINIMUM_ALIGNMENT_BYTES) { alignment = MINIMUM_ALIGNMENT_BYTES; } // We don't have any idea what the natural alignment of // the underlying native allocator is, so we always need to // over allocate. size_t real_size = alignment + bytes + g_debug->extra_bytes(); if (real_size < bytes) { // Overflow. errno = ENOMEM; return nullptr; } pointer = g_dispatch->malloc(real_size); if (pointer == nullptr) { return nullptr; } uintptr_t value = reinterpret_cast<uintptr_t>(pointer) + g_debug->pointer_offset(); // Now align the pointer. value += (-value % alignment); Header* header = g_debug->GetHeader(reinterpret_cast<void*>(value)); pointer = InitHeader(header, pointer, bytes); } else { size_t real_size = bytes + g_debug->extra_bytes(); if (real_size < bytes) { // Overflow. errno = ENOMEM; return nullptr; } pointer = g_dispatch->memalign(alignment, real_size); } if (pointer != nullptr && g_debug->config().options & FILL_ON_ALLOC) { size_t bytes = internal_malloc_usable_size(pointer); size_t fill_bytes = g_debug->config().fill_on_alloc_bytes; bytes = (bytes < fill_bytes) ? bytes : fill_bytes; memset(pointer, g_debug->config().fill_alloc_value, bytes); } return pointer; }