diff options
Diffstat (limited to 'chattymalloc.c')
| -rw-r--r-- | chattymalloc.c | 381 |
1 files changed, 381 insertions, 0 deletions
diff --git a/chattymalloc.c b/chattymalloc.c new file mode 100644 index 0000000..4696700 --- /dev/null +++ b/chattymalloc.c @@ -0,0 +1,381 @@ +/* +Copyright 2018-2020 Florian Fischer <florian.fl.fischer@fau.de> + +This file is part of chattymalloc. + +chattymalloc is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +chattymalloc 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with chattymalloc. If not, see <http://www.gnu.org/licenses/>. +*/ + +#define _GNU_SOURCE +#include "chattymalloc.h" + +#include <dlfcn.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <pthread.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/syscall.h> +#include <unistd.h> + +#define unlikely(x) __builtin_expect((x), 0) + +#define BOOTSTRAP_MEMORY_SIZE 4096 + +#define GROWTH_THRESHOLD 4096 +#define GROWTH_ENTRIES 100000 + +// flag to stop recursion during bootstrap +static int initializing = 0; + +// memory to bootstrap malloc +static char tmpbuff[BOOTSTRAP_MEMORY_SIZE]; +static unsigned long tmppos = 0; +static unsigned long tmpallocs = 0; + +// global log file descriptor +static int out_fd = -1; +// memory mapping of our output file +static volatile trace_t *out[2] = {NULL, NULL}; +// next free index into the mapped buffer +static volatile uint64_t next_entry = 0; +// current size of our log file / mapping +static volatile uint64_t total_entries = 0; + +// pthread mutex and cond to protect growth of the buffer +static pthread_cond_t growing; +static pthread_mutex_t growth_mutex; + +static __thread pid_t tid = 0; + +// thread specific key to register a destructor +static pthread_key_t tls_key; +static pthread_once_t tls_key_once = PTHREAD_ONCE_INIT; + +// log_thread_termination forward declaration because it uses trace_write +static void log_thread_termination(void *key __attribute__((unused))); + +static void make_tls_key() { + int err = pthread_key_create(&tls_key, log_thread_termination); + if (err) { + abort(); + } +} + +static void init_thread() { + tid = syscall(SYS_gettid); + + // init our thread destructor + int err = pthread_once(&tls_key_once, make_tls_key); + if (err) { + abort(); + } + + // set the key to something != NULL to execute the destructor on thread exit + // NOLINTNEXTLINE(readability-magic-numbers) + err = pthread_setspecific(tls_key, (void *)42); + if (err) { + abort(); + } +} + +/*========================================================= + * intercepted functions + */ + +static void *(*next_malloc)(size_t size); +static void (*next_free)(void *ptr); +static void *(*next_calloc)(size_t nmemb, size_t size); +static void *(*next_realloc)(void *ptr, size_t size); +static void *(*next_memalign)(size_t alignment, size_t size); +static int (*next_posix_memalign)(void **memptr, size_t alignment, size_t size); +static void *(*next_valloc)(size_t size); +static void *(*next_pvalloc)(size_t size); +static void *(*next_aligned_alloc)(size_t alignment, size_t size); +static int (*next_malloc_stats)(); + +static void grow_trace() { + pthread_mutex_lock(&growth_mutex); + + size_t old_buf_idx; + if (unlikely(total_entries == 0)) { + old_buf_idx = 0; + } else { + old_buf_idx = ((total_entries) / GROWTH_ENTRIES) % 2; + } + size_t new_buf_size = (total_entries + GROWTH_ENTRIES) * sizeof(trace_t); + + /* remap old buffer + * hopefully no thread uses the old buffer anymore! + */ + if (out[old_buf_idx] == NULL) { + out[old_buf_idx] = + (trace_t *)mmap(NULL, new_buf_size, PROT_WRITE, MAP_FILE | MAP_SHARED, out_fd, 0); + if (out[old_buf_idx] == MAP_FAILED) { + perror("mapping new buf failed"); + abort(); + } + } else { + size_t old_buf_size = (total_entries - GROWTH_ENTRIES) * sizeof(trace_t); + out[old_buf_idx] = + (trace_t *)mremap((void *)out[old_buf_idx], old_buf_size, new_buf_size, MREMAP_MAYMOVE); + if (out[old_buf_idx] == MAP_FAILED) { + perror("remapping old buf failed"); + abort(); + } + } + + if (ftruncate(out_fd, new_buf_size) != 0) { + perror("extending file failed"); + abort(); + } + + total_entries += GROWTH_ENTRIES; + pthread_cond_broadcast(&growing); + pthread_mutex_unlock(&growth_mutex); +} + +static void write_trace(char func, void *ptr, size_t size, size_t var_arg) { + if (unlikely(tid == 0)) { + init_thread(); + } + + uint64_t idx = __atomic_fetch_add(&next_entry, 1, __ATOMIC_SEQ_CST); + if (idx == total_entries - GROWTH_THRESHOLD) { + grow_trace(); + // wait for growth completion + } else if (idx >= total_entries) { + pthread_mutex_lock(&growth_mutex); + while (idx >= total_entries) { + pthread_cond_wait(&growing, &growth_mutex); + } + pthread_mutex_unlock(&growth_mutex); + } + + volatile trace_t *trace = &out[(idx / GROWTH_ENTRIES) % 2][idx]; + + trace->tid = tid; + trace->func = func; + trace->ptr = ptr; + trace->size = size; + trace->var_arg = var_arg; +} + +static void log_thread_termination(void *key __attribute__((unused))) { + write_trace(THREAD_TERMINATION, NULL, 0, 0); +} + +static void trim_trace() { + uint64_t cur_size = next_entry * sizeof(trace_t); + if (ftruncate(out_fd, cur_size) != 0) { + perror("trimming file failed"); + } + close(out_fd); +} + +static void __attribute__((constructor)) init() { + initializing = 1; + char *fname = getenv("CHATTYMALLOC_FILE"); + if (fname == NULL) { + fname = "chattymalloc.trace"; + } + + out_fd = open(fname, O_RDWR | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (out_fd == -1) { + perror("opening output file"); + abort(); + } + + pthread_cond_init(&growing, NULL); + pthread_mutex_init(&growth_mutex, NULL); + + // init trace buffer + grow_trace(); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + next_malloc = (void *(*)(size_t))dlsym(RTLD_NEXT, "malloc"); + next_free = (void (*)(void *))dlsym(RTLD_NEXT, "free"); + next_calloc = (void *(*)(size_t, size_t))dlsym(RTLD_NEXT, "calloc"); + next_realloc = (void *(*)(void *, size_t))dlsym(RTLD_NEXT, "realloc"); + next_memalign = (void *(*)(size_t, size_t))dlsym(RTLD_NEXT, "memalign"); + next_posix_memalign = (int (*)(void **, size_t, size_t))dlsym(RTLD_NEXT, "posix_memalign"); + next_valloc = (void *(*)(size_t))dlsym(RTLD_NEXT, "valloc"); + next_pvalloc = (void *(*)(size_t))dlsym(RTLD_NEXT, "pvalloc"); + next_aligned_alloc = (void *(*)(size_t, size_t))dlsym(RTLD_NEXT, "aligned_alloc"); + next_malloc_stats = (int (*)(void))dlsym(RTLD_NEXT, "malloc_stats"); +#pragma GCC diagnostic pop + + if (!next_malloc || !next_free || !next_calloc || !next_realloc || !next_memalign) { + fprintf(stderr, "Can't load core functions with `dlsym`: %s\n", dlerror()); + abort(); + } + if (!next_posix_memalign) { + fprintf(stderr, "Can't load posix_memalign with `dlsym`: %s\n", dlerror()); + } + if (!next_valloc) { + fprintf(stderr, "Can't load valloc with `dlsym`: %s\n", dlerror()); + } + if (!next_pvalloc) { + fprintf(stderr, "Can't load pvalloc with `dlsym`: %s\n", dlerror()); + } + if (!next_aligned_alloc) { + fprintf(stderr, "Can't load aligned_alloc with `dlsym`: %s\n", dlerror()); + } + if (!next_malloc_stats) { + fprintf(stderr, "Can't load malloc_stats with `dlsym`: %s\n", dlerror()); + } + + atexit(trim_trace); + initializing = 0; +} + +void *malloc(size_t size) { + if (unlikely(next_malloc == NULL)) { + if (!initializing) { + init(); + + } else { + void *retptr = tmpbuff + tmppos; + tmppos += size; + ++tmpallocs; + + if (tmppos < sizeof(tmpbuff)) { + return retptr; + } + + fprintf(stderr, "%ld in %ld allocs\n", tmppos, tmpallocs); + fprintf(stderr, "jcheck: too much memory requested during initialisation - " + "increase tmpbuff size\n"); + abort(); + } + } + + void *ptr = next_malloc(size); + write_trace(MALLOC, ptr, size, 0); + return ptr; +} + +void free(void *ptr) { + // something wrong if we call free before one of the allocators! + if (unlikely(next_malloc == NULL)) { + init(); + } + + if (!(ptr >= (void *)tmpbuff && ptr <= (void *)(tmpbuff + tmppos))) { + write_trace(FREE, ptr, 0, 0); + next_free(ptr); + } +} + +void *realloc(void *ptr, size_t size) { + if (unlikely(next_realloc == NULL)) { + void *nptr = malloc(size); + if (nptr && ptr) { + // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) + memmove(nptr, ptr, size); + free(ptr); + } + return nptr; + } + + void *nptr = next_realloc(ptr, size); + write_trace(REALLOC, nptr, size, (size_t)ptr); + return nptr; +} + +void *calloc(size_t nmemb, size_t size) { + if (unlikely(next_calloc == NULL)) { + void *ptr = malloc(nmemb * size); + if (ptr) { + // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) + memset(ptr, 0, nmemb * size); + } + return ptr; + } + + void *ptr = next_calloc(nmemb, size); + write_trace(CALLOC, ptr, size, nmemb); + return ptr; +} + +void *memalign(size_t alignment, size_t size) { + if (unlikely(next_memalign == NULL)) { + fprintf(stderr, "called memalign before or during init\n"); + abort(); + } + + void *ptr = next_memalign(alignment, size); + write_trace(MEMALIGN, ptr, size, alignment); + return ptr; +} + +int posix_memalign(void **memptr, size_t alignment, size_t size) { + if (unlikely(next_posix_memalign == NULL)) { + fprintf(stderr, "called posix_memalign before or during init\n"); + abort(); + } + + int ret = next_posix_memalign(memptr, alignment, size); + write_trace(POSIX_MEMALIGN, *memptr, size, alignment); + return ret; +} + +void *valloc(size_t size) { + if (unlikely(next_valloc == NULL)) { + fprintf(stderr, "called valloc before or during init"); + abort(); + } + + void *ptr = next_valloc(size); + write_trace(VALLOC, ptr, size, 0); + return ptr; +} + +void *pvalloc(size_t size) { + if (unlikely(next_pvalloc == NULL)) { + fprintf(stderr, "called pvalloc before or during init\n"); + abort(); + } + + void *ptr = next_pvalloc(size); + write_trace(PVALLOC, ptr, size, 0); + return ptr; +} + +void *aligned_alloc(size_t alignment, size_t size) { + if (next_aligned_alloc == NULL) { + fprintf(stderr, "called aligned_alloc before or during init\n"); + abort(); + } + + void *ptr = next_aligned_alloc(alignment, size); + write_trace(ALIGNED_ALLOC, ptr, size, alignment); + return ptr; +} + +int malloc_stats() { + if (unlikely(next_malloc_stats == NULL)) { + fprintf(stderr, "called malloc_stats before or during init\n"); + abort(); + } + + fprintf(stderr, "chattymalloc by muhq\n"); + return next_malloc_stats(); +} |
