diff options
Diffstat (limited to 'src/libstrongswan/utils/leak_detective.c')
-rw-r--r-- | src/libstrongswan/utils/leak_detective.c | 514 |
1 files changed, 375 insertions, 139 deletions
diff --git a/src/libstrongswan/utils/leak_detective.c b/src/libstrongswan/utils/leak_detective.c index 6bf4d63cd..26e3e43fc 100644 --- a/src/libstrongswan/utils/leak_detective.c +++ b/src/libstrongswan/utils/leak_detective.c @@ -1,5 +1,6 @@ /* - * Copyright (C) 2006-2008 Martin Willi + * Copyright (C) 2013 Tobias Brunner + * Copyright (C) 2006-2013 Martin Willi * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -14,20 +15,29 @@ */ #define _GNU_SOURCE -#include <sched.h> #include <stddef.h> #include <string.h> #include <stdio.h> -#include <malloc.h> #include <signal.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <syslog.h> -#include <pthread.h> #include <netdb.h> #include <locale.h> +#include <dlfcn.h> +#include <time.h> +#include <errno.h> + +#ifdef __APPLE__ +#include <sys/mman.h> +#include <malloc/malloc.h> +/* overload some of our types clashing with mach */ +#define host_t strongswan_host_t +#define processor_t strongswan_processor_t +#define thread_t strongswan_thread_t +#endif /* __APPLE__ */ #include "leak_detective.h" @@ -35,6 +45,8 @@ #include <utils/debug.h> #include <utils/backtrace.h> #include <collections/hashtable.h> +#include <threading/thread_value.h> +#include <threading/spinlock.h> typedef struct private_leak_detective_t private_leak_detective_t; @@ -69,21 +81,6 @@ struct private_leak_detective_t { */ #define MEMORY_ALLOC_PATTERN 0xEE - -static void install_hooks(void); -static void uninstall_hooks(void); -static void *malloc_hook(size_t, const void *); -static void *realloc_hook(void *, size_t, const void *); -static void free_hook(void*, const void *); - -void *(*old_malloc_hook)(size_t, const void *); -void *(*old_realloc_hook)(void *, size_t, const void *); -void (*old_free_hook)(void*, const void *); - -static u_int count_malloc = 0; -static u_int count_free = 0; -static u_int count_realloc = 0; - typedef struct memory_header_t memory_header_t; typedef struct memory_tail_t memory_tail_t; @@ -108,6 +105,11 @@ struct memory_header_t { backtrace_t *backtrace; /** + * Padding to make sizeof(memory_header_t) == 32 + */ + u_int32_t padding[sizeof(void*) == sizeof(u_int32_t) ? 3 : 0]; + + /** * Number of bytes following after the header */ u_int32_t bytes; @@ -136,50 +138,282 @@ struct memory_tail_t { * the others on it... */ static memory_header_t first_header = { - magic: MEMORY_HEADER_MAGIC, - bytes: 0, - backtrace: NULL, - previous: NULL, - next: NULL + .magic = MEMORY_HEADER_MAGIC, }; /** - * are the hooks currently installed? + * Spinlock to access header linked list + */ +static spinlock_t *lock; + +/** + * Is leak detection currently enabled? + */ +static bool enabled = FALSE; + +/** + * Is leak detection disabled for the current thread? */ -static bool installed = FALSE; +static thread_value_t *thread_disabled; /** * Installs the malloc hooks, enables leak detection */ -static void install_hooks() +static void enable_leak_detective() +{ + enabled = TRUE; +} + +/** + * Uninstalls the malloc hooks, disables leak detection + */ +static void disable_leak_detective() +{ + enabled = FALSE; +} + +/** + * Enable/Disable leak detective for the current thread + * + * @return Previous value + */ +static bool enable_thread(bool enable) +{ + bool before; + + before = thread_disabled->get(thread_disabled) == NULL; + thread_disabled->set(thread_disabled, enable ? NULL : (void*)TRUE); + return before; +} + +#ifdef __APPLE__ + +/** + * Copy of original default zone, with functions we call in hooks + */ +static malloc_zone_t original; + +/** + * Call original malloc() + */ +static void* real_malloc(size_t size) +{ + return original.malloc(malloc_default_zone(), size); +} + +/** + * Call original free() + */ +static void real_free(void *ptr) +{ + original.free(malloc_default_zone(), ptr); +} + +/** + * Call original realloc() + */ +static void* real_realloc(void *ptr, size_t size) +{ + return original.realloc(malloc_default_zone(), ptr, size); +} + +/** + * Hook definition: static function with _hook suffix, takes additional zone + */ +#define HOOK(ret, name, ...) \ + static ret name ## _hook(malloc_zone_t *_z, __VA_ARGS__) + +/** + * forward declaration of hooks + */ +HOOK(void*, malloc, size_t bytes); +HOOK(void*, calloc, size_t nmemb, size_t size); +HOOK(void*, valloc, size_t size); +HOOK(void, free, void *ptr); +HOOK(void*, realloc, void *old, size_t bytes); + +/** + * malloc zone size(), must consider the memory header prepended + */ +HOOK(size_t, size, const void *ptr) { - if (!installed) + bool before; + size_t size; + + if (enabled) + { + before = enable_thread(FALSE); + if (before) + { + ptr -= sizeof(memory_header_t); + } + } + size = original.size(malloc_default_zone(), ptr); + if (enabled) { - old_malloc_hook = __malloc_hook; - old_realloc_hook = __realloc_hook; - old_free_hook = __free_hook; - __malloc_hook = malloc_hook; - __realloc_hook = realloc_hook; - __free_hook = free_hook; - installed = TRUE; + enable_thread(before); } + return size; } /** - * Uninstalls the malloc hooks, disables leak detection + * Version of malloc zones we currently support + */ +#define MALLOC_ZONE_VERSION 8 /* Snow Leopard */ + +/** + * Hook-in our malloc functions into the default zone + */ +static bool register_hooks() +{ + malloc_zone_t *zone; + void *page; + + zone = malloc_default_zone(); + if (zone->version != MALLOC_ZONE_VERSION) + { + DBG1(DBG_CFG, "malloc zone version %d unsupported (requiring %d)", + zone->version, MALLOC_ZONE_VERSION); + return FALSE; + } + + original = *zone; + + page = (void*)((uintptr_t)zone / getpagesize() * getpagesize()); + if (mprotect(page, getpagesize(), PROT_WRITE | PROT_READ) != 0) + { + DBG1(DBG_CFG, "malloc zone unprotection failed: %s", strerror(errno)); + return FALSE; + } + + zone->size = size_hook; + zone->malloc = malloc_hook; + zone->calloc = calloc_hook; + zone->valloc = valloc_hook; + zone->free = free_hook; + zone->realloc = realloc_hook; + + /* those other functions can be NULLed out to not use them */ + zone->batch_malloc = NULL; + zone->batch_free = NULL; + zone->memalign = NULL; + zone->free_definite_size = NULL; + + return TRUE; +} + +#else /* !__APPLE__ */ + +/** + * dlsym() might do a malloc(), but we can't do one before we get the malloc() + * function pointer. Use this minimalistic malloc implementation instead. */ -static void uninstall_hooks() +static void* malloc_for_dlsym(size_t size) { - if (installed) + static char buf[1024] = {}; + static size_t used = 0; + char *ptr; + + /* roundup to a multiple of 32 */ + size = (size - 1) / 32 * 32 + 32; + + if (used + size > sizeof(buf)) { - __malloc_hook = old_malloc_hook; - __free_hook = old_free_hook; - __realloc_hook = old_realloc_hook; - installed = FALSE; + return NULL; } + ptr = buf + used; + used += size; + return ptr; } /** + * Lookup a malloc function, while disabling wrappers + */ +static void* get_malloc_fn(char *name) +{ + bool before = FALSE; + void *fn; + + if (enabled) + { + before = enable_thread(FALSE); + } + fn = dlsym(RTLD_NEXT, name); + if (enabled) + { + enable_thread(before); + } + return fn; +} + +/** + * Call original malloc() + */ +static void* real_malloc(size_t size) +{ + static void* (*fn)(size_t size); + static int recursive = 0; + + if (!fn) + { + /* checking recursiveness should actually be thread-specific. But as + * it is very likely that the first allocation is done before we go + * multi-threaded, we keep it simple. */ + if (recursive) + { + return malloc_for_dlsym(size); + } + recursive++; + fn = get_malloc_fn("malloc"); + recursive--; + } + return fn(size); +} + +/** + * Call original free() + */ +static void real_free(void *ptr) +{ + static void (*fn)(void *ptr); + + if (!fn) + { + fn = get_malloc_fn("free"); + } + return fn(ptr); +} + +/** + * Call original realloc() + */ +static void* real_realloc(void *ptr, size_t size) +{ + static void* (*fn)(void *ptr, size_t size); + + if (!fn) + { + fn = get_malloc_fn("realloc"); + } + return fn(ptr, size); +} + +/** + * Hook definition: plain function overloading existing malloc calls + */ +#define HOOK(ret, name, ...) ret name(__VA_ARGS__) + +/** + * Hook initialization when not using hooks + */ +static bool register_hooks() +{ + return TRUE; +} + +#endif /* !__APPLE__ */ + +/** * Leak report white list * * List of functions using static allocation buffers or should be suppressed @@ -194,12 +428,6 @@ char *whitelist[] = { "pthread_setspecific", "__pthread_setspecific", /* glibc functions */ - "mktime", - "ctime", - "__gmtime_r", - "localtime_r", - "tzset", - "time_printf_hook", "inet_ntoa", "strerror", "getprotobyname", @@ -277,6 +505,14 @@ char *whitelist[] = { "gnutls_global_init", }; +/** + * Some functions are hard to whitelist, as they don't use a symbol directly. + * Use some static initialization to suppress them on leak reports + */ +static void init_static_allocations() +{ + tzset(); +} /** * Hashtable hash function @@ -323,11 +559,13 @@ static int print_traces(private_leak_detective_t *this, /** number of allocations */ u_int count; } *entry; + bool before; - uninstall_hooks(); + before = enable_thread(FALSE); entries = hashtable_create((hashtable_hash_t)hash, (hashtable_equals_t)equals, 1024); + lock->lock(lock); for (hdr = first_header.next; hdr != NULL; hdr = hdr->next) { if (whitelisted && @@ -354,6 +592,7 @@ static int print_traces(private_leak_detective_t *this, } leaks++; } + lock->unlock(lock); enumerator = entries->create_enumerator(entries); while (enumerator->enumerate(enumerator, NULL, &entry)) { @@ -368,7 +607,7 @@ static int print_traces(private_leak_detective_t *this, enumerator->destroy(enumerator); entries->destroy(entries); - install_hooks(); + enable_thread(before); return leaks; } @@ -403,85 +642,65 @@ METHOD(leak_detective_t, report, void, METHOD(leak_detective_t, set_state, bool, private_leak_detective_t *this, bool enable) { - static struct sched_param oldparams; - static int oldpolicy; - struct sched_param params; - pthread_t thread_id; - - if (enable == installed) + if (enable == enabled) { - return installed; + return enabled; } - thread_id = pthread_self(); if (enable) { - install_hooks(); - pthread_setschedparam(thread_id, oldpolicy, &oldparams); + enable_leak_detective(); } else { - pthread_getschedparam(thread_id, &oldpolicy, &oldparams); - params.__sched_priority = sched_get_priority_max(SCHED_FIFO); - pthread_setschedparam(thread_id, SCHED_FIFO, ¶ms); - uninstall_hooks(); + disable_leak_detective(); } - installed = enable; - return !installed; + return !enabled; } METHOD(leak_detective_t, usage, void, private_leak_detective_t *this, FILE *out) { - int oldpolicy, thresh; bool detailed; - pthread_t thread_id = pthread_self(); - struct sched_param oldparams, params; + int thresh; thresh = lib->settings->get_int(lib->settings, "libstrongswan.leak_detective.usage_threshold", 10240); detailed = lib->settings->get_bool(lib->settings, "libstrongswan.leak_detective.detailed", TRUE); - pthread_getschedparam(thread_id, &oldpolicy, &oldparams); - params.__sched_priority = sched_get_priority_max(SCHED_FIFO); - pthread_setschedparam(thread_id, SCHED_FIFO, ¶ms); - print_traces(this, out, thresh, detailed, NULL); - - pthread_setschedparam(thread_id, oldpolicy, &oldparams); } /** - * Hook function for malloc() + * Wrapped malloc() function */ -void *malloc_hook(size_t bytes, const void *caller) +HOOK(void*, malloc, size_t bytes) { memory_header_t *hdr; memory_tail_t *tail; - pthread_t thread_id = pthread_self(); - int oldpolicy; - struct sched_param oldparams, params; + bool before; - pthread_getschedparam(thread_id, &oldpolicy, &oldparams); - - params.__sched_priority = sched_get_priority_max(SCHED_FIFO); - pthread_setschedparam(thread_id, SCHED_FIFO, ¶ms); + if (!enabled || thread_disabled->get(thread_disabled)) + { + return real_malloc(bytes); + } - count_malloc++; - uninstall_hooks(); - hdr = malloc(sizeof(memory_header_t) + bytes + sizeof(memory_tail_t)); + hdr = real_malloc(sizeof(memory_header_t) + bytes + sizeof(memory_tail_t)); tail = ((void*)hdr) + bytes + sizeof(memory_header_t); /* set to something which causes crashes */ memset(hdr, MEMORY_ALLOC_PATTERN, sizeof(memory_header_t) + bytes + sizeof(memory_tail_t)); + before = enable_thread(FALSE); + hdr->backtrace = backtrace_create(2); + enable_thread(before); + hdr->magic = MEMORY_HEADER_MAGIC; hdr->bytes = bytes; - hdr->backtrace = backtrace_create(2); tail->magic = MEMORY_TAIL_MAGIC; - install_hooks(); /* insert at the beginning of the list */ + lock->lock(lock); hdr->next = first_header.next; if (hdr->next) { @@ -489,25 +708,49 @@ void *malloc_hook(size_t bytes, const void *caller) } hdr->previous = &first_header; first_header.next = hdr; - - pthread_setschedparam(thread_id, oldpolicy, &oldparams); + lock->unlock(lock); return hdr + 1; } /** - * Hook function for free() + * Wrapped calloc() function + */ +HOOK(void*, calloc, size_t nmemb, size_t size) +{ + void *ptr; + + size *= nmemb; + ptr = malloc(size); + memset(ptr, 0, size); + + return ptr; +} + +/** + * Wrapped valloc(), TODO: currently not supported + */ +HOOK(void*, valloc, size_t size) +{ + DBG1(DBG_LIB, "valloc() used, but leak-detective hook missing"); + return NULL; +} + +/** + * Wrapped free() function */ -void free_hook(void *ptr, const void *caller) +HOOK(void, free, void *ptr) { memory_header_t *hdr, *current; memory_tail_t *tail; backtrace_t *backtrace; - pthread_t thread_id = pthread_self(); - int oldpolicy; - struct sched_param oldparams, params; - bool found = FALSE; + bool found = FALSE, before; + if (!enabled || thread_disabled->get(thread_disabled)) + { + real_free(ptr); + return; + } /* allow freeing of NULL */ if (ptr == NULL) { @@ -516,16 +759,11 @@ void free_hook(void *ptr, const void *caller) hdr = ptr - sizeof(memory_header_t); tail = ptr + hdr->bytes; - pthread_getschedparam(thread_id, &oldpolicy, &oldparams); - - params.__sched_priority = sched_get_priority_max(SCHED_FIFO); - pthread_setschedparam(thread_id, SCHED_FIFO, ¶ms); - - count_free++; - uninstall_hooks(); + before = enable_thread(FALSE); if (hdr->magic != MEMORY_HEADER_MAGIC || tail->magic != MEMORY_TAIL_MAGIC) { + lock->lock(lock); for (current = &first_header; current != NULL; current = current->next) { if (current == hdr) @@ -534,6 +772,7 @@ void free_hook(void *ptr, const void *caller) break; } } + lock->unlock(lock); if (found) { /* memory was allocated by our hooks but is corrupted */ @@ -544,7 +783,7 @@ void free_hook(void *ptr, const void *caller) else { /* memory was not allocated by our hooks */ - fprintf(stderr, "freeing invalid memory (%p)", ptr); + fprintf(stderr, "freeing invalid memory (%p)\n", ptr); } backtrace = backtrace_create(2); backtrace->log(backtrace, stderr, TRUE); @@ -553,52 +792,48 @@ void free_hook(void *ptr, const void *caller) else { /* remove item from list */ + lock->lock(lock); if (hdr->next) { hdr->next->previous = hdr->previous; } hdr->previous->next = hdr->next; + lock->unlock(lock); + hdr->backtrace->destroy(hdr->backtrace); /* clear MAGIC, set mem to something remarkable */ memset(hdr, MEMORY_FREE_PATTERN, sizeof(memory_header_t) + hdr->bytes + sizeof(memory_tail_t)); - free(hdr); + real_free(hdr); } - - install_hooks(); - pthread_setschedparam(thread_id, oldpolicy, &oldparams); + enable_thread(before); } /** - * Hook function for realloc() + * Wrapped realloc() function */ -void *realloc_hook(void *old, size_t bytes, const void *caller) +HOOK(void*, realloc, void *old, size_t bytes) { memory_header_t *hdr; memory_tail_t *tail; backtrace_t *backtrace; - pthread_t thread_id = pthread_self(); - int oldpolicy; - struct sched_param oldparams, params; + bool before; + if (!enabled || thread_disabled->get(thread_disabled)) + { + return real_realloc(old, bytes); + } /* allow reallocation of NULL */ if (old == NULL) { - return malloc_hook(bytes, caller); + return malloc(bytes); } hdr = old - sizeof(memory_header_t); tail = old + hdr->bytes; - pthread_getschedparam(thread_id, &oldpolicy, &oldparams); - - params.__sched_priority = sched_get_priority_max(SCHED_FIFO); - pthread_setschedparam(thread_id, SCHED_FIFO, ¶ms); - - count_realloc++; - uninstall_hooks(); if (hdr->magic != MEMORY_HEADER_MAGIC || tail->magic != MEMORY_TAIL_MAGIC) { @@ -613,33 +848,37 @@ void *realloc_hook(void *old, size_t bytes, const void *caller) /* clear tail magic, allocate, set tail magic */ memset(&tail->magic, MEMORY_ALLOC_PATTERN, sizeof(tail->magic)); } - hdr = realloc(hdr, sizeof(memory_header_t) + bytes + sizeof(memory_tail_t)); + hdr = real_realloc(hdr, + sizeof(memory_header_t) + bytes + sizeof(memory_tail_t)); tail = ((void*)hdr) + bytes + sizeof(memory_header_t); tail->magic = MEMORY_TAIL_MAGIC; /* update statistics */ hdr->bytes = bytes; + + before = enable_thread(FALSE); hdr->backtrace->destroy(hdr->backtrace); hdr->backtrace = backtrace_create(2); + enable_thread(before); /* update header of linked list neighbours */ + lock->lock(lock); if (hdr->next) { hdr->next->previous = hdr; } hdr->previous->next = hdr; - install_hooks(); - pthread_setschedparam(thread_id, oldpolicy, &oldparams); + lock->unlock(lock); + return hdr + 1; } METHOD(leak_detective_t, destroy, void, private_leak_detective_t *this) { - if (installed) - { - uninstall_hooks(); - } + disable_leak_detective(); + lock->destroy(lock); + thread_disabled->destroy(thread_disabled); free(this); } @@ -659,20 +898,17 @@ leak_detective_t *leak_detective_create() }, ); - if (getenv("LEAK_DETECTIVE_DISABLE") == NULL) - { - cpu_set_t mask; + lock = spinlock_create(); + thread_disabled = thread_value_create(NULL); - CPU_ZERO(&mask); - CPU_SET(0, &mask); + init_static_allocations(); - if (sched_setaffinity(0, sizeof(cpu_set_t), &mask) != 0) + if (getenv("LEAK_DETECTIVE_DISABLE") == NULL) + { + if (register_hooks()) { - fprintf(stderr, "setting CPU affinity failed: %m"); + enable_leak_detective(); } - - install_hooks(); } return &this->public; } - |