From 17211b6b9ae3330c97691dd63ce0d54705de85b3 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 2 Apr 2013 13:37:06 +0200 Subject: [PATCH 1/8] leak-detective: override malloc functions instead of using deprecated hooks malloc hooks have become deprecated, and their use has always been problematic, especially in multi-threaded applications. Replace the functionality by overriding all malloc functions and query the system allocator functions using dlsym() with RTLD_NEXT. --- src/libstrongswan/utils/leak_detective.c | 336 ++++++++++++++--------- 1 file changed, 207 insertions(+), 129 deletions(-) diff --git a/src/libstrongswan/utils/leak_detective.c b/src/libstrongswan/utils/leak_detective.c index 6bf4d63cdc..69e539d641 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,18 @@ */ #define _GNU_SOURCE -#include #include #include #include -#include #include #include #include #include #include #include -#include #include #include +#include #include "leak_detective.h" @@ -35,6 +34,8 @@ #include #include #include +#include +#include typedef struct private_leak_detective_t private_leak_detective_t; @@ -69,17 +70,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; @@ -136,47 +126,146 @@ 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 bool installed = FALSE; +static spinlock_t *lock; + +/** + * Is leak detection currently enabled? + */ +static bool enabled = FALSE; + +/** + * Is leak detection disabled for the current thread? + */ +static thread_value_t *thread_disabled; /** * Installs the malloc hooks, enables leak detection */ -static void install_hooks() +static void enable_leak_detective() { - if (!installed) - { - 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; - } + enabled = TRUE; } /** * Uninstalls the malloc hooks, disables leak detection */ -static void uninstall_hooks() +static void disable_leak_detective() { - if (installed) + 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; +} + +/** + * 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* malloc_for_dlsym(size_t size) +{ + 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); } /** @@ -323,11 +412,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 +445,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 +460,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 +495,66 @@ 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) +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 +562,40 @@ 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 */ -void free_hook(void *ptr, const void *caller) +void* calloc(size_t nmemb, size_t size) +{ + void *ptr; + + size *= nmemb; + ptr = malloc(size); + memset(ptr, 0, size); + + return ptr; +} + +/** + * Wrapped free() function + */ +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 +604,12 @@ 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 +618,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 +629,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 +638,49 @@ 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) +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 +695,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 +745,12 @@ leak_detective_t *leak_detective_create() }, ); + lock = spinlock_create(); + thread_disabled = thread_value_create(NULL); + if (getenv("LEAK_DETECTIVE_DISABLE") == NULL) { - cpu_set_t mask; - - CPU_ZERO(&mask); - CPU_SET(0, &mask); - - if (sched_setaffinity(0, sizeof(cpu_set_t), &mask) != 0) - { - fprintf(stderr, "setting CPU affinity failed: %m"); - } - - install_hooks(); + enable_leak_detective(); } return &this->public; } - From 7e3f6299d5407445393e336f0c53746fafd344d6 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 2 Apr 2013 15:47:55 +0200 Subject: [PATCH 2/8] leak-detective: call tzset() explicitly before enabling leak detective tzset() is hard to whitelist on some systems, as there is no symbol involved. Call tzset() explicitly before initialization to avoid false positives. --- src/libstrongswan/utils/leak_detective.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/libstrongswan/utils/leak_detective.c b/src/libstrongswan/utils/leak_detective.c index 69e539d641..2883b88d9d 100644 --- a/src/libstrongswan/utils/leak_detective.c +++ b/src/libstrongswan/utils/leak_detective.c @@ -27,6 +27,7 @@ #include #include #include +#include #include "leak_detective.h" @@ -283,12 +284,6 @@ char *whitelist[] = { "pthread_setspecific", "__pthread_setspecific", /* glibc functions */ - "mktime", - "ctime", - "__gmtime_r", - "localtime_r", - "tzset", - "time_printf_hook", "inet_ntoa", "strerror", "getprotobyname", @@ -366,6 +361,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 @@ -748,6 +751,8 @@ leak_detective_t *leak_detective_create() lock = spinlock_create(); thread_disabled = thread_value_create(NULL); + init_static_allocations(); + if (getenv("LEAK_DETECTIVE_DISABLE") == NULL) { enable_leak_detective(); From 3117824f55544719fe358aa21f37c22ea82bbb0a Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 2 Apr 2013 17:41:04 +0200 Subject: [PATCH 3/8] leak-detective: align allocations on both 32 and 64-bit systems to 32 bytes --- src/libstrongswan/utils/leak_detective.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libstrongswan/utils/leak_detective.c b/src/libstrongswan/utils/leak_detective.c index 2883b88d9d..169d2c87f7 100644 --- a/src/libstrongswan/utils/leak_detective.c +++ b/src/libstrongswan/utils/leak_detective.c @@ -98,6 +98,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 */ From 50fbd32472fe4676e17915e9e56710b02c5c32c3 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 2 Apr 2013 18:27:12 +0200 Subject: [PATCH 4/8] leak-detective: remove unused malloc call counters --- src/libstrongswan/utils/leak_detective.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/libstrongswan/utils/leak_detective.c b/src/libstrongswan/utils/leak_detective.c index 169d2c87f7..8ba7be8e95 100644 --- a/src/libstrongswan/utils/leak_detective.c +++ b/src/libstrongswan/utils/leak_detective.c @@ -71,10 +71,6 @@ struct private_leak_detective_t { */ #define MEMORY_ALLOC_PATTERN 0xEE -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; @@ -546,7 +542,6 @@ void* malloc(size_t bytes) return real_malloc(bytes); } - count_malloc++; 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 */ @@ -612,7 +607,6 @@ void free(void *ptr) hdr = ptr - sizeof(memory_header_t); tail = ptr + hdr->bytes; - count_free++; before = enable_thread(FALSE); if (hdr->magic != MEMORY_HEADER_MAGIC || tail->magic != MEMORY_TAIL_MAGIC) @@ -688,7 +682,6 @@ void* realloc(void *old, size_t bytes) hdr = old - sizeof(memory_header_t); tail = old + hdr->bytes; - count_realloc++; if (hdr->magic != MEMORY_HEADER_MAGIC || tail->magic != MEMORY_TAIL_MAGIC) { From d8f6f0c01c628e83f6bf5493bcaf03e3e7b86ed5 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 18 Apr 2013 13:07:27 +0200 Subject: [PATCH 5/8] leak-detective: add support for OS X by hooking default malloc zone --- src/libstrongswan/utils/leak_detective.c | 165 ++++++++++++++++++++++- 1 file changed, 160 insertions(+), 5 deletions(-) diff --git a/src/libstrongswan/utils/leak_detective.c b/src/libstrongswan/utils/leak_detective.c index 8ba7be8e95..26e3e43fc9 100644 --- a/src/libstrongswan/utils/leak_detective.c +++ b/src/libstrongswan/utils/leak_detective.c @@ -28,6 +28,16 @@ #include #include #include +#include + +#ifdef __APPLE__ +#include +#include +/* 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" @@ -176,6 +186,124 @@ static bool enable_thread(bool enable) 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) +{ + 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) + { + enable_thread(before); + } + return size; +} + +/** + * 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. @@ -270,6 +398,21 @@ static void* real_realloc(void *ptr, size_t size) 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 * @@ -531,7 +674,7 @@ METHOD(leak_detective_t, usage, void, /** * Wrapped malloc() function */ -void* malloc(size_t bytes) +HOOK(void*, malloc, size_t bytes) { memory_header_t *hdr; memory_tail_t *tail; @@ -573,7 +716,7 @@ void* malloc(size_t bytes) /** * Wrapped calloc() function */ -void* calloc(size_t nmemb, size_t size) +HOOK(void*, calloc, size_t nmemb, size_t size) { void *ptr; @@ -584,10 +727,19 @@ void* calloc(size_t nmemb, size_t 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(void *ptr) +HOOK(void, free, void *ptr) { memory_header_t *hdr, *current; memory_tail_t *tail; @@ -662,7 +814,7 @@ void free(void *ptr) /** * Wrapped realloc() function */ -void* realloc(void *old, size_t bytes) +HOOK(void*, realloc, void *old, size_t bytes) { memory_header_t *hdr; memory_tail_t *tail; @@ -753,7 +905,10 @@ leak_detective_t *leak_detective_create() if (getenv("LEAK_DETECTIVE_DISABLE") == NULL) { - enable_leak_detective(); + if (register_hooks()) + { + enable_leak_detective(); + } } return &this->public; } From 83714577a9c99c9971ce2d3968ab773b37f4ea5b Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Fri, 5 Apr 2013 16:26:27 +0200 Subject: [PATCH 6/8] backtrace: add an alternative stack unwinding implementation using libunwind --- configure.in | 9 +++++++ src/libstrongswan/Makefile.am | 2 +- src/libstrongswan/utils/backtrace.c | 39 +++++++++++++++++++++++++---- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/configure.in b/configure.in index f0a2ec9614..311b15c6b5 100644 --- a/configure.in +++ b/configure.in @@ -238,6 +238,7 @@ ARG_ENABL_SET([radattr], [enable plugin to inject and process custom RADI ARG_ENABL_SET([vstr], [enforce using the Vstr string library to replace glibc-like printf hooks.]) ARG_ENABL_SET([monolithic], [build monolithic version of libstrongswan that includes all enabled plugins. Similarly, the plugins of charon are assembled in libcharon.]) ARG_ENABL_SET([bfd-backtraces], [use binutils libbfd to resolve backtraces for memory leaks and segfaults.]) +ARG_ENABL_SET([unwind-backtraces],[use libunwind to create backtraces for memory leaks and segfaults.]) ARG_ENABL_SET([unit-tests], [enable unit tests using the check test framework.]) ARG_ENABL_SET([tkm], [enable Trusted Key Manager support.]) @@ -886,6 +887,14 @@ if test x$bfd_backtraces = xtrue; then AC_SUBST(BFDLIB) fi +if test x$unwind_backtraces = xtrue; then + AC_CHECK_LIB([unwind],[main],[LIBS="$LIBS"],[AC_MSG_ERROR([libunwind not found!])],[]) + AC_CHECK_HEADER([libunwind.h],[AC_DEFINE([HAVE_LIBUNWIND_H],,[have libunwind.h])], + [AC_MSG_ERROR([libunwind.h header not found!])]) + UNWINDLIB="-lunwind" + AC_SUBST(UNWINDLIB) +fi + AM_CONDITIONAL(USE_DEV_HEADERS, [test "x$dev_headers" != xno]) if test x$dev_headers = xyes; then dev_headers="$includedir/strongswan" diff --git a/src/libstrongswan/Makefile.am b/src/libstrongswan/Makefile.am index 81e271a8a0..ce6df2855e 100644 --- a/src/libstrongswan/Makefile.am +++ b/src/libstrongswan/Makefile.am @@ -79,7 +79,7 @@ endif library.lo : $(top_builddir)/config.status -libstrongswan_la_LIBADD = $(PTHREADLIB) $(DLLIB) $(BTLIB) $(SOCKLIB) $(RTLIB) $(BFDLIB) +libstrongswan_la_LIBADD = $(PTHREADLIB) $(DLLIB) $(BTLIB) $(SOCKLIB) $(RTLIB) $(BFDLIB) $(UNWINDLIB) INCLUDES = -I$(top_srcdir)/src/libstrongswan AM_CFLAGS = \ diff --git a/src/libstrongswan/utils/backtrace.c b/src/libstrongswan/utils/backtrace.c index 77137f9f1a..dd2be7a0a9 100644 --- a/src/libstrongswan/utils/backtrace.c +++ b/src/libstrongswan/utils/backtrace.c @@ -373,7 +373,7 @@ void backtrace_deinit() {} METHOD(backtrace_t, log_, void, private_backtrace_t *this, FILE *file, bool detailed) { -#ifdef HAVE_BACKTRACE +#if defined(HAVE_BACKTRACE) || defined(HAVE_LIBUNWIND_H) size_t i; char **strings; @@ -420,9 +420,9 @@ METHOD(backtrace_t, log_, void, } } free (strings); -#else /* !HAVE_BACKTRACE */ - println(file, "C library does not support backtrace()."); -#endif /* HAVE_BACKTRACE */ +#else /* !HAVE_BACKTRACE && !HAVE_LIBUNWIND_H */ + println(file, "no support for backtrace()/libunwind"); +#endif /* HAVE_BACKTRACE/HAVE_LIBUNWIND_H */ } METHOD(backtrace_t, contains_function, bool, @@ -518,6 +518,33 @@ METHOD(backtrace_t, destroy, void, free(this); } +#ifdef HAVE_LIBUNWIND_H +#define UNW_LOCAL_ONLY +#include + +/** + * libunwind variant for glibc backtrace() + */ +static inline int backtrace_unwind(void **frames, int count) +{ + unw_context_t context; + unw_cursor_t cursor; + unw_word_t ip; + int depth = 0; + + unw_getcontext(&context); + unw_init_local(&cursor, &context); + do + { + unw_get_reg(&cursor, UNW_REG_IP, &ip); + frames[depth++] = (void*)ip; + } + while (depth < count && unw_step(&cursor) > 0); + + return depth; +} +#endif /* HAVE_UNWIND */ + /** * See header */ @@ -527,7 +554,9 @@ backtrace_t *backtrace_create(int skip) void *frames[50]; int frame_count = 0; -#ifdef HAVE_BACKTRACE +#ifdef HAVE_LIBUNWIND_H + frame_count = backtrace_unwind(frames, countof(frames)); +#elif defined(HAVE_BACKTRACE) frame_count = backtrace(frames, countof(frames)); #endif /* HAVE_BACKTRACE */ frame_count = max(frame_count - skip, 0); From 29324299feeb9cd1d662a9db9b9c2061ff4c42ba Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 17 Apr 2013 17:45:25 +0200 Subject: [PATCH 7/8] backtrace: use atos instead of addr2line on OS X to resolve source lines --- src/libstrongswan/utils/backtrace.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/libstrongswan/utils/backtrace.c b/src/libstrongswan/utils/backtrace.c index dd2be7a0a9..820dba44be 100644 --- a/src/libstrongswan/utils/backtrace.c +++ b/src/libstrongswan/utils/backtrace.c @@ -299,7 +299,7 @@ static bfd_entry_t *get_bfd_entry(char *filename) /** * Print the source file with line number to file, libbfd variant */ -static void print_sourceline(FILE *file, char *filename, void *ptr) +static void print_sourceline(FILE *file, char *filename, void *ptr, void *base) { bfd_entry_t *entry; bfd_find_data_t data = { @@ -334,13 +334,20 @@ void backtrace_deinit() {} /** * Print the source file with line number to file, slow addr2line variant */ -static void print_sourceline(FILE *file, char *filename, void *ptr) +static void print_sourceline(FILE *file, char *filename, void *ptr, void* base) { char buf[1024]; FILE *output; int c, i = 0; +#ifdef __APPLE__ + snprintf(buf, sizeof(buf), "atos -o %s -l %p %p 2>&1 | tail -n1", + filename, base, ptr); +#else /* !__APPLE__ */ snprintf(buf, sizeof(buf), "addr2line -e %s %p", filename, ptr); +#endif /* __APPLE__ */ + + output = popen(buf, "r"); if (output) { @@ -410,7 +417,8 @@ METHOD(backtrace_t, log_, void, } if (detailed && info.dli_fname[0]) { - print_sourceline(file, (char*)info.dli_fname, ptr); + print_sourceline(file, (char*)info.dli_fname, + ptr, info.dli_fbase); } } else From 40f2a5306aff2c43065778b378b127e61ecb76cd Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Fri, 5 Apr 2013 16:24:38 +0200 Subject: [PATCH 8/8] scripts: add a simple test utility to do some malloc() benchmarking/profiling --- scripts/.gitignore | 1 + scripts/Makefile.am | 4 +- scripts/malloc_speed.c | 85 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 scripts/malloc_speed.c diff --git a/scripts/.gitignore b/scripts/.gitignore index b97347fbd0..14579cbb47 100644 --- a/scripts/.gitignore +++ b/scripts/.gitignore @@ -9,6 +9,7 @@ dh_speed pubkey_speed crypt_burn hash_burn +malloc_speed tls_test fetch dnssec diff --git a/scripts/Makefile.am b/scripts/Makefile.am index f7ecd9ef60..b9b3a70a84 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -4,7 +4,7 @@ AM_CFLAGS = \ noinst_PROGRAMS = bin2array bin2sql id2sql key2keyid keyid2sql oid2der \ thread_analysis dh_speed pubkey_speed crypt_burn hash_burn fetch \ - dnssec + dnssec malloc_speed if USE_TLS noinst_PROGRAMS += tls_test @@ -24,6 +24,7 @@ dh_speed_SOURCES = dh_speed.c pubkey_speed_SOURCES = pubkey_speed.c crypt_burn_SOURCES = crypt_burn.c hash_burn_SOURCES = hash_burn.c +malloc_speed_SOURCES = malloc_speed.c fetch_SOURCES = fetch.c dnssec_SOURCES = dnssec.c id2sql_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la @@ -34,6 +35,7 @@ dh_speed_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la -lrt pubkey_speed_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la -lrt crypt_burn_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la hash_burn_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la +malloc_speed_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la fetch_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la dnssec_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la diff --git a/scripts/malloc_speed.c b/scripts/malloc_speed.c new file mode 100644 index 0000000000..85d51a2811 --- /dev/null +++ b/scripts/malloc_speed.c @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2013 Martin Willi + * Copyright (C) 2013 revosec aG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include +#include +#include +#include + +#ifdef HAVE_MALLINFO +#include +#endif /* HAVE_MALLINFO */ + +static void start_timing(struct timespec *start) +{ + clock_gettime(CLOCK_THREAD_CPUTIME_ID, start); +} + +static double end_timing(struct timespec *start) +{ + struct timespec end; + + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end); + return (end.tv_nsec - start->tv_nsec) / 1000000000.0 + + (end.tv_sec - start->tv_sec) * 1.0; +} + +static void print_mallinfo() +{ +#ifdef HAVE_MALLINFO + struct mallinfo mi = mallinfo(); + + printf("malloc: sbrk %d, mmap %d, used %d, free %d\n", + mi.arena, mi.hblkhd, mi.uordblks, mi.fordblks); +#endif /* HAVE_MALLINFO */ +} + +#define ALLOCS 1024 +#define ROUNDS 2048 + +int main(int argc, char *argv[]) +{ + struct timespec timing; + int i, round; + void *m[ALLOCS]; + /* a random set of allocations we test */ + int sizes[16] = { 1, 13, 100, 1000, 16, 10000, 50, 17, + 123, 32, 8, 64, 8096, 1024, 123, 9 }; + + library_init(NULL); + atexit(library_deinit); + + print_mallinfo(); + + start_timing(&timing); + + for (round = 0; round < ROUNDS; round++) + { + for (i = 0; i < ALLOCS; i++) + { + m[i] = malloc(sizes[(round + i) % countof(sizes)]); + } + for (i = 0; i < ALLOCS; i++) + { + free(m[i]); + } + } + printf("time for %d malloc/frees, repeating %d rounds: %.4fs\n", + ALLOCS, ROUNDS, end_timing(&timing)); + + print_mallinfo(); + + return 0; +}