summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Makefile.am34
-rw-r--r--matrace.c483
-rwxr-xr-xmatrace.in72
4 files changed, 586 insertions, 4 deletions
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 <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 "$@"