Merge "Implement malloc hooks."
am: af8d54a7d7
Change-Id: Ic89879c033b37b11c56ff557b0af3e95453dc1da
This commit is contained in:
commit
7cf17cc1b0
16 changed files with 1079 additions and 132 deletions
|
@ -72,6 +72,12 @@ static constexpr MallocDispatch __libc_malloc_default_dispatch
|
|||
Malloc(aligned_alloc),
|
||||
};
|
||||
|
||||
// Malloc hooks.
|
||||
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*);
|
||||
|
||||
// In a VM process, this is set to 1 after fork()ing out of zygote.
|
||||
int gMallocLeakZygoteChild = 0;
|
||||
|
||||
|
@ -190,17 +196,30 @@ extern "C" void* valloc(size_t bytes) {
|
|||
|
||||
extern "C" int __cxa_atexit(void (*func)(void *), void *arg, void *dso);
|
||||
|
||||
static const char* HOOKS_SHARED_LIB = "libc_malloc_hooks.so";
|
||||
static const char* HOOKS_PROPERTY_ENABLE = "libc.debug.hooks.enable";
|
||||
static const char* HOOKS_ENV_ENABLE = "LIBC_HOOKS_ENABLE";
|
||||
|
||||
static const char* DEBUG_SHARED_LIB = "libc_malloc_debug.so";
|
||||
static const char* DEBUG_MALLOC_PROPERTY_OPTIONS = "libc.debug.malloc.options";
|
||||
static const char* DEBUG_MALLOC_PROPERTY_PROGRAM = "libc.debug.malloc.program";
|
||||
static const char* DEBUG_MALLOC_ENV_OPTIONS = "LIBC_DEBUG_MALLOC_OPTIONS";
|
||||
static const char* DEBUG_PROPERTY_OPTIONS = "libc.debug.malloc.options";
|
||||
static const char* DEBUG_PROPERTY_PROGRAM = "libc.debug.malloc.program";
|
||||
static const char* DEBUG_ENV_OPTIONS = "LIBC_DEBUG_MALLOC_OPTIONS";
|
||||
|
||||
static void* libc_malloc_impl_handle = nullptr;
|
||||
enum FunctionEnum : uint8_t {
|
||||
FUNC_INITIALIZE,
|
||||
FUNC_FINALIZE,
|
||||
FUNC_GET_MALLOC_LEAK_INFO,
|
||||
FUNC_FREE_MALLOC_LEAK_INFO,
|
||||
FUNC_MALLOC_BACKTRACE,
|
||||
FUNC_LAST,
|
||||
};
|
||||
static void* g_functions[FUNC_LAST];
|
||||
|
||||
static void (*g_debug_finalize_func)();
|
||||
static void (*g_debug_get_malloc_leak_info_func)(uint8_t**, size_t*, size_t*, size_t*, size_t*);
|
||||
static void (*g_debug_free_malloc_leak_info_func)(uint8_t*);
|
||||
static ssize_t (*g_debug_malloc_backtrace_func)(void*, uintptr_t*, size_t);
|
||||
typedef void (*finalize_func_t)();
|
||||
typedef bool (*init_func_t)(const MallocDispatch*, int*, const char*);
|
||||
typedef void (*get_malloc_leak_info_func_t)(uint8_t**, size_t*, size_t*, size_t*, size_t*);
|
||||
typedef void (*free_malloc_leak_info_func_t)(uint8_t*);
|
||||
typedef ssize_t (*malloc_backtrace_func_t)(void*, uintptr_t*, size_t);
|
||||
|
||||
// =============================================================================
|
||||
// Log functions
|
||||
|
@ -225,17 +244,20 @@ static ssize_t (*g_debug_malloc_backtrace_func)(void*, uintptr_t*, size_t);
|
|||
// "*backtrace_size" is set to the maximum number of entries in the back trace
|
||||
extern "C" void get_malloc_leak_info(uint8_t** info, size_t* overall_size,
|
||||
size_t* info_size, size_t* total_memory, size_t* backtrace_size) {
|
||||
if (g_debug_get_malloc_leak_info_func == nullptr) {
|
||||
void* func = g_functions[FUNC_GET_MALLOC_LEAK_INFO];
|
||||
if (func == nullptr) {
|
||||
return;
|
||||
}
|
||||
g_debug_get_malloc_leak_info_func(info, overall_size, info_size, total_memory, backtrace_size);
|
||||
reinterpret_cast<get_malloc_leak_info_func_t>(func)(info, overall_size, info_size, total_memory,
|
||||
backtrace_size);
|
||||
}
|
||||
|
||||
extern "C" void free_malloc_leak_info(uint8_t* info) {
|
||||
if (g_debug_free_malloc_leak_info_func == nullptr) {
|
||||
void* func = g_functions[FUNC_FREE_MALLOC_LEAK_INFO];
|
||||
if (func == nullptr) {
|
||||
return;
|
||||
}
|
||||
g_debug_free_malloc_leak_info_func(info);
|
||||
reinterpret_cast<free_malloc_leak_info_func_t>(func)(info);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
@ -252,66 +274,56 @@ static bool InitMallocFunction(void* malloc_impl_handler, FunctionType* func, co
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool InitMalloc(void* malloc_impl_handler, MallocDispatch* table, const char* prefix) {
|
||||
if (!InitMallocFunction<MallocCalloc>(malloc_impl_handler, &table->calloc,
|
||||
prefix, "calloc")) {
|
||||
static bool InitMallocFunctions(void* impl_handler, MallocDispatch* table, const char* prefix) {
|
||||
if (!InitMallocFunction<MallocCalloc>(impl_handler, &table->calloc, prefix, "calloc")) {
|
||||
return false;
|
||||
}
|
||||
if (!InitMallocFunction<MallocFree>(malloc_impl_handler, &table->free,
|
||||
prefix, "free")) {
|
||||
if (!InitMallocFunction<MallocFree>(impl_handler, &table->free, prefix, "free")) {
|
||||
return false;
|
||||
}
|
||||
if (!InitMallocFunction<MallocMallinfo>(malloc_impl_handler, &table->mallinfo,
|
||||
prefix, "mallinfo")) {
|
||||
if (!InitMallocFunction<MallocMallinfo>(impl_handler, &table->mallinfo, prefix, "mallinfo")) {
|
||||
return false;
|
||||
}
|
||||
if (!InitMallocFunction<MallocMallopt>(malloc_impl_handler, &table->mallopt,
|
||||
prefix, "mallopt")) {
|
||||
if (!InitMallocFunction<MallocMallopt>(impl_handler, &table->mallopt, prefix, "mallopt")) {
|
||||
return false;
|
||||
}
|
||||
if (!InitMallocFunction<MallocMalloc>(malloc_impl_handler, &table->malloc,
|
||||
prefix, "malloc")) {
|
||||
if (!InitMallocFunction<MallocMalloc>(impl_handler, &table->malloc, prefix, "malloc")) {
|
||||
return false;
|
||||
}
|
||||
if (!InitMallocFunction<MallocMallocUsableSize>(
|
||||
malloc_impl_handler, &table->malloc_usable_size, prefix, "malloc_usable_size")) {
|
||||
if (!InitMallocFunction<MallocMallocUsableSize>(impl_handler, &table->malloc_usable_size, prefix,
|
||||
"malloc_usable_size")) {
|
||||
return false;
|
||||
}
|
||||
if (!InitMallocFunction<MallocMemalign>(malloc_impl_handler, &table->memalign,
|
||||
prefix, "memalign")) {
|
||||
if (!InitMallocFunction<MallocMemalign>(impl_handler, &table->memalign, prefix, "memalign")) {
|
||||
return false;
|
||||
}
|
||||
if (!InitMallocFunction<MallocPosixMemalign>(malloc_impl_handler, &table->posix_memalign,
|
||||
prefix, "posix_memalign")) {
|
||||
if (!InitMallocFunction<MallocPosixMemalign>(impl_handler, &table->posix_memalign, prefix,
|
||||
"posix_memalign")) {
|
||||
return false;
|
||||
}
|
||||
if (!InitMallocFunction<MallocAlignedAlloc>(malloc_impl_handler, &table->aligned_alloc,
|
||||
if (!InitMallocFunction<MallocAlignedAlloc>(impl_handler, &table->aligned_alloc,
|
||||
prefix, "aligned_alloc")) {
|
||||
return false;
|
||||
}
|
||||
if (!InitMallocFunction<MallocRealloc>(malloc_impl_handler, &table->realloc,
|
||||
prefix, "realloc")) {
|
||||
if (!InitMallocFunction<MallocRealloc>(impl_handler, &table->realloc, prefix, "realloc")) {
|
||||
return false;
|
||||
}
|
||||
if (!InitMallocFunction<MallocIterate>(malloc_impl_handler, &table->iterate,
|
||||
prefix, "iterate")) {
|
||||
if (!InitMallocFunction<MallocIterate>(impl_handler, &table->iterate, prefix, "iterate")) {
|
||||
return false;
|
||||
}
|
||||
if (!InitMallocFunction<MallocMallocDisable>(malloc_impl_handler, &table->malloc_disable,
|
||||
prefix, "malloc_disable")) {
|
||||
if (!InitMallocFunction<MallocMallocDisable>(impl_handler, &table->malloc_disable, prefix,
|
||||
"malloc_disable")) {
|
||||
return false;
|
||||
}
|
||||
if (!InitMallocFunction<MallocMallocEnable>(malloc_impl_handler, &table->malloc_enable,
|
||||
prefix, "malloc_enable")) {
|
||||
if (!InitMallocFunction<MallocMallocEnable>(impl_handler, &table->malloc_enable, prefix,
|
||||
"malloc_enable")) {
|
||||
return false;
|
||||
}
|
||||
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
|
||||
if (!InitMallocFunction<MallocPvalloc>(malloc_impl_handler, &table->pvalloc,
|
||||
prefix, "pvalloc")) {
|
||||
if (!InitMallocFunction<MallocPvalloc>(impl_handler, &table->pvalloc, prefix, "pvalloc")) {
|
||||
return false;
|
||||
}
|
||||
if (!InitMallocFunction<MallocValloc>(malloc_impl_handler, &table->valloc,
|
||||
prefix, "valloc")) {
|
||||
if (!InitMallocFunction<MallocValloc>(impl_handler, &table->valloc, prefix, "valloc")) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
@ -328,102 +340,114 @@ static void malloc_fini_impl(void*) {
|
|||
fclose(stdout);
|
||||
fclose(stderr);
|
||||
|
||||
g_debug_finalize_func();
|
||||
reinterpret_cast<finalize_func_t>(g_functions[FUNC_FINALIZE])();
|
||||
}
|
||||
|
||||
static bool CheckLoadMallocHooks(char** options) {
|
||||
char* env = getenv(HOOKS_ENV_ENABLE);
|
||||
if ((env == nullptr || env[0] == '\0' || env[0] == '0') &&
|
||||
(__system_property_get(HOOKS_PROPERTY_ENABLE, *options) == 0 || *options[0] == '\0' || *options[0] == '0')) {
|
||||
return false;
|
||||
}
|
||||
*options = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool CheckLoadMallocDebug(char** options) {
|
||||
// If DEBUG_MALLOC_ENV_OPTIONS is set then it overrides the system properties.
|
||||
char* env = getenv(DEBUG_ENV_OPTIONS);
|
||||
if (env == nullptr || env[0] == '\0') {
|
||||
if (__system_property_get(DEBUG_PROPERTY_OPTIONS, *options) == 0 || *options[0] == '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check to see if only a specific program should have debug malloc enabled.
|
||||
char program[PROP_VALUE_MAX];
|
||||
if (__system_property_get(DEBUG_PROPERTY_PROGRAM, program) != 0 &&
|
||||
strstr(getprogname(), program) == nullptr) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
*options = env;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ClearGlobalFunctions() {
|
||||
for (size_t i = 0; i < FUNC_LAST; i++) {
|
||||
g_functions[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void* LoadSharedLibrary(const char* shared_lib, const char* prefix, MallocDispatch* dispatch_table) {
|
||||
void* impl_handle = dlopen(shared_lib, RTLD_NOW | RTLD_LOCAL);
|
||||
if (impl_handle == nullptr) {
|
||||
error_log("%s: Unable to open shared library %s: %s", getprogname(), shared_lib, dlerror());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static constexpr const char* names[] = {
|
||||
"initialize",
|
||||
"finalize",
|
||||
"get_malloc_leak_info",
|
||||
"free_malloc_leak_info",
|
||||
"malloc_backtrace",
|
||||
};
|
||||
for (size_t i = 0; i < FUNC_LAST; i++) {
|
||||
char symbol[128];
|
||||
snprintf(symbol, sizeof(symbol), "%s_%s", prefix, names[i]);
|
||||
g_functions[i] = dlsym(impl_handle, symbol);
|
||||
if (g_functions[i] == nullptr) {
|
||||
error_log("%s: %s routine not found in %s", getprogname(), symbol, shared_lib);
|
||||
dlclose(impl_handle);
|
||||
ClearGlobalFunctions();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!InitMallocFunctions(impl_handle, dispatch_table, prefix)) {
|
||||
dlclose(impl_handle);
|
||||
ClearGlobalFunctions();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return impl_handle;
|
||||
}
|
||||
|
||||
// Initializes memory allocation framework once per process.
|
||||
static void malloc_init_impl(libc_globals* globals) {
|
||||
char value[PROP_VALUE_MAX];
|
||||
|
||||
// If DEBUG_MALLOC_ENV_OPTIONS is set then it overrides the system properties.
|
||||
const char* options = getenv(DEBUG_MALLOC_ENV_OPTIONS);
|
||||
if (options == nullptr || options[0] == '\0') {
|
||||
if (__system_property_get(DEBUG_MALLOC_PROPERTY_OPTIONS, value) == 0 || value[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
options = value;
|
||||
|
||||
// Check to see if only a specific program should have debug malloc enabled.
|
||||
char program[PROP_VALUE_MAX];
|
||||
if (__system_property_get(DEBUG_MALLOC_PROPERTY_PROGRAM, program) != 0 &&
|
||||
strstr(getprogname(), program) == nullptr) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Load the debug malloc shared library.
|
||||
void* malloc_impl_handle = dlopen(DEBUG_SHARED_LIB, RTLD_NOW | RTLD_LOCAL);
|
||||
if (malloc_impl_handle == nullptr) {
|
||||
error_log("%s: Unable to open debug malloc shared library %s: %s",
|
||||
getprogname(), DEBUG_SHARED_LIB, dlerror());
|
||||
const char* prefix;
|
||||
const char* shared_lib;
|
||||
char prop[PROP_VALUE_MAX];
|
||||
char* options = prop;
|
||||
// Prefer malloc debug since it existed first and is a more complete
|
||||
// malloc interceptor than the hooks.
|
||||
if (CheckLoadMallocDebug(&options)) {
|
||||
prefix = "debug";
|
||||
shared_lib = DEBUG_SHARED_LIB;
|
||||
} else if (CheckLoadMallocHooks(&options)) {
|
||||
prefix = "hooks";
|
||||
shared_lib = HOOKS_SHARED_LIB;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize malloc debugging in the loaded module.
|
||||
auto init_func = reinterpret_cast<bool (*)(const MallocDispatch*, int*, const char*)>(
|
||||
dlsym(malloc_impl_handle, "debug_initialize"));
|
||||
if (init_func == nullptr) {
|
||||
error_log("%s: debug_initialize routine not found in %s", getprogname(), DEBUG_SHARED_LIB);
|
||||
dlclose(malloc_impl_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the syms for the external functions.
|
||||
void* finalize_sym = dlsym(malloc_impl_handle, "debug_finalize");
|
||||
if (finalize_sym == nullptr) {
|
||||
error_log("%s: debug_finalize routine not found in %s", getprogname(), DEBUG_SHARED_LIB);
|
||||
dlclose(malloc_impl_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
void* get_leak_info_sym = dlsym(malloc_impl_handle, "debug_get_malloc_leak_info");
|
||||
if (get_leak_info_sym == nullptr) {
|
||||
error_log("%s: debug_get_malloc_leak_info routine not found in %s", getprogname(),
|
||||
DEBUG_SHARED_LIB);
|
||||
dlclose(malloc_impl_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
void* free_leak_info_sym = dlsym(malloc_impl_handle, "debug_free_malloc_leak_info");
|
||||
if (free_leak_info_sym == nullptr) {
|
||||
error_log("%s: debug_free_malloc_leak_info routine not found in %s", getprogname(),
|
||||
DEBUG_SHARED_LIB);
|
||||
dlclose(malloc_impl_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
void* malloc_backtrace_sym = dlsym(malloc_impl_handle, "debug_malloc_backtrace");
|
||||
if (malloc_backtrace_sym == nullptr) {
|
||||
error_log("%s: debug_malloc_backtrace routine not found in %s", getprogname(),
|
||||
DEBUG_SHARED_LIB);
|
||||
dlclose(malloc_impl_handle);
|
||||
MallocDispatch dispatch_table;
|
||||
void* impl_handle = LoadSharedLibrary(shared_lib, prefix, &dispatch_table);
|
||||
if (impl_handle == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
init_func_t init_func = reinterpret_cast<init_func_t>(g_functions[FUNC_INITIALIZE]);
|
||||
if (!init_func(&__libc_malloc_default_dispatch, &gMallocLeakZygoteChild, options)) {
|
||||
dlclose(malloc_impl_handle);
|
||||
dlclose(impl_handle);
|
||||
ClearGlobalFunctions();
|
||||
return;
|
||||
}
|
||||
|
||||
MallocDispatch malloc_dispatch_table;
|
||||
if (!InitMalloc(malloc_impl_handle, &malloc_dispatch_table, "debug")) {
|
||||
auto finalize_func = reinterpret_cast<void (*)()>(finalize_sym);
|
||||
finalize_func();
|
||||
dlclose(malloc_impl_handle);
|
||||
return;
|
||||
}
|
||||
globals->malloc_dispatch = dispatch_table;
|
||||
|
||||
g_debug_finalize_func = reinterpret_cast<void (*)()>(finalize_sym);
|
||||
g_debug_get_malloc_leak_info_func = reinterpret_cast<void (*)(
|
||||
uint8_t**, size_t*, size_t*, size_t*, size_t*)>(get_leak_info_sym);
|
||||
g_debug_free_malloc_leak_info_func = reinterpret_cast<void (*)(uint8_t*)>(free_leak_info_sym);
|
||||
g_debug_malloc_backtrace_func = reinterpret_cast<ssize_t (*)(
|
||||
void*, uintptr_t*, size_t)>(malloc_backtrace_sym);
|
||||
|
||||
globals->malloc_dispatch = malloc_dispatch_table;
|
||||
libc_malloc_impl_handle = malloc_impl_handle;
|
||||
|
||||
info_log("%s: malloc debug enabled", getprogname());
|
||||
info_log("%s: malloc %s enabled", getprogname(), prefix);
|
||||
|
||||
// Use atexit to trigger the cleanup function. This avoids a problem
|
||||
// where another atexit function is used to cleanup allocated memory,
|
||||
|
@ -478,10 +502,11 @@ extern "C" void malloc_enable() {
|
|||
|
||||
#ifndef LIBC_STATIC
|
||||
extern "C" ssize_t malloc_backtrace(void* pointer, uintptr_t* frames, size_t frame_count) {
|
||||
if (g_debug_malloc_backtrace_func == nullptr) {
|
||||
void* func = g_functions[FUNC_MALLOC_BACKTRACE];
|
||||
if (func == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return g_debug_malloc_backtrace_func(pointer, frames, frame_count);
|
||||
return reinterpret_cast<malloc_backtrace_func_t>(func)(pointer, frames, frame_count);
|
||||
}
|
||||
#else
|
||||
extern "C" ssize_t malloc_backtrace(void*, uintptr_t*, size_t) {
|
||||
|
|
|
@ -81,6 +81,14 @@ int malloc_info(int __must_be_zero, FILE* __fp) __INTRODUCED_IN(23);
|
|||
#define M_DECAY_TIME -100
|
||||
int mallopt(int __option, int __value) __INTRODUCED_IN(26);
|
||||
|
||||
/*
|
||||
* Memory Allocation Hooks
|
||||
*/
|
||||
extern void* (*volatile __malloc_hook)(size_t, const void*) __INTRODUCED_IN(28);
|
||||
extern void* (*volatile __realloc_hook)(void*, size_t, const void*) __INTRODUCED_IN(28);
|
||||
extern void (*volatile __free_hook)(void*, const void*) __INTRODUCED_IN(28);
|
||||
extern void* (*volatile __memalign_hook)(size_t, size_t, const void*) __INTRODUCED_IN(28);
|
||||
|
||||
__END_DECLS
|
||||
|
||||
#endif /* LIBC_INCLUDE_MALLOC_H_ */
|
||||
|
|
|
@ -1321,7 +1321,11 @@ LIBC_O {
|
|||
LIBC_P { # introduced=P
|
||||
global:
|
||||
__freading;
|
||||
__free_hook;
|
||||
__fwriting;
|
||||
__malloc_hook;
|
||||
__memalign_hook;
|
||||
__realloc_hook;
|
||||
aligned_alloc;
|
||||
endhostent;
|
||||
endnetent;
|
||||
|
|
|
@ -1241,7 +1241,11 @@ LIBC_O {
|
|||
LIBC_P { # introduced=P
|
||||
global:
|
||||
__freading;
|
||||
__free_hook;
|
||||
__fwriting;
|
||||
__malloc_hook;
|
||||
__memalign_hook;
|
||||
__realloc_hook;
|
||||
aligned_alloc;
|
||||
endhostent;
|
||||
endnetent;
|
||||
|
|
|
@ -1346,7 +1346,11 @@ LIBC_O {
|
|||
LIBC_P { # introduced=P
|
||||
global:
|
||||
__freading;
|
||||
__free_hook;
|
||||
__fwriting;
|
||||
__malloc_hook;
|
||||
__memalign_hook;
|
||||
__realloc_hook;
|
||||
aligned_alloc;
|
||||
endhostent;
|
||||
endnetent;
|
||||
|
|
|
@ -1305,7 +1305,11 @@ LIBC_O {
|
|||
LIBC_P { # introduced=P
|
||||
global:
|
||||
__freading;
|
||||
__free_hook;
|
||||
__fwriting;
|
||||
__malloc_hook;
|
||||
__memalign_hook;
|
||||
__realloc_hook;
|
||||
aligned_alloc;
|
||||
endhostent;
|
||||
endnetent;
|
||||
|
|
|
@ -1241,7 +1241,11 @@ LIBC_O {
|
|||
LIBC_P { # introduced=P
|
||||
global:
|
||||
__freading;
|
||||
__free_hook;
|
||||
__fwriting;
|
||||
__malloc_hook;
|
||||
__memalign_hook;
|
||||
__realloc_hook;
|
||||
aligned_alloc;
|
||||
endhostent;
|
||||
endnetent;
|
||||
|
|
|
@ -1303,7 +1303,11 @@ LIBC_O {
|
|||
LIBC_P { # introduced=P
|
||||
global:
|
||||
__freading;
|
||||
__free_hook;
|
||||
__fwriting;
|
||||
__malloc_hook;
|
||||
__memalign_hook;
|
||||
__realloc_hook;
|
||||
aligned_alloc;
|
||||
endhostent;
|
||||
endnetent;
|
||||
|
|
|
@ -1241,7 +1241,11 @@ LIBC_O {
|
|||
LIBC_P { # introduced=P
|
||||
global:
|
||||
__freading;
|
||||
__free_hook;
|
||||
__fwriting;
|
||||
__malloc_hook;
|
||||
__memalign_hook;
|
||||
__realloc_hook;
|
||||
aligned_alloc;
|
||||
endhostent;
|
||||
endnetent;
|
||||
|
|
66
libc/malloc_hooks/Android.bp
Normal file
66
libc/malloc_hooks/Android.bp
Normal file
|
@ -0,0 +1,66 @@
|
|||
// ==============================================================
|
||||
// libc_malloc_hooks.so
|
||||
// ==============================================================
|
||||
cc_library {
|
||||
name: "libc_malloc_hooks",
|
||||
|
||||
srcs: [
|
||||
"malloc_hooks.cpp",
|
||||
],
|
||||
|
||||
static_libs: [
|
||||
"libasync_safe",
|
||||
],
|
||||
|
||||
multilib: {
|
||||
lib32: {
|
||||
version_script: "exported32.map",
|
||||
},
|
||||
lib64: {
|
||||
version_script: "exported64.map",
|
||||
},
|
||||
},
|
||||
include_dirs: ["bionic/libc"],
|
||||
|
||||
sanitize: {
|
||||
never: true,
|
||||
},
|
||||
native_coverage: false,
|
||||
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
"-fno-stack-protector",
|
||||
],
|
||||
}
|
||||
|
||||
// ==============================================================
|
||||
// Unit Tests
|
||||
// ==============================================================
|
||||
cc_test {
|
||||
name: "malloc_hooks_unit_tests",
|
||||
multilib: {
|
||||
lib32: {
|
||||
suffix: "32",
|
||||
},
|
||||
lib64: {
|
||||
suffix: "64",
|
||||
},
|
||||
},
|
||||
|
||||
srcs: [
|
||||
"tests/malloc_hooks_tests.cpp",
|
||||
],
|
||||
|
||||
whole_static_libs: ["libc_malloc_hooks"],
|
||||
|
||||
shared_libs: ["libbase"],
|
||||
|
||||
local_include_dirs: ["tests"],
|
||||
include_dirs: ["bionic/libc", "bionic"],
|
||||
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
],
|
||||
}
|
118
libc/malloc_hooks/README.md
Normal file
118
libc/malloc_hooks/README.md
Normal file
|
@ -0,0 +1,118 @@
|
|||
Malloc Hooks
|
||||
============
|
||||
|
||||
Malloc hooks allows a program to intercept all allocation/free calls that
|
||||
happen during execution. It is only available in Android P and newer versions
|
||||
of the OS.
|
||||
|
||||
There are two ways to enable these hooks, set a special system
|
||||
property, or set a special environment variable and run your app/program.
|
||||
|
||||
When malloc hooks is enabled, it works by adding a shim layer that replaces
|
||||
the normal allocation calls. The replaced calls are:
|
||||
|
||||
* `malloc`
|
||||
* `free`
|
||||
* `calloc`
|
||||
* `realloc`
|
||||
* `posix_memalign`
|
||||
* `memalign`
|
||||
* `aligned_alloc`
|
||||
* `malloc_usable_size`
|
||||
|
||||
On 32 bit systems, these two deprecated functions are also replaced:
|
||||
|
||||
* `pvalloc`
|
||||
* `valloc`
|
||||
|
||||
These four hooks are defined in malloc.h:
|
||||
|
||||
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*);
|
||||
|
||||
When malloc is called and \_\_malloc\_hook has been set, then the hook
|
||||
function is called instead.
|
||||
|
||||
When realloc is called and \_\_realloc\_hook has been set, then the hook
|
||||
function is called instead.
|
||||
|
||||
When free is called and \_\_free\_hook has been set, then the hook
|
||||
function is called instead.
|
||||
|
||||
When memalign is called and \_\_memalign\_hook has been set, then the hook
|
||||
function is called instead.
|
||||
|
||||
For posix\_memalign, if \_\_memalign\_hook has been set, then the hook is
|
||||
called, but only if alignment is a power of 2.
|
||||
|
||||
For aligned\_alloc, if \_\_memalign\_hook has been set, then the hook is
|
||||
called, but only if alignment is a power of 2.
|
||||
|
||||
For calloc, if \_\_malloc\_hook has been set, then the hook function is
|
||||
called, then the allocated memory is set to zero.
|
||||
|
||||
For the two deprecated functions pvalloc and valloc, if \_\_memalign\_hook
|
||||
has been set, then the hook is called with an appropriate alignment value.
|
||||
|
||||
There is no hook for malloc\_usable\_size as of now.
|
||||
|
||||
These hooks can be set at any time, but there is no thread safety, so
|
||||
the caller must guarantee that it does not depend on allocations/frees
|
||||
occurring at the same time.
|
||||
|
||||
Implementation Details
|
||||
======================
|
||||
When malloc hooks is enabled, then the hook pointers are set to
|
||||
the current default allocation functions. It is expected that if an
|
||||
app does intercept the allocation/free calls, it will eventually call
|
||||
the original hook function to do allocations. If the app does not do this,
|
||||
it runs the risk of crashing whenever a malloc\_usable\_size call is made.
|
||||
|
||||
Example Implementation
|
||||
======================
|
||||
Below is a simple implementation intercepting only malloc/calloc calls.
|
||||
|
||||
void* new_malloc_hook(size_t bytes, const char* arg) {
|
||||
return orig_malloc_hook(bytes, arg);
|
||||
}
|
||||
|
||||
void orig_malloc_hook = __malloc_hook;
|
||||
__malloc_hook = new_malloc_hook;
|
||||
|
||||
Enabling Examples
|
||||
=================
|
||||
|
||||
### For platform developers
|
||||
|
||||
Enable the hooks for all processes:
|
||||
|
||||
adb shell stop
|
||||
adb shell setprop libc.debug.malloc.hooks 1
|
||||
adb shell start
|
||||
|
||||
Enable malloc debug using an environment variable:
|
||||
|
||||
adb shell
|
||||
# export LIBC_HOOK_ENABLE=1
|
||||
# ls
|
||||
|
||||
Any process spawned from this shell will run with malloc hooks enabled.
|
||||
|
||||
### For app developers
|
||||
|
||||
Enable malloc hooks for a specific program/application:
|
||||
|
||||
adb shell setprop wrap.<APP> '"LIBC_HOOKS_ENABLE=1"'
|
||||
|
||||
For example, to enable malloc hooks for the google search box:
|
||||
|
||||
adb shell setprop wrap.com.google.android.googlequicksearchbox '"LIBC_HOOKS_ENABLE=1 logwrapper"'
|
||||
adb shell am force-stop com.google.android.googlequicksearchbox
|
||||
|
||||
NOTE: On pre-O versions of the Android OS, property names had a length limit
|
||||
of 32. This meant that to create a wrap property with the name of the app, it
|
||||
was necessary to truncate the name to fit. On O, property names can be
|
||||
an order of magnitude larger, so there should be no need to truncate the name
|
||||
at all.
|
26
libc/malloc_hooks/exported32.map
Normal file
26
libc/malloc_hooks/exported32.map
Normal file
|
@ -0,0 +1,26 @@
|
|||
LIBC_MALLOC_HOOKS {
|
||||
global:
|
||||
hooks_aligned_alloc;
|
||||
hooks_calloc;
|
||||
hooks_finalize;
|
||||
hooks_free;
|
||||
hooks_free_malloc_leak_info;
|
||||
hooks_get_malloc_leak_info;
|
||||
hooks_initialize;
|
||||
hooks_iterate;
|
||||
hooks_mallinfo;
|
||||
hooks_malloc;
|
||||
hooks_malloc_backtrace;
|
||||
hooks_malloc_disable;
|
||||
hooks_malloc_enable;
|
||||
hooks_malloc_usable_size;
|
||||
hooks_mallopt;
|
||||
hooks_memalign;
|
||||
hooks_posix_memalign;
|
||||
hooks_pvalloc;
|
||||
hooks_realloc;
|
||||
hooks_valloc;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
24
libc/malloc_hooks/exported64.map
Normal file
24
libc/malloc_hooks/exported64.map
Normal file
|
@ -0,0 +1,24 @@
|
|||
LIBC_MALLOC_HOOKS {
|
||||
global:
|
||||
hooks_aligned_alloc;
|
||||
hooks_calloc;
|
||||
hooks_finalize;
|
||||
hooks_free;
|
||||
hooks_free_malloc_leak_info;
|
||||
hooks_get_malloc_leak_info;
|
||||
hooks_initialize;
|
||||
hooks_iterate;
|
||||
hooks_mallinfo;
|
||||
hooks_malloc;
|
||||
hooks_malloc_backtrace;
|
||||
hooks_malloc_disable;
|
||||
hooks_malloc_enable;
|
||||
hooks_malloc_usable_size;
|
||||
hooks_mallopt;
|
||||
hooks_memalign;
|
||||
hooks_posix_memalign;
|
||||
hooks_realloc;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
234
libc/malloc_hooks/malloc_hooks.cpp
Normal file
234
libc/malloc_hooks/malloc_hooks.cpp
Normal file
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
* Copyright (C) 2018 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.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <malloc.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sys/param.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <private/bionic_malloc_dispatch.h>
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Global Data
|
||||
// ------------------------------------------------------------------------
|
||||
const MallocDispatch* g_dispatch;
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Use C style prototypes for all exported functions. This makes it easy
|
||||
// to do dlsym lookups during libc initialization when hooks are enabled.
|
||||
// ------------------------------------------------------------------------
|
||||
__BEGIN_DECLS
|
||||
|
||||
bool hooks_initialize(const MallocDispatch* malloc_dispatch, int* malloc_zygote_child,
|
||||
const char* options);
|
||||
void hooks_finalize();
|
||||
void hooks_get_malloc_leak_info(
|
||||
uint8_t** info, size_t* overall_size, size_t* info_size, size_t* total_memory,
|
||||
size_t* backtrace_size);
|
||||
ssize_t hooks_malloc_backtrace(void* pointer, uintptr_t* frames, size_t frame_count);
|
||||
void hooks_free_malloc_leak_info(uint8_t* info);
|
||||
size_t hooks_malloc_usable_size(void* pointer);
|
||||
void* hooks_malloc(size_t size);
|
||||
void hooks_free(void* pointer);
|
||||
void* hooks_memalign(size_t alignment, size_t bytes);
|
||||
void* hooks_aligned_alloc(size_t alignment, size_t bytes);
|
||||
void* hooks_realloc(void* pointer, size_t bytes);
|
||||
void* hooks_calloc(size_t nmemb, size_t bytes);
|
||||
struct mallinfo hooks_mallinfo();
|
||||
int hooks_mallopt(int param, int value);
|
||||
int hooks_posix_memalign(void** memptr, size_t alignment, size_t size);
|
||||
int hooks_iterate(uintptr_t base, size_t size,
|
||||
void (*callback)(uintptr_t base, size_t size, void* arg), void* arg);
|
||||
void hooks_malloc_disable();
|
||||
void hooks_malloc_enable();
|
||||
|
||||
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
|
||||
void* hooks_pvalloc(size_t bytes);
|
||||
void* hooks_valloc(size_t size);
|
||||
#endif
|
||||
|
||||
static void* default_malloc_hook(size_t bytes, const void*) {
|
||||
return g_dispatch->malloc(bytes);
|
||||
}
|
||||
|
||||
static void* default_realloc_hook(void* pointer, size_t bytes, const void*) {
|
||||
return g_dispatch->realloc(pointer, bytes);
|
||||
}
|
||||
|
||||
static void default_free_hook(void* pointer, const void*) {
|
||||
g_dispatch->free(pointer);
|
||||
}
|
||||
|
||||
static void* default_memalign_hook(size_t alignment, size_t bytes, const void*) {
|
||||
return g_dispatch->memalign(alignment, bytes);
|
||||
}
|
||||
|
||||
__END_DECLS
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
bool hooks_initialize(const MallocDispatch* malloc_dispatch, int*, const char*) {
|
||||
g_dispatch = malloc_dispatch;
|
||||
__malloc_hook = default_malloc_hook;
|
||||
__realloc_hook = default_realloc_hook;
|
||||
__free_hook = default_free_hook;
|
||||
__memalign_hook = default_memalign_hook;
|
||||
return true;
|
||||
}
|
||||
|
||||
void hooks_finalize() {
|
||||
}
|
||||
|
||||
void hooks_get_malloc_leak_info(uint8_t** info, size_t* overall_size,
|
||||
size_t* info_size, size_t* total_memory, size_t* backtrace_size) {
|
||||
*info = nullptr;
|
||||
*overall_size = 0;
|
||||
*info_size = 0;
|
||||
*total_memory = 0;
|
||||
*backtrace_size = 0;
|
||||
}
|
||||
|
||||
void hooks_free_malloc_leak_info(uint8_t*) {
|
||||
}
|
||||
|
||||
size_t hooks_malloc_usable_size(void* pointer) {
|
||||
return g_dispatch->malloc_usable_size(pointer);
|
||||
}
|
||||
|
||||
void* hooks_malloc(size_t size) {
|
||||
if (__malloc_hook != nullptr && __malloc_hook != default_malloc_hook) {
|
||||
return __malloc_hook(size, __builtin_return_address(0));
|
||||
}
|
||||
return g_dispatch->malloc(size);
|
||||
}
|
||||
|
||||
void hooks_free(void* pointer) {
|
||||
if (__free_hook != nullptr && __free_hook != default_free_hook) {
|
||||
return __free_hook(pointer, __builtin_return_address(0));
|
||||
}
|
||||
return g_dispatch->free(pointer);
|
||||
}
|
||||
|
||||
void* hooks_memalign(size_t alignment, size_t bytes) {
|
||||
if (__memalign_hook != nullptr && __memalign_hook != default_memalign_hook) {
|
||||
return __memalign_hook(alignment, bytes, __builtin_return_address(0));
|
||||
}
|
||||
return g_dispatch->memalign(alignment, bytes);
|
||||
}
|
||||
|
||||
void* hooks_realloc(void* pointer, size_t bytes) {
|
||||
if (__realloc_hook != nullptr && __realloc_hook != default_realloc_hook) {
|
||||
return __realloc_hook(pointer, bytes, __builtin_return_address(0));
|
||||
}
|
||||
return g_dispatch->realloc(pointer, bytes);
|
||||
}
|
||||
|
||||
void* hooks_calloc(size_t nmemb, size_t bytes) {
|
||||
if (__malloc_hook != nullptr && __malloc_hook != default_malloc_hook) {
|
||||
size_t size;
|
||||
if (__builtin_mul_overflow(nmemb, bytes, &size)) {
|
||||
return nullptr;
|
||||
}
|
||||
void* ptr = __malloc_hook(size, __builtin_return_address(0));
|
||||
if (ptr != nullptr) {
|
||||
memset(ptr, 0, size);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
return g_dispatch->calloc(nmemb, bytes);
|
||||
}
|
||||
|
||||
struct mallinfo hooks_mallinfo() {
|
||||
return g_dispatch->mallinfo();
|
||||
}
|
||||
|
||||
int hooks_mallopt(int param, int value) {
|
||||
return g_dispatch->mallopt(param, value);
|
||||
}
|
||||
|
||||
void* hooks_aligned_alloc(size_t alignment, size_t size) {
|
||||
if (__memalign_hook != nullptr && __memalign_hook != default_memalign_hook) {
|
||||
if (!powerof2(alignment)) {
|
||||
errno = EINVAL;
|
||||
return nullptr;
|
||||
}
|
||||
void* ptr = __memalign_hook(alignment, size, __builtin_return_address(0));
|
||||
if (ptr == nullptr) {
|
||||
errno = ENOMEM;
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
return g_dispatch->aligned_alloc(alignment, size);
|
||||
}
|
||||
|
||||
int hooks_posix_memalign(void** memptr, size_t alignment, size_t size) {
|
||||
if (__memalign_hook != nullptr && __memalign_hook != default_memalign_hook) {
|
||||
if (!powerof2(alignment)) {
|
||||
return EINVAL;
|
||||
}
|
||||
*memptr = __memalign_hook(alignment, size, __builtin_return_address(0));
|
||||
if (*memptr == nullptr) {
|
||||
return ENOMEM;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return g_dispatch->posix_memalign(memptr, alignment, size);
|
||||
}
|
||||
|
||||
int hooks_iterate(uintptr_t, size_t, void (*)(uintptr_t, size_t, void*), void*) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void hooks_malloc_disable() {
|
||||
}
|
||||
|
||||
void hooks_malloc_enable() {
|
||||
}
|
||||
|
||||
ssize_t hooks_malloc_backtrace(void*, uintptr_t*, size_t) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
|
||||
void* hooks_pvalloc(size_t bytes) {
|
||||
size_t pagesize = getpagesize();
|
||||
size_t size = __BIONIC_ALIGN(bytes, pagesize);
|
||||
if (size < bytes) {
|
||||
// Overflow
|
||||
errno = ENOMEM;
|
||||
return nullptr;
|
||||
}
|
||||
return hooks_memalign(pagesize, size);
|
||||
}
|
||||
|
||||
void* hooks_valloc(size_t size) {
|
||||
return hooks_memalign(getpagesize(), size);
|
||||
}
|
||||
#endif
|
412
libc/malloc_hooks/tests/malloc_hooks_tests.cpp
Normal file
412
libc/malloc_hooks/tests/malloc_hooks_tests.cpp
Normal file
|
@ -0,0 +1,412 @@
|
|||
/*
|
||||
* Copyright (C) 2018 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.
|
||||
*/
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <malloc.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <private/bionic_malloc_dispatch.h>
|
||||
#include <tests/utils.h>
|
||||
|
||||
__BEGIN_DECLS
|
||||
|
||||
void get_malloc_leak_info(uint8_t**, size_t*, size_t*, size_t*, size_t*);
|
||||
void free_malloc_leak_info(uint8_t*);
|
||||
int malloc_iterate(uintptr_t, size_t, void (*)(uintptr_t, size_t, void*), void*);
|
||||
void malloc_enable();
|
||||
void malloc_disable();
|
||||
ssize_t malloc_backtrace(void*, uintptr_t*, size_t);
|
||||
|
||||
__END_DECLS
|
||||
|
||||
class MallocHooksTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
ASSERT_EQ(0, setenv("LIBC_HOOKS_ENABLE", "1", true));
|
||||
initialized_ = false;
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
if (initialized_) {
|
||||
Reset();
|
||||
}
|
||||
ASSERT_EQ(0, unsetenv("LIBC_HOOKS_ENABLE"));
|
||||
}
|
||||
|
||||
void Init() {
|
||||
initialized_ = true;
|
||||
malloc_hook_called_ = false;
|
||||
free_hook_called_ = false;
|
||||
realloc_hook_called_ = false;
|
||||
memalign_hook_called_ = false;
|
||||
|
||||
void_arg_ = nullptr;
|
||||
|
||||
orig_malloc_hook_ = __malloc_hook;
|
||||
orig_free_hook_ = __free_hook;
|
||||
orig_realloc_hook_ = __realloc_hook;
|
||||
orig_memalign_hook_ = __memalign_hook;
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
__malloc_hook = orig_malloc_hook_;
|
||||
__free_hook = orig_free_hook_;
|
||||
__realloc_hook = orig_realloc_hook_;
|
||||
__memalign_hook = orig_memalign_hook_;
|
||||
}
|
||||
|
||||
void Exec(std::vector<const char*> args);
|
||||
void RunTest(std::string test_name);
|
||||
|
||||
public:
|
||||
bool initialized_;
|
||||
|
||||
int fd_;
|
||||
std::string raw_output_;
|
||||
pid_t pid_;
|
||||
|
||||
static bool malloc_hook_called_;
|
||||
static bool free_hook_called_;
|
||||
static bool realloc_hook_called_;
|
||||
static bool memalign_hook_called_;
|
||||
static const void* void_arg_;
|
||||
|
||||
static void* (*orig_malloc_hook_)(size_t, const void*);
|
||||
static void (*orig_free_hook_)(void*, const void*);
|
||||
static void* (*orig_realloc_hook_)(void*, size_t, const void*);
|
||||
static void* (*orig_memalign_hook_)(size_t, size_t, const void*);
|
||||
|
||||
static void* test_malloc_hook(size_t, const void*);
|
||||
static void* test_realloc_hook(void* ptr, size_t, const void*);
|
||||
static void* test_memalign_hook(size_t, size_t, const void*);
|
||||
static void test_free_hook(void*, const void*);
|
||||
};
|
||||
|
||||
bool MallocHooksTest::malloc_hook_called_;
|
||||
bool MallocHooksTest::free_hook_called_;
|
||||
bool MallocHooksTest::realloc_hook_called_;
|
||||
bool MallocHooksTest::memalign_hook_called_;
|
||||
const void* MallocHooksTest::void_arg_;
|
||||
|
||||
void* (*MallocHooksTest::orig_malloc_hook_)(size_t, const void*);
|
||||
void (*MallocHooksTest::orig_free_hook_)(void*, const void*);
|
||||
void* (*MallocHooksTest::orig_realloc_hook_)(void*, size_t, const void*);
|
||||
void* (*MallocHooksTest::orig_memalign_hook_)(size_t, size_t, const void*);
|
||||
|
||||
static void GetExe(std::string* exe_name) {
|
||||
char path[PATH_MAX];
|
||||
ssize_t path_len = readlink("/proc/self/exe", path, sizeof(path));
|
||||
ASSERT_TRUE(path_len >= 0);
|
||||
*exe_name = std::string(path, path_len);
|
||||
}
|
||||
|
||||
void MallocHooksTest::RunTest(std::string test_name) {
|
||||
std::vector<const char*> args;
|
||||
args.push_back("--gtest_also_run_disabled_tests");
|
||||
std::string filter_arg("--gtest_filter=" + test_name);
|
||||
args.push_back(filter_arg.c_str());
|
||||
|
||||
ExecTestHelper test;
|
||||
test.Run(
|
||||
[&]() {
|
||||
std::string exe_name;
|
||||
GetExe(&exe_name);
|
||||
args.insert(args.begin(), exe_name.c_str());
|
||||
args.push_back(nullptr);
|
||||
execv(args[0], reinterpret_cast<char* const*>(const_cast<char**>(args.data())));
|
||||
exit(1);
|
||||
},
|
||||
0, nullptr);
|
||||
}
|
||||
|
||||
void* MallocHooksTest::test_malloc_hook(size_t size, const void* arg) {
|
||||
malloc_hook_called_ = true;
|
||||
void_arg_ = arg;
|
||||
return orig_malloc_hook_(size, arg);
|
||||
}
|
||||
|
||||
void* MallocHooksTest::test_realloc_hook(void* ptr, size_t size, const void* arg) {
|
||||
realloc_hook_called_ = true;
|
||||
void_arg_ = arg;
|
||||
return orig_realloc_hook_(ptr, size, arg);
|
||||
}
|
||||
|
||||
void* MallocHooksTest::test_memalign_hook(size_t alignment, size_t size, const void* arg) {
|
||||
memalign_hook_called_ = true;
|
||||
void_arg_ = arg;
|
||||
return orig_memalign_hook_(alignment, size, arg);
|
||||
}
|
||||
|
||||
void MallocHooksTest::test_free_hook(void* ptr, const void* arg) {
|
||||
free_hook_called_ = true;
|
||||
void_arg_ = arg;
|
||||
return orig_free_hook_(ptr, arg);
|
||||
}
|
||||
|
||||
TEST_F(MallocHooksTest, other_malloc_functions) {
|
||||
RunTest("*.DISABLED_other_malloc_functions");
|
||||
}
|
||||
|
||||
// Call other intercepted functions to make sure there are no crashes.
|
||||
TEST_F(MallocHooksTest, DISABLED_other_malloc_functions) {
|
||||
struct mallinfo info = mallinfo();
|
||||
EXPECT_NE(0U, info.uordblks);
|
||||
|
||||
EXPECT_EQ(0, mallopt(-1000, -1000));
|
||||
|
||||
void* ptr = malloc(1024);
|
||||
EXPECT_LE(1024U, malloc_usable_size(ptr));
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
TEST_F(MallocHooksTest, extended_functions) {
|
||||
RunTest("*.DISABLED_extended_functions");
|
||||
}
|
||||
|
||||
TEST_F(MallocHooksTest, DISABLED_extended_functions) {
|
||||
uint8_t* info = nullptr;
|
||||
size_t overall_size = 100;
|
||||
size_t info_size = 200;
|
||||
size_t total_memory = 300;
|
||||
size_t backtrace_size = 400;
|
||||
get_malloc_leak_info(&info, &overall_size, &info_size, &total_memory, &backtrace_size);
|
||||
EXPECT_EQ(nullptr, info);
|
||||
EXPECT_EQ(0U, overall_size);
|
||||
EXPECT_EQ(0U, info_size);
|
||||
EXPECT_EQ(0U, total_memory);
|
||||
EXPECT_EQ(0U, backtrace_size);
|
||||
|
||||
free_malloc_leak_info(info);
|
||||
|
||||
malloc_enable();
|
||||
malloc_disable();
|
||||
|
||||
EXPECT_EQ(0, malloc_iterate(0, 0, nullptr, nullptr));
|
||||
|
||||
// Malloc hooks doesn't support any backtracing.
|
||||
EXPECT_EQ(0, malloc_backtrace(nullptr, nullptr, 10));
|
||||
}
|
||||
|
||||
TEST_F(MallocHooksTest, malloc_hook) {
|
||||
RunTest("*.DISABLED_malloc_hook");
|
||||
}
|
||||
|
||||
TEST_F(MallocHooksTest, DISABLED_malloc_hook) {
|
||||
Init();
|
||||
ASSERT_TRUE(__malloc_hook != nullptr);
|
||||
__malloc_hook = test_malloc_hook;
|
||||
|
||||
void* ptr = malloc(1024);
|
||||
ASSERT_TRUE(ptr != nullptr);
|
||||
write(0, ptr, 0);
|
||||
free(ptr);
|
||||
|
||||
EXPECT_TRUE(malloc_hook_called_) << "The malloc hook was not called.";
|
||||
EXPECT_TRUE(void_arg_ != nullptr) << "The malloc hook was called with a nullptr.";
|
||||
}
|
||||
|
||||
TEST_F(MallocHooksTest, free_hook) {
|
||||
RunTest("*.DISABLED_free_hook");
|
||||
}
|
||||
|
||||
TEST_F(MallocHooksTest, DISABLED_free_hook) {
|
||||
Init();
|
||||
ASSERT_TRUE(__free_hook != nullptr);
|
||||
__free_hook = test_free_hook;
|
||||
|
||||
void* ptr = malloc(1024);
|
||||
ASSERT_TRUE(ptr != nullptr);
|
||||
free(ptr);
|
||||
write(0, ptr, 0);
|
||||
|
||||
EXPECT_TRUE(free_hook_called_) << "The free hook was not called.";
|
||||
EXPECT_TRUE(void_arg_ != nullptr) << "The free hook was called with a nullptr.";
|
||||
}
|
||||
|
||||
TEST_F(MallocHooksTest, realloc_hook) {
|
||||
RunTest("*.DISABLED_realloc_hook");
|
||||
}
|
||||
|
||||
TEST_F(MallocHooksTest, DISABLED_realloc_hook) {
|
||||
Init();
|
||||
ASSERT_TRUE(__realloc_hook != nullptr);
|
||||
__realloc_hook = test_realloc_hook;
|
||||
|
||||
void* ptr = malloc(1024);
|
||||
ASSERT_TRUE(ptr != nullptr);
|
||||
ptr = realloc(ptr, 2048);
|
||||
free(ptr);
|
||||
write(0, ptr, 0);
|
||||
|
||||
EXPECT_TRUE(realloc_hook_called_) << "The realloc hook was not called.";
|
||||
EXPECT_TRUE(void_arg_ != nullptr) << "The realloc hook was called with a nullptr.";
|
||||
}
|
||||
|
||||
TEST_F(MallocHooksTest, memalign_hook) {
|
||||
RunTest("*.DISABLED_memalign_hook");
|
||||
}
|
||||
|
||||
TEST_F(MallocHooksTest, DISABLED_memalign_hook) {
|
||||
Init();
|
||||
ASSERT_TRUE(__memalign_hook != nullptr);
|
||||
__memalign_hook = test_memalign_hook;
|
||||
|
||||
void* ptr = memalign(32, 1024);
|
||||
ASSERT_TRUE(ptr != nullptr);
|
||||
free(ptr);
|
||||
write(0, ptr, 0);
|
||||
|
||||
EXPECT_TRUE(memalign_hook_called_) << "The memalign hook was not called.";
|
||||
EXPECT_TRUE(void_arg_ != nullptr) << "The memalign hook was called with a nullptr.";
|
||||
}
|
||||
|
||||
TEST_F(MallocHooksTest, posix_memalign_hook) {
|
||||
RunTest("*.DISABLED_posix_memalign_hook");
|
||||
}
|
||||
|
||||
TEST_F(MallocHooksTest, DISABLED_posix_memalign_hook) {
|
||||
Init();
|
||||
ASSERT_TRUE(__memalign_hook != nullptr);
|
||||
__memalign_hook = test_memalign_hook;
|
||||
|
||||
void* ptr;
|
||||
ASSERT_EQ(0, posix_memalign(&ptr, 32, 1024));
|
||||
ASSERT_TRUE(ptr != nullptr);
|
||||
free(ptr);
|
||||
write(0, ptr, 0);
|
||||
|
||||
EXPECT_TRUE(memalign_hook_called_) << "The memalign hook was not called when running posix_memalign.";
|
||||
EXPECT_TRUE(void_arg_ != nullptr) << "The memalign hook was called with a nullptr.";
|
||||
}
|
||||
|
||||
TEST_F(MallocHooksTest, posix_memalign_hook_error) {
|
||||
RunTest("*.DISABLED_posix_memalign_hook_error");
|
||||
}
|
||||
|
||||
TEST_F(MallocHooksTest, DISABLED_posix_memalign_hook_error) {
|
||||
Init();
|
||||
ASSERT_TRUE(__memalign_hook != nullptr);
|
||||
__memalign_hook = test_memalign_hook;
|
||||
|
||||
void* ptr;
|
||||
ASSERT_EQ(EINVAL, posix_memalign(&ptr, 11, 1024));
|
||||
write(0, ptr, 0);
|
||||
|
||||
EXPECT_FALSE(memalign_hook_called_)
|
||||
<< "The memalign hook was called when running posix_memalign with an error.";
|
||||
EXPECT_FALSE(void_arg_ != nullptr)
|
||||
<< "The memalign hook was called with a nullptr with an error.";
|
||||
}
|
||||
|
||||
TEST_F(MallocHooksTest, aligned_alloc_hook) {
|
||||
RunTest("*.DISABLED_aligned_alloc_hook");
|
||||
}
|
||||
|
||||
TEST_F(MallocHooksTest, DISABLED_aligned_alloc_hook) {
|
||||
Init();
|
||||
ASSERT_TRUE(__memalign_hook != nullptr);
|
||||
__memalign_hook = test_memalign_hook;
|
||||
|
||||
void* ptr = aligned_alloc(32, 1024);
|
||||
ASSERT_TRUE(ptr != nullptr);
|
||||
free(ptr);
|
||||
write(0, ptr, 0);
|
||||
|
||||
EXPECT_TRUE(memalign_hook_called_) << "The memalign hook was not called when running aligned_alloc.";
|
||||
EXPECT_TRUE(void_arg_ != nullptr) << "The memalign hook was called with a nullptr.";
|
||||
}
|
||||
|
||||
TEST_F(MallocHooksTest, aligned_alloc_hook_error) {
|
||||
RunTest("*.DISABLED_aligned_alloc_hook_error");
|
||||
}
|
||||
|
||||
TEST_F(MallocHooksTest, DISABLED_aligned_alloc_hook_error) {
|
||||
Init();
|
||||
ASSERT_TRUE(__memalign_hook != nullptr);
|
||||
__memalign_hook = test_memalign_hook;
|
||||
|
||||
void* ptr = aligned_alloc(11, 1024);
|
||||
ASSERT_TRUE(ptr == nullptr);
|
||||
EXPECT_EQ(EINVAL, errno);
|
||||
write(0, ptr, 0);
|
||||
|
||||
EXPECT_FALSE(memalign_hook_called_)
|
||||
<< "The memalign hook was called when running aligned_alloc with an error.";
|
||||
EXPECT_FALSE(void_arg_ != nullptr)
|
||||
<< "The memalign hook was called with a nullptr with an error.";
|
||||
}
|
||||
|
||||
#if !defined(__LP64__)
|
||||
TEST_F(MallocHooksTest, pvalloc_hook) {
|
||||
RunTest("*.DISABLED_pvalloc_hook");
|
||||
}
|
||||
|
||||
extern "C" void* pvalloc(size_t);
|
||||
|
||||
TEST_F(MallocHooksTest, DISABLED_pvalloc_hook) {
|
||||
Init();
|
||||
ASSERT_TRUE(__memalign_hook != nullptr);
|
||||
__memalign_hook = test_memalign_hook;
|
||||
|
||||
void* ptr = pvalloc(1024);
|
||||
ASSERT_TRUE(ptr != nullptr);
|
||||
write(0, ptr, 0);
|
||||
free(ptr);
|
||||
|
||||
EXPECT_TRUE(memalign_hook_called_) << "The memalign hook was not called for pvalloc.";
|
||||
EXPECT_TRUE(void_arg_ != nullptr) << "The memalign hook was called with a nullptr.";
|
||||
}
|
||||
|
||||
TEST_F(MallocHooksTest, valloc_hook) {
|
||||
RunTest("*.DISABLED_valloc_hook");
|
||||
}
|
||||
|
||||
extern "C" void* valloc(size_t);
|
||||
|
||||
TEST_F(MallocHooksTest, DISABLED_valloc_hook) {
|
||||
Init();
|
||||
ASSERT_TRUE(__memalign_hook != nullptr);
|
||||
__memalign_hook = test_memalign_hook;
|
||||
|
||||
void* ptr = valloc(1024);
|
||||
ASSERT_TRUE(ptr != nullptr);
|
||||
write(0, ptr, 0);
|
||||
free(ptr);
|
||||
|
||||
EXPECT_TRUE(memalign_hook_called_) << "The memalign hook was not called for valloc.";
|
||||
EXPECT_TRUE(void_arg_ != nullptr) << "The memalign hook was called with a nullptr.";
|
||||
}
|
||||
#endif
|
|
@ -140,15 +140,20 @@ static inline void WaitUntilThreadSleep(std::atomic<pid_t>& tid) {
|
|||
}
|
||||
}
|
||||
|
||||
static inline void AssertChildExited(int pid, int expected_exit_status) {
|
||||
static inline void AssertChildExited(int pid, int expected_exit_status,
|
||||
const std::string* error_msg = nullptr) {
|
||||
int status;
|
||||
ASSERT_EQ(pid, TEMP_FAILURE_RETRY(waitpid(pid, &status, 0)));
|
||||
std::string error;
|
||||
if (error_msg == nullptr) {
|
||||
error_msg = &error;
|
||||
}
|
||||
ASSERT_EQ(pid, TEMP_FAILURE_RETRY(waitpid(pid, &status, 0))) << *error_msg;
|
||||
if (expected_exit_status >= 0) {
|
||||
ASSERT_TRUE(WIFEXITED(status));
|
||||
ASSERT_EQ(expected_exit_status, WEXITSTATUS(status));
|
||||
ASSERT_TRUE(WIFEXITED(status)) << *error_msg;
|
||||
ASSERT_EQ(expected_exit_status, WEXITSTATUS(status)) << *error_msg;
|
||||
} else {
|
||||
ASSERT_TRUE(WIFSIGNALED(status));
|
||||
ASSERT_EQ(-expected_exit_status, WTERMSIG(status));
|
||||
ASSERT_TRUE(WIFSIGNALED(status)) << *error_msg;
|
||||
ASSERT_EQ(-expected_exit_status, WTERMSIG(status)) << *error_msg;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -215,7 +220,8 @@ class ExecTestHelper {
|
|||
}
|
||||
close(fds[0]);
|
||||
|
||||
AssertChildExited(pid, expected_exit_status);
|
||||
std::string error_msg("Test output:\n" + output);
|
||||
AssertChildExited(pid, expected_exit_status, &error_msg);
|
||||
if (expected_output != nullptr) {
|
||||
ASSERT_EQ(expected_output, output);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue