platform_bionic/libc/bionic/malloc_common.cpp
Christopher Ferris b4e560ed7c Add android_mallopt M_GET_DECAY_TIME_ENABLED.
The bionic benchmarks set the decay time in various ways, but
don't necessarily restore it properly. Add a new method for
getting the current decay time and then a way to restore it.

Right now the assumption is that the decay time defaults to zero,
but in the near future that assumption might be incorrect. Therefore
using this method will future proof the code.

Bug: 302212507

Test: Unit tests pass for both static and dynamic executables.
Test: Ran bionic benchmarks that were modified.
Change-Id: Ia77ff9ffee3081c5c1c02cb4309880f33b284e82
2023-10-30 15:30:16 -07:00

398 lines
14 KiB
C++

/*
* Copyright (C) 2009 The Android Open Source Project
* 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.
*
* 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.
*/
// Contains a thin layer that calls whatever real native allocator
// has been defined. For the libc shared library, this allows the
// implementation of a debug malloc that can intercept all of the allocation
// calls and add special debugging code to attempt to catch allocation
// errors. All of the debugging code is implemented in a separate shared
// library that is only loaded when the property "libc.debug.malloc.options"
// is set to a non-zero value.
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <platform/bionic/malloc.h>
#include <private/ScopedPthreadMutexLocker.h>
#include <private/bionic_config.h>
#include "gwp_asan_wrappers.h"
#include "heap_tagging.h"
#include "heap_zero_init.h"
#include "malloc_common.h"
#include "malloc_limit.h"
#include "malloc_tagged_pointers.h"
// =============================================================================
// Global variables instantations.
// =============================================================================
// Malloc hooks globals.
void* (*volatile __malloc_hook)(size_t, const void*);
void* (*volatile __realloc_hook)(void*, size_t, const void*);
void (*volatile __free_hook)(void*, const void*);
void* (*volatile __memalign_hook)(size_t, size_t, const void*);
// =============================================================================
// =============================================================================
// Allocation functions
// =============================================================================
extern "C" void* calloc(size_t n_elements, size_t elem_size) {
auto dispatch_table = GetDispatchTable();
if (__predict_false(dispatch_table != nullptr)) {
return MaybeTagPointer(dispatch_table->calloc(n_elements, elem_size));
}
void* result = Malloc(calloc)(n_elements, elem_size);
if (__predict_false(result == nullptr)) {
warning_log("calloc(%zu, %zu) failed: returning null pointer", n_elements, elem_size);
}
return MaybeTagPointer(result);
}
extern "C" void free(void* mem) {
auto dispatch_table = GetDispatchTable();
mem = MaybeUntagAndCheckPointer(mem);
if (__predict_false(dispatch_table != nullptr)) {
dispatch_table->free(mem);
} else {
Malloc(free)(mem);
}
}
extern "C" struct mallinfo mallinfo() {
auto dispatch_table = GetDispatchTable();
if (__predict_false(dispatch_table != nullptr)) {
return dispatch_table->mallinfo();
}
return Malloc(mallinfo)();
}
extern "C" int malloc_info(int options, FILE* fp) {
auto dispatch_table = GetDispatchTable();
if (__predict_false(dispatch_table != nullptr)) {
return dispatch_table->malloc_info(options, fp);
}
return Malloc(malloc_info)(options, fp);
}
extern "C" int mallopt(int param, int value) {
// Some are handled by libc directly rather than by the allocator.
if (param == M_BIONIC_SET_HEAP_TAGGING_LEVEL) {
ScopedPthreadMutexLocker locker(&g_heap_tagging_lock);
return SetHeapTaggingLevel(static_cast<HeapTaggingLevel>(value));
}
if (param == M_BIONIC_ZERO_INIT) {
return SetHeapZeroInitialize(value);
}
// The rest we pass on...
int retval;
auto dispatch_table = GetDispatchTable();
if (__predict_false(dispatch_table != nullptr)) {
retval = dispatch_table->mallopt(param, value);
} else {
retval = Malloc(mallopt)(param, value);
}
// Track the M_DECAY_TIME mallopt calls.
if (param == M_DECAY_TIME && retval == 1) {
__libc_globals.mutate([value](libc_globals* globals) {
if (value == 0) {
atomic_store(&globals->decay_time_enabled, false);
} else {
atomic_store(&globals->decay_time_enabled, true);
}
});
}
return retval;
}
extern "C" void* malloc(size_t bytes) {
auto dispatch_table = GetDispatchTable();
void *result;
if (__predict_false(dispatch_table != nullptr)) {
result = dispatch_table->malloc(bytes);
} else {
result = Malloc(malloc)(bytes);
}
if (__predict_false(result == nullptr)) {
warning_log("malloc(%zu) failed: returning null pointer", bytes);
return nullptr;
}
return MaybeTagPointer(result);
}
extern "C" size_t malloc_usable_size(const void* mem) {
auto dispatch_table = GetDispatchTable();
mem = MaybeUntagAndCheckPointer(mem);
if (__predict_false(dispatch_table != nullptr)) {
return dispatch_table->malloc_usable_size(mem);
}
return Malloc(malloc_usable_size)(mem);
}
extern "C" void* memalign(size_t alignment, size_t bytes) {
auto dispatch_table = GetDispatchTable();
if (__predict_false(dispatch_table != nullptr)) {
return MaybeTagPointer(dispatch_table->memalign(alignment, bytes));
}
void* result = Malloc(memalign)(alignment, bytes);
if (__predict_false(result == nullptr)) {
warning_log("memalign(%zu, %zu) failed: returning null pointer", alignment, bytes);
}
return MaybeTagPointer(result);
}
extern "C" int posix_memalign(void** memptr, size_t alignment, size_t size) {
auto dispatch_table = GetDispatchTable();
int result;
if (__predict_false(dispatch_table != nullptr)) {
result = dispatch_table->posix_memalign(memptr, alignment, size);
} else {
result = Malloc(posix_memalign)(memptr, alignment, size);
}
if (result == 0) {
*memptr = MaybeTagPointer(*memptr);
}
return result;
}
extern "C" void* aligned_alloc(size_t alignment, size_t size) {
auto dispatch_table = GetDispatchTable();
if (__predict_false(dispatch_table != nullptr)) {
return MaybeTagPointer(dispatch_table->aligned_alloc(alignment, size));
}
void* result = Malloc(aligned_alloc)(alignment, size);
if (__predict_false(result == nullptr)) {
warning_log("aligned_alloc(%zu, %zu) failed: returning null pointer", alignment, size);
}
return MaybeTagPointer(result);
}
extern "C" __attribute__((__noinline__)) void* realloc(void* old_mem, size_t bytes) {
auto dispatch_table = GetDispatchTable();
old_mem = MaybeUntagAndCheckPointer(old_mem);
if (__predict_false(dispatch_table != nullptr)) {
return MaybeTagPointer(dispatch_table->realloc(old_mem, bytes));
}
void* result = Malloc(realloc)(old_mem, bytes);
if (__predict_false(result == nullptr && bytes != 0)) {
warning_log("realloc(%p, %zu) failed: returning null pointer", old_mem, bytes);
}
return MaybeTagPointer(result);
}
extern "C" void* reallocarray(void* old_mem, size_t item_count, size_t item_size) {
size_t new_size;
if (__builtin_mul_overflow(item_count, item_size, &new_size)) {
warning_log("reallocaray(%p, %zu, %zu) failed: returning null pointer",
old_mem, item_count, item_size);
errno = ENOMEM;
return nullptr;
}
return realloc(old_mem, new_size);
}
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
extern "C" void* pvalloc(size_t bytes) {
auto dispatch_table = GetDispatchTable();
if (__predict_false(dispatch_table != nullptr)) {
return MaybeTagPointer(dispatch_table->pvalloc(bytes));
}
void* result = Malloc(pvalloc)(bytes);
if (__predict_false(result == nullptr)) {
warning_log("pvalloc(%zu) failed: returning null pointer", bytes);
}
return MaybeTagPointer(result);
}
extern "C" void* valloc(size_t bytes) {
auto dispatch_table = GetDispatchTable();
if (__predict_false(dispatch_table != nullptr)) {
return MaybeTagPointer(dispatch_table->valloc(bytes));
}
void* result = Malloc(valloc)(bytes);
if (__predict_false(result == nullptr)) {
warning_log("valloc(%zu) failed: returning null pointer", bytes);
}
return MaybeTagPointer(result);
}
#endif
// =============================================================================
struct CallbackWrapperArg {
void (*callback)(uintptr_t base, size_t size, void* arg);
void* arg;
};
void CallbackWrapper(uintptr_t base, size_t size, void* arg) {
CallbackWrapperArg* wrapper_arg = reinterpret_cast<CallbackWrapperArg*>(arg);
wrapper_arg->callback(
reinterpret_cast<uintptr_t>(MaybeTagPointer(reinterpret_cast<void*>(base))),
size, wrapper_arg->arg);
}
// =============================================================================
// Exported for use by libmemunreachable.
// =============================================================================
// Calls callback for every allocation in the anonymous heap mapping
// [base, base+size). Must be called between malloc_disable and malloc_enable.
// `base` in this can take either a tagged or untagged pointer, but we always
// provide a tagged pointer to the `base` argument of `callback` if the kernel
// supports tagged pointers.
extern "C" int malloc_iterate(uintptr_t base, size_t size,
void (*callback)(uintptr_t base, size_t size, void* arg), void* arg) {
auto dispatch_table = GetDispatchTable();
// Wrap the malloc_iterate callback we were provided, in order to provide
// pointer tagging support.
CallbackWrapperArg wrapper_arg;
wrapper_arg.callback = callback;
wrapper_arg.arg = arg;
uintptr_t untagged_base =
reinterpret_cast<uintptr_t>(UntagPointer(reinterpret_cast<void*>(base)));
if (__predict_false(dispatch_table != nullptr)) {
return dispatch_table->malloc_iterate(
untagged_base, size, CallbackWrapper, &wrapper_arg);
}
return Malloc(malloc_iterate)(
untagged_base, size, CallbackWrapper, &wrapper_arg);
}
// Disable calls to malloc so malloc_iterate gets a consistent view of
// allocated memory.
extern "C" void malloc_disable() {
auto dispatch_table = GetDispatchTable();
if (__predict_false(dispatch_table != nullptr)) {
return dispatch_table->malloc_disable();
}
return Malloc(malloc_disable)();
}
// Re-enable calls to malloc after a previous call to malloc_disable.
extern "C" void malloc_enable() {
auto dispatch_table = GetDispatchTable();
if (__predict_false(dispatch_table != nullptr)) {
return dispatch_table->malloc_enable();
}
return Malloc(malloc_enable)();
}
#if defined(LIBC_STATIC)
extern "C" ssize_t malloc_backtrace(void*, uintptr_t*, size_t) {
return 0;
}
#endif
#if __has_feature(hwaddress_sanitizer)
// FIXME: implement these in HWASan allocator.
extern "C" int __sanitizer_malloc_iterate(uintptr_t base __unused, size_t size __unused,
void (*callback)(uintptr_t base, size_t size, void* arg)
__unused,
void* arg __unused) {
return 0;
}
extern "C" void __sanitizer_malloc_disable() {
}
extern "C" void __sanitizer_malloc_enable() {
}
extern "C" int __sanitizer_malloc_info(int, FILE*) {
errno = ENOTSUP;
return -1;
}
#endif
// =============================================================================
// =============================================================================
// Platform-internal mallopt variant.
// =============================================================================
#if defined(LIBC_STATIC)
extern "C" bool android_mallopt(int opcode, void* arg, size_t arg_size) {
if (opcode == M_SET_ALLOCATION_LIMIT_BYTES) {
return LimitEnable(arg, arg_size);
}
if (opcode == M_INITIALIZE_GWP_ASAN) {
if (arg == nullptr || arg_size != sizeof(android_mallopt_gwp_asan_options_t)) {
errno = EINVAL;
return false;
}
return EnableGwpAsan(*reinterpret_cast<android_mallopt_gwp_asan_options_t*>(arg));
}
if (opcode == M_MEMTAG_STACK_IS_ON) {
if (arg == nullptr || arg_size != sizeof(bool)) {
errno = EINVAL;
return false;
}
*reinterpret_cast<bool*>(arg) = atomic_load(&__libc_globals->memtag_stack);
return true;
}
if (opcode == M_GET_DECAY_TIME_ENABLED) {
if (arg == nullptr || arg_size != sizeof(bool)) {
errno = EINVAL;
return false;
}
*reinterpret_cast<bool*>(arg) = atomic_load(&__libc_globals->decay_time_enabled);
return true;
}
errno = ENOTSUP;
return false;
}
#endif
// =============================================================================
static constexpr MallocDispatch __libc_malloc_default_dispatch __attribute__((unused)) = {
Malloc(calloc),
Malloc(free),
Malloc(mallinfo),
Malloc(malloc),
Malloc(malloc_usable_size),
Malloc(memalign),
Malloc(posix_memalign),
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
Malloc(pvalloc),
#endif
Malloc(realloc),
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
Malloc(valloc),
#endif
Malloc(malloc_iterate),
Malloc(malloc_disable),
Malloc(malloc_enable),
Malloc(mallopt),
Malloc(aligned_alloc),
Malloc(malloc_info),
};
const MallocDispatch* NativeAllocatorDispatch() {
return &__libc_malloc_default_dispatch;
}