From c054479e54e8a9c41bb15760fbf9b1a5b149b1e3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 20 Sep 2009 03:33:28 +0200 Subject: matrace: add matrace RT memory allocation tracker --- .gitignore | 1 + Makefile.am | 34 ++++- matrace.c | 483 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ matrace.in | 72 +++++++++ 4 files changed, 586 insertions(+), 4 deletions(-) create mode 100644 matrace.c create mode 100755 matrace.in diff --git a/.gitignore b/.gitignore index a05417b..b292bdc 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ libtool ltmain.sh install-sh mutrace +matrace diff --git a/Makefile.am b/Makefile.am index f473d11..e8f4c94 100644 --- a/Makefile.am +++ b/Makefile.am @@ -19,19 +19,23 @@ EXTRA_DIST = \ bootstrap.sh \ LGPL \ README \ - mutrace.in + mutrace.in \ + matrace.in #include_HEADERS = \ # mutrace.h lib_LTLIBRARIES = \ - libmutrace.la + libmutrace.la \ + libmatrace.la bin_SCRIPTS = \ - mutrace + mutrace \ + matrace CLEANFILES = \ - mutrace + mutrace \ + matrace libmutrace_la_SOURCES = \ mutrace.c @@ -49,12 +53,34 @@ libmutrace_la_CFLAGS = \ $(PTHREAD_CFLAGS) \ -DSONAME=\"libmutrace.so\" +libmatrace_la_SOURCES = \ + matrace.c +libmatrace_la_LDFLAGS = \ + -avoid-version \ + -module \ + -export-dynamic \ + -shared \ + -prefer-pic +libmatrace_la_LIBADD = \ + $(PTHREAD_LIBS) \ + -lrt \ + -ldl +libmatrace_la_CFLAGS = \ + $(PTHREAD_CFLAGS) \ + -DSONAME=\"libmatrace.so\" + mutrace: mutrace.in Makefile sed -e 's,@PACKAGE_STRING\@,$(PACKAGE_STRING),g' \ -e 's,@PACKAGE_NAME\@,$(PACKAGE_NAME),g' < $< > $@ chmod +x mutrace +matrace: matrace.in Makefile + sed -e 's,@PACKAGE_STRING\@,$(PACKAGE_STRING),g' \ + -e 's,@PACKAGE_NAME\@,$(PACKAGE_NAME),g' < $< > $@ + chmod +x matrace + install-exec-hook: rm -f $(DESTDIR)$(libdir)/libmutrace.la + rm -f $(DESTDIR)$(libdir)/libmatrace.la ACLOCAL_AMFLAGS = -I m4 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 . +***/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/matrace.in b/matrace.in new file mode 100755 index 0000000..7d4d8c6 --- /dev/null +++ b/matrace.in @@ -0,0 +1,72 @@ +#!/bin/bash + +# 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 . + +if ! TEMP=`getopt -o +h --long frames:,help -n matrace -- "$@"` ; then + exit 1 +fi + +eval set -- "$TEMP" + +while : ; do + case $1 in + --frames) + export MATRACE_FRAMES="$2" + shift 2 + ;; + + -h|--help) + cat <&2 + exit 1 + ;; + esac +done + +shift $(($OPTIND-1)) + +if [ x"$1" = x ] ; then + echo "Please specify an application to profile!" >&2 + exit 1 +fi + +if [ x"$LD_PRELOAD" = x ] ; then + export LD_PRELOAD="libmatrace.so" +else + export LD_PRELOAD="$LD_PRELOAD libmatrace.so" +fi + +exec "$@" -- cgit