/*-*- 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) {
/* Generated by glibc's native backtrace_symbols() on Fedora */
if (strstr(s, "/" SONAME "("))
return false;
/* Generated by glibc's native backtrace_symbols() on Debian */
if (strstr(s, "/" SONAME " ["))
return false;
/* Generated by backtrace-symbols.c */
if (strstr(s, __FILE__":"))
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;
}