diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Makefile.am | 34 | ||||
| -rw-r--r-- | matrace.c | 483 | ||||
| -rwxr-xr-x | matrace.in | 72 | 
4 files changed, 586 insertions, 4 deletions
| @@ -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 <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; +} 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 <http://www.gnu.org/licenses/>. + +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 <<EOF +@PACKAGE_STRING@ + +Usage: matrace [OPTIONS...] APPLICATION [ARGUMENTS...] + +COMMANDS: +  -h, --help                      Show this help + +OPTIONS: +      --frames=INTEGER            Set number of frames to show in stack traces +EOF +                        exit 0 +                        ;; +                --) +                        shift +                        break +                        ;; + +                *) +                        echo "Parsing failed!" >&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 "$@" | 
