--- /dev/null
+/*
+ * tmalloc.c
+ *
+ * Copyright (c) 2019, Urban Wallasch
+ * BSD 3-Clause License, see LICENSE file for more details.
+ *
+ * A wrapper for malloc(), calloc(), realloc() and free(), providing
+ * basic support for simple heap debugging and sanity checks.
+ *
+ * This source file can be either made part of a project and compiled
+ * in, or build as a shared or static library and linked against. As
+ * long as none of the external functions declared in the accompanying
+ * tmalloc.h header file are used, inclusion of the header is not
+ * necessary, as overloading the C library allocator functions happens
+ * automatically by taking advantage of those being declared as weak
+ * references.
+ *
+ * NOTE: Linking with libpthread is only necessary when writing actual
+ * multi-threaded code. For simple single-threaded code not linked
+ * with libpthread the mutexes used here will simply become no-ops.
+ *
+ * CAVEAT: This code is highly non-portable! It will presumably only
+ * work in conjunction with the GNU C library when using either of gcc
+ * or clang as compiler.
+ *
+ */
+
+#ifdef cplusplus
+#warning "You probably do not want to use this code in a C++ project!"
+#endif
+
+#include <ctype.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <pthread.h> /* for hash table mutex */
+
+#include "tmalloc.h"
+
+
+/* Prototypes for the "real" versions of the overloaded functions: */
+extern void *__libc_malloc(size_t size);
+extern void *__libc_realloc(void *ptr, size_t size);
+extern void *__libc_calloc(size_t nmemb, size_t size);
+extern void *__libc_free(void *ptr);
+
+
+#define TM_LOG_PFX "tmalloc: "
+static int tmalloc_log_fd = STDERR_FILENO;
+static int tmalloc_log_level = TM_LOG_TRACE;
+static int tmalloc_abort_on_error = 0;
+
+static uint64_t tm_sentinel = 0x5a5a5a5a5a5a5a5aULL;
+#define TM_SENTSZ (sizeof tm_sentinel)
+
+
+/*
+ * Safe logging functions for allocation-free printing:
+ */
+
+static const char *hdig = "0123456789abcdef";
+
+static inline void log_str(const char *s) {
+ const char *p = s;
+
+ if (!p)
+ return;
+ while (*p)
+ ++p;
+ write(tmalloc_log_fd, s, p - s);
+}
+
+static inline void log_dec(uint64_t n) {
+ char buf[20];
+ char *b = buf + sizeof buf;
+
+ do {
+ *--b = '0' + n % 10;
+ } while (n /= 10);
+ write(tmalloc_log_fd, b, sizeof buf - (b - buf));
+}
+
+static inline void log_hex(uint64_t n) {
+ char buf[18];
+ char *b = buf + sizeof buf;
+
+ do {
+ *--b = hdig[n % 16];
+ } while (n /= 16);
+ *--b = 'x';
+ *--b = '0';
+ write(tmalloc_log_fd, b, sizeof buf - (b - buf));
+}
+
+static inline void log_xdump(const void *s, size_t n) {
+ const unsigned char *p = s;
+ char buf[68];
+ char *b = buf;
+ char *a = buf + 51;
+ size_t i = 0;
+
+ memset(buf, ' ', sizeof buf);
+ while (i < n) {
+ *b++ = hdig[p[i] / 16];
+ *b++ = hdig[p[i] % 16];
+ *a++ = isgraph(p[i]) ? p[i] : '.';
+ ++i;
+ if (i % 16 == 0) {
+ *a++ = '\n';
+ write(tmalloc_log_fd, buf, a - buf);
+ memset(buf, ' ', sizeof buf);
+ b = buf;
+ a = buf + 51;
+ }
+ else {
+ if (i % 8 == 0)
+ *b++ = ' ';
+ *b++ = ' ';
+ }
+ }
+ if (b != buf) {
+ *a++ = '\n';
+ write(tmalloc_log_fd, buf, a - buf);
+ }
+}
+
+
+/*
+ * Called when a serious error is encountered:
+ */
+
+static inline void tm_panic(void) {
+ if (tmalloc_abort_on_error) {
+ log_str("Aborting.\n");
+ abort();
+ }
+}
+
+
+/*
+ * Hash table to store information about allocated blocks of memory.
+ */
+
+#define TM_HASHTAB_SIZE 1024
+
+struct tm_info_struct {
+ void *addr;
+ size_t size;
+ void *raddr;
+ size_t rsize;
+ void *caller;
+ struct tm_info_struct *next;
+};
+
+static struct tm_info_struct *tm_htab[TM_HASHTAB_SIZE] = {NULL};
+
+static pthread_mutex_t tm_ht_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+#define TM_HT_LOCK() pthread_mutex_lock(&tm_ht_mutex)
+#define TM_HT_UNLOCK() pthread_mutex_unlock(&tm_ht_mutex)
+
+/* Jenkins's one_at_a_time hash to compute hash table index: */
+static inline uint32_t tm_hash(uint64_t k) {
+ unsigned char *key = (unsigned char *)&k;
+ size_t len = sizeof k;
+ uint32_t hash = 0;
+
+ while (len) {
+ hash += key[--len];
+ hash += hash << 10;
+ hash ^= hash >> 6;
+ }
+ hash += hash << 3;
+ hash ^= hash >> 11;
+ hash += hash << 15;
+ return hash % TM_HASHTAB_SIZE;
+}
+
+static inline int tm_ht_insert(void *addr, size_t size,
+ void *raddr, size_t rsize, void *caller) {
+ struct tm_info_struct *e = __libc_malloc(sizeof *e);
+ uint32_t h;
+
+ if (!e) {
+ log_str("ERROR: failed to allocate internal block info struct");
+ tm_panic();
+ }
+ h = tm_hash((uint64_t)addr);
+ for (struct tm_info_struct *p = tm_htab[h]; p; p = p->next) {
+ if (p->addr == addr) {
+ log_str("ERROR: block at");
+ log_hex((uint64_t)p->addr); log_str(" (");
+ log_hex((uint64_t)p->raddr); log_str(") ");
+ log_str("already listed as allocated");
+ tm_panic();
+ return -1;
+ }
+ }
+ e->addr = addr;
+ e->size = size;
+ e->raddr = raddr;
+ e->rsize = rsize;
+ e->caller = caller;
+ e->next = tm_htab[h];
+ tm_htab[h] = e;
+ return 0;
+}
+
+static inline int tm_ht_check_entry(struct tm_info_struct *p) {
+ int res = 0;
+
+ if (memcmp(p->raddr, &tm_sentinel, TM_SENTSZ) != 0) {
+ if (tmalloc_log_level >= TM_LOG_WARNING) {
+ log_str(TM_LOG_PFX"WARNING: corruption detected at start of block: ");
+ log_str("addr="); log_hex((uint64_t)p->addr);
+ log_str(" ("); log_hex((uint64_t)p->raddr);
+ log_str("),\tsize="); log_dec(p->size);
+ log_str(" ("); log_dec(p->rsize);
+ log_str(")\n");
+ }
+ res = -1;
+ }
+ if (memcmp(p->raddr + p->rsize - TM_SENTSZ, &tm_sentinel, TM_SENTSZ) != 0) {
+ if (tmalloc_log_level >= TM_LOG_WARNING) {
+ log_str(TM_LOG_PFX"WARNING: corruption detected at end of block ");
+ log_str("addr="); log_hex((uint64_t)p->addr);
+ log_str(" ("); log_hex((uint64_t)p->raddr);
+ log_str("),\tsize="); log_dec(p->size);
+ log_str(" ("); log_dec(p->rsize);
+ log_str(")\n");
+ }
+ res = -1;
+ }
+ return res;
+}
+
+static inline int tm_ht_remove(void *addr, int wipe) {
+ struct tm_info_struct *p, *prev = NULL;
+ uint32_t h;
+
+ h = tm_hash((uint64_t)addr);
+ for (p = tm_htab[h]; p; p = p->next) {
+ if (p->addr == addr) {
+ if (prev == NULL)
+ tm_htab[h] = p->next;
+ else
+ prev->next = p->next;
+ if (wipe)
+ memset(p->raddr, '\x55', p->rsize);
+ return 0;
+ }
+ prev = p;
+ }
+ log_str(TM_LOG_PFX"ERROR: attempt to remove block at ");
+ log_hex((uint64_t)addr);
+ log_str(", not listed as allocated!\n");
+ tm_panic();
+ return -1;
+}
+
+static inline int tm_ht_check(void *addr) {
+ struct tm_info_struct *p;
+ uint32_t h;
+
+ h = tm_hash((uint64_t)addr);
+ for (p = tm_htab[h]; p; p = p->next) {
+ if (p->addr == addr) {
+ return tm_ht_check_entry(p);
+ }
+ }
+ log_str(TM_LOG_PFX"ERROR: attempt to check block at ");
+ log_hex((uint64_t)addr);
+ log_str(", not listed as allocated!\n");
+ tm_panic();
+ return -1;
+}
+
+static inline int tm_ht_checkall(void) {
+ size_t i, n;
+ struct tm_info_struct *p;
+
+ for (i = n = 0; i < TM_HASHTAB_SIZE; ++i) {
+ for (p = tm_htab[i]; p; p = p->next) {
+ if (tm_ht_check_entry(p) != 0)
+ ++n;
+ }
+ }
+ log_str(TM_LOG_PFX"CHECK: ");
+ log_dec(n); log_str(" corrupted block(s) found.\n");
+ return n ? -1 : 0;
+}
+
+static inline void tm_ht_dump(void) {
+ size_t i, n, total;
+ struct tm_info_struct *p;
+
+ for (i = n = total = 0; i < TM_HASHTAB_SIZE; ++i) {
+ for (p = tm_htab[i]; p; p = p->next) {
+ if (tmalloc_log_level >= TM_LOG_INFO) {
+ log_str(TM_LOG_PFX"INFO: block #"); log_dec(n);
+ log_str(":\taddr="); log_hex((uint64_t)p->addr);
+ log_str(" ("); log_hex((uint64_t)p->raddr);
+ log_str("),\tsize="); log_dec(p->size);
+ log_str(" ("); log_dec(p->rsize);
+ log_str("), \tcaller="); log_hex((uint64_t)p->caller);
+ log_str("\n");
+ }
+ if (tmalloc_log_level >= TM_LOG_DEBUG) {
+ log_xdump(p->addr, p->size > 64 ? 64 : p->size);
+ }
+ tm_ht_check_entry(p);
+ ++n;
+ total += p->size;
+ }
+ }
+ log_str(TM_LOG_PFX"STAT: ");
+ log_dec(n); log_str(" block(s) with "); log_dec(total);
+ log_str(" bytes total currently allocated.\n");
+}
+
+
+/*
+ * Instrumented versions of *alloc() and free() functions overloading
+ * the respective weak references in libc.
+ */
+
+#ifdef __GNUC__
+ #define ret_address(l_) __builtin_return_address(l_)
+#else
+ #define ret_address(l_) ((void *)0xdeadbeefcafebabeULL)
+#endif
+
+void *malloc(size_t size) {
+ void *caller = ret_address(0);
+ void *addr;
+ size_t xsize = size + (TM_SENTSZ * 2);
+
+ TM_HT_LOCK();
+ addr = __libc_malloc(xsize);
+ if (addr) {
+ memcpy(addr, &tm_sentinel, TM_SENTSZ);
+ memcpy(addr + xsize - TM_SENTSZ, &tm_sentinel, TM_SENTSZ);
+ tm_ht_insert(addr + TM_SENTSZ, size, addr, xsize, caller);
+ addr += TM_SENTSZ;
+ }
+ else if (tmalloc_log_level >= TM_LOG_WARNING) {
+ log_str(TM_LOG_PFX"WARNING: __libc_malloc() returned NULL pointer!\n");
+ }
+ TM_HT_UNLOCK();
+
+ if (tmalloc_log_level >= TM_LOG_TRACE) {
+ log_str(TM_LOG_PFX"TRACE: malloc("); log_dec(size);
+ log_str(") called from "); log_hex((uint64_t)caller);
+ log_str(" returned "); log_hex((uint64_t)addr);
+ log_str("\n");
+ }
+ return addr;
+}
+
+void *calloc(size_t nmemb, size_t size) {
+ void *caller = ret_address(0);
+ void *addr;
+ size_t xnmemb = nmemb + ((TM_SENTSZ - 1) / size + 1) * 2;
+
+ TM_HT_LOCK();
+ addr = __libc_calloc(xnmemb, size);
+ if (addr) {
+ memcpy(addr, &tm_sentinel, TM_SENTSZ);
+ memcpy(addr + xnmemb * size - TM_SENTSZ, &tm_sentinel, sizeof tm_sentinel);
+ tm_ht_insert(addr + TM_SENTSZ, nmemb * size, addr, xnmemb * size, caller);
+ addr += TM_SENTSZ;
+ }
+ else if (tmalloc_log_level >= TM_LOG_WARNING) {
+ log_str(TM_LOG_PFX"WARNING: __libc_calloc() returned NULL pointer!\n");
+ }
+ TM_HT_UNLOCK();
+
+ if (tmalloc_log_level >= TM_LOG_TRACE) {
+ log_str(TM_LOG_PFX"TRACE: calloc(");
+ log_dec(nmemb); log_str(", "); log_dec(size);
+ log_str(") called from "); log_hex((uint64_t)caller);
+ log_str(" returned "); log_hex((uint64_t)addr);
+ log_str("\n");
+ }
+ return addr;
+}
+
+void *realloc(void *ptr, size_t size) {
+ void *caller = ret_address(0);
+ void *addr;
+ size_t xsize = size + (TM_SENTSZ * 2);
+
+ TM_HT_LOCK();
+ tm_ht_check(ptr);
+ addr = __libc_realloc(ptr - TM_SENTSZ, xsize);
+ if (addr) {
+ tm_ht_remove(ptr, 0);
+ memcpy(addr, &tm_sentinel, TM_SENTSZ);
+ memcpy(addr + xsize - TM_SENTSZ, &tm_sentinel, sizeof tm_sentinel);
+ tm_ht_insert(addr + TM_SENTSZ, size, addr, xsize, caller);
+ addr += TM_SENTSZ;
+ }
+ else if (tmalloc_log_level >= TM_LOG_WARNING) {
+ log_str(TM_LOG_PFX"WARNING: __libc_realloc() returned NULL pointer!\n");
+ }
+ TM_HT_UNLOCK();
+
+ if (tmalloc_log_level >= TM_LOG_TRACE) {
+ log_str(TM_LOG_PFX"TRACE: realloc(");
+ log_hex((uint64_t)ptr); log_str(", "); log_dec(size);
+ log_str(") called from "); log_hex((uint64_t)caller);
+ log_str(" returned "); log_hex((uint64_t)addr);
+ log_str("\n");
+ }
+ return addr;
+}
+
+void free(void *ptr) {
+ void *caller = ret_address(0);
+
+ if (tmalloc_log_level >= TM_LOG_TRACE) {
+ log_str(TM_LOG_PFX"TRACE: free("); log_hex((uint64_t)ptr);
+ log_str(") called from "); log_hex((uint64_t)caller);
+ log_str("\n");
+ }
+
+ if (ptr) {
+ TM_HT_LOCK();
+ tm_ht_check(ptr);
+ if (tm_ht_remove(ptr, 1) != 0) {
+ log_str(TM_LOG_PFX"ERROR: possible double free(");
+ log_hex((uint64_t)ptr);
+ log_str("): *** HEAP IS VERY LIKELY CORRUPT! ***\n");
+ tm_panic();
+ }
+ __libc_free(ptr - TM_SENTSZ);
+ TM_HT_UNLOCK();
+ }
+ else if (tmalloc_log_level >= TM_LOG_INFO) {
+ log_str(TM_LOG_PFX"INFO: free() called with NULL pointer argument!\n");
+ __libc_free(ptr);
+ }
+}
+
+
+/*
+ * Exported supplementary functions:
+ */
+
+void tmalloc_set_log_fd(int fd) {
+ tmalloc_log_fd = fd;
+}
+
+void tmalloc_set_log_level(int v) {
+ tmalloc_log_level = v;
+}
+
+void tmalloc_set_abort_on_error(int a) {
+ tmalloc_abort_on_error = a;
+}
+
+void tmalloc_check(void) {
+ TM_HT_LOCK();
+ tm_ht_checkall();
+ TM_HT_UNLOCK();
+}
+
+void tmalloc_stat(void) {
+ TM_HT_LOCK();
+ tm_ht_dump();
+ TM_HT_UNLOCK();
+}
+