diff options
Diffstat (limited to 'matrace.c')
-rw-r--r-- | matrace.c | 483 |
1 files changed, 483 insertions, 0 deletions
diff --git a/matrace.c b/matrace.c new file mode 100644 index 0000000..ddee2e2 --- /dev/null +++ b/matrace.c @@ -0,0 +1,483 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +/*** + This file is part of mutrace. + + Copyright 2009 Lennart Poettering + + mutrace is free software: you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + mutrace 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with mutrace. If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "config.h" + +#include <pthread.h> +#include <execinfo.h> +#include <stdlib.h> +#include <inttypes.h> +#include <assert.h> +#include <dlfcn.h> +#include <stdbool.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/syscall.h> +#include <unistd.h> +#include <sys/prctl.h> +#include <sched.h> +#include <malloc.h> + +#if !defined (__linux__) || !defined(__GLIBC__) +#error "This stuff only works on Linux!" +#endif + +#ifndef SCHED_RESET_ON_FORK +/* "Your libc lacks the definition of SCHED_RESET_ON_FORK. We'll now + * define it ourselves, however make sure your kernel is new + * enough! */ +#define SCHED_RESET_ON_FORK 0x40000000 +#endif + +#if defined(__i386__) || defined(__x86_64__) +#define DEBUG_TRAP __asm__("int $3") +#else +#define DEBUG_TRAP raise(SIGTRAP) +#endif + +#define LIKELY(x) (__builtin_expect(!!(x),1)) +#define UNLIKELY(x) (__builtin_expect(!!(x),0)) + +static unsigned frames_max = 16; + +static volatile unsigned n_allocations_rt = 0; +static volatile unsigned n_frees_rt = 0; +static volatile unsigned n_allocations_non_rt = 0; +static volatile unsigned n_frees_non_rt = 0; + +static void* (*real_malloc)(size_t s) = NULL; +static void* (*real_calloc)(size_t n, size_t s) = NULL; +static void* (*real_realloc)(void *p, size_t s) = NULL; +static void (*real_free)(void *p) = NULL; +static void (*real_cfree)(void *p) = NULL; +static void* (*real_memalign)(size_t a, size_t s) = NULL; +static int (*real_posix_memalign)(void **p, size_t a, size_t s) = NULL; +static void* (*real_valloc)(size_t s) = NULL; +static void (*real_exit)(int status) __attribute__((noreturn)) = NULL; +static void (*real__exit)(int status) __attribute__((noreturn)) = NULL; +static void (*real__Exit)(int status) __attribute__((noreturn)) = NULL; +static int (*real_backtrace)(void **array, int size) = NULL; +static char **(*real_backtrace_symbols)(void *const *array, int size) = NULL; +static void (*real_backtrace_symbols_fd)(void *const *array, int size, int fd) = NULL; + +static __thread bool recursive = false; + +static volatile bool initialized = false; + +static void setup(void) __attribute ((constructor)); +static void shutdown(void) __attribute ((destructor)); + +static pid_t _gettid(void) { + return (pid_t) syscall(SYS_gettid); +} + +static const char *get_prname(void) { + static char prname[17]; + int r; + + r = prctl(PR_GET_NAME, prname); + assert(r == 0); + + prname[16] = 0; + + return prname; +} + +static int parse_env(const char *n, unsigned *t) { + const char *e; + char *x = NULL; + unsigned long ul; + + if (!(e = getenv(n))) + return 0; + + errno = 0; + ul = strtoul(e, &x, 0); + if (!x || *x || errno != 0) + return -1; + + *t = (unsigned) ul; + + if ((unsigned long) *t != ul) + return -1; + + return 0; +} + +#define LOAD_FUNC(name) \ + do { \ + *(void**) (&real_##name) = dlsym(RTLD_NEXT, #name); \ + assert(real_##name); \ + } while (false) + +static void load_functions(void) { + static volatile bool loaded = false; + + if (LIKELY(loaded)) + return; + + recursive = true; + + LOAD_FUNC(malloc); + LOAD_FUNC(calloc); + LOAD_FUNC(realloc); + LOAD_FUNC(free); + LOAD_FUNC(cfree); + LOAD_FUNC(memalign); + LOAD_FUNC(posix_memalign); + LOAD_FUNC(valloc); + + LOAD_FUNC(exit); + LOAD_FUNC(_exit); + LOAD_FUNC(_Exit); + + LOAD_FUNC(backtrace); + LOAD_FUNC(backtrace_symbols); + LOAD_FUNC(backtrace_symbols_fd); + + loaded = true; + recursive = false; +} + +static void setup(void) { + unsigned t; + + load_functions(); + + if (LIKELY(initialized)) + return; + + if (__malloc_hook) { + fprintf(stderr, + "matrace: Detected non-glibc memory allocator. Your program uses some\n" + "matrace: alternative memory allocator (jemalloc?) which is not compatible with\n" + "matrace: matrace. Please rebuild your program with the standard memory\n" + "matrace: allocator or fix matrace to handle yours correctly.\n"); + + real_exit(1); + } + + t = frames_max; + if (parse_env("MATRACE_FRAMES", &t) < 0 || t <= 0) + fprintf(stderr, "matrace: WARNING: Failed to parse $MATRACE_FRAMES.\n"); + else + frames_max = t; + + initialized = true; + + fprintf(stderr, "matrace: "PACKAGE_VERSION" sucessfully initialized for process %s (pid %lu).\n", + get_prname(), (unsigned long) getpid()); +} + +static void show_summary(void) { + static pthread_mutex_t summary_mutex = PTHREAD_MUTEX_INITIALIZER; + static bool shown_summary = false; + + pthread_mutex_lock(&summary_mutex); + + if (shown_summary) + goto finish; + + fprintf(stderr, + "\n" + "matrace: Total of %u allocations and %u frees in non-realtime threads in process %s (pid %lu).\n" + "matrace: Total of %u allocations and %u frees in realtime threads.\n", + n_allocations_non_rt, + n_frees_non_rt, + get_prname(), (unsigned long) getpid(), + n_allocations_rt, + n_frees_rt); + +finish: + shown_summary = true; + + pthread_mutex_unlock(&summary_mutex); +} + +static void shutdown(void) { + show_summary(); +} + +void exit(int status) { + show_summary(); + real_exit(status); +} + +void _exit(int status) { + show_summary(); + real_exit(status); +} + +void _Exit(int status) { + show_summary(); + real__Exit(status); +} + +static bool is_realtime(void) { + int policy; + + policy = sched_getscheduler(_gettid()); + assert(policy >= 0); + + policy &= ~SCHED_RESET_ON_FORK; + + return + policy == SCHED_FIFO || + policy == SCHED_RR; +} + +static bool verify_frame(const char *s) { + + if (strstr(s, "/" SONAME "(")) + return false; + + if (strstr(s, "/" SONAME " [")) + return false; + + return true; +} + +static char* generate_stacktrace(void) { + void **buffer; + char **strings, *ret, *p; + int n, i; + size_t k; + bool b; + + buffer = malloc(sizeof(void*) * frames_max); + assert(buffer); + + n = real_backtrace(buffer, frames_max); + assert(n >= 0); + + strings = real_backtrace_symbols(buffer, n); + assert(strings); + + free(buffer); + + k = 0; + for (i = 0; i < n; i++) + k += strlen(strings[i]) + 2; + + ret = real_malloc(k + 1); + assert(ret); + + b = false; + for (i = 0, p = ret; i < n; i++) { + if (!b && !verify_frame(strings[i])) + continue; + + if (!b && i > 0) { + /* Skip all but the first stack frame of ours */ + *(p++) = '\t'; + strcpy(p, strings[i-1]); + p += strlen(strings[i-1]); + *(p++) = '\n'; + } + + b = true; + + *(p++) = '\t'; + strcpy(p, strings[i]); + p += strlen(strings[i]); + *(p++) = '\n'; + } + + *p = 0; + + real_free(strings); + + return ret; +} + +static void print_backtrace(void) { + char *bt; + + if (UNLIKELY(recursive)) + return; + + recursive = true; + + bt = generate_stacktrace(); + + fprintf(stderr, + "\n" + "matrace: Memory allocator operation in realtime thread %lu:\n" + "%s", (unsigned long) _gettid(), bt); + real_free(bt); + + recursive = false; +} + +static void check_allocation(void) { + + if (is_realtime()) { + __sync_fetch_and_add(&n_allocations_rt, 1); + print_backtrace(); + } else + __sync_fetch_and_add(&n_allocations_non_rt, 1); +} + +static void check_free(void) { + + if (is_realtime()) { + __sync_fetch_and_add(&n_frees_rt, 1); + print_backtrace(); + } else + __sync_fetch_and_add(&n_frees_non_rt, 1); +} + +void *malloc(size_t s) { + + /* In dlsym() glibc might actually call malloc() itself which + * could make us enter an endless loop, when we try to call it + * from inside the malloc(). However, glibc gracefully handles + * malloc() returning NULL in this case. We use this to escape + * this endless loop. */ + + if (UNLIKELY(!initialized && recursive)) { + errno = ENOMEM; + return NULL; + } + + load_functions(); + check_allocation(); + + return real_malloc(s); +} + +void *calloc(size_t n, size_t s) { + + if (UNLIKELY(!initialized && recursive)) { + errno = ENOMEM; + return NULL; + } + + load_functions(); + check_allocation(); + + return real_calloc(n, s); +} + +void *realloc(void *p, size_t s) { + + if (UNLIKELY(!initialized && recursive)) { + errno = ENOMEM; + return NULL; + } + + load_functions(); + check_allocation(); + + return real_realloc(p, s); +} + +void free(void *p) { + + load_functions(); + check_free(); + + real_free(p); +} + +void cfree(void *p) { + + load_functions(); + check_free(); + + real_cfree(p); +} + +void *memalign(size_t a, size_t s) { + + if (UNLIKELY(!initialized && recursive)) { + errno = ENOMEM; + return NULL; + } + + load_functions(); + check_allocation(); + + return real_memalign(a, s); +} + +int posix_memalign(void **p, size_t a, size_t s) { + + if (UNLIKELY(!initialized && recursive)) + return ENOMEM; + + load_functions(); + check_allocation(); + + return real_posix_memalign(p, a, s); +} + +void *valloc(size_t s) { + + if (UNLIKELY(!initialized && recursive)) { + errno = ENOMEM; + return NULL; + } + + load_functions(); + check_allocation(); + + return real_valloc(s); +} + +int backtrace(void **array, int size) { + int r; + + load_functions(); + + recursive = true; + r = real_backtrace(array, size); + recursive = false; + + return r; +} + +char **backtrace_symbols(void *const *array, int size) { + char **r; + + load_functions(); + + /* backtrace_symbols() internally uses malloc(). To avoid an + * endless loop we need to disable ourselves so that we don't + * try to call backtrace() ourselves when looking at that + * malloc(). */ + + recursive = true; + r = real_backtrace_symbols(array, size); + recursive = false; + + return r; +} + +void backtrace_symbols_fd(void *const *array, int size, int fd) { + load_functions(); + + recursive = true; + real_backtrace_symbols_fd(array, size, fd); + recursive = false; +} |