* tmalloc: initial commit
authorUrban Wallasch <urban.wallasch@freenet.de>
Sat, 9 Nov 2019 10:20:14 +0000 (11:20 +0100)
committerUrban Wallasch <urban.wallasch@freenet.de>
Sat, 9 Nov 2019 10:20:14 +0000 (11:20 +0100)
* updated .gitignore

.gitignore
tmalloc/run_test.sh [new file with mode: 0755]
tmalloc/tmalloc.c [new file with mode: 0644]
tmalloc/tmalloc.h [new file with mode: 0644]
tmalloc/tmalloc_test.c [new file with mode: 0644]

index 261745f92d84a9b9734265ccf352ab0073981268..8ae237f1bdc18451c4c061c23c256cb0864e61cb 100644 (file)
@@ -1,2 +1,3 @@
 *.[oa]
+*.so
 *_test
diff --git a/tmalloc/run_test.sh b/tmalloc/run_test.sh
new file mode 100755 (executable)
index 0000000..ee52ce5
--- /dev/null
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+LIBNAME="tmalloc"
+LIBSONAME="lib$LIBNAME.so"
+LIBSRC=tmalloc.c
+LIBOPTS="-Wall -Wextra -Werror -shared -fPIC"
+LIBCMD="gcc $LIBOPTS -o $LIBSONAME $LIBSRC"
+
+EXEC=tmalloc_test
+CSRC=tmalloc_test.c
+COPTS="-Wall -Wextra -Werror -Wl,-rpath=. -L. -l$LIBNAME"
+CCMD="gcc $COPTS -o $EXEC $CSRC"
+
+echo "$LIBCMD"
+if ! $LIBCMD ; then
+    echo "lib build error"
+    exit
+fi
+
+echo "$CCMD"
+if ! $CCMD ; then
+    echo "test build error"
+    exit
+fi
+
+./$EXEC
diff --git a/tmalloc/tmalloc.c b/tmalloc/tmalloc.c
new file mode 100644 (file)
index 0000000..d09e4e4
--- /dev/null
@@ -0,0 +1,474 @@
+/*
+ * 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();
+}
+
diff --git a/tmalloc/tmalloc.h b/tmalloc/tmalloc.h
new file mode 100644 (file)
index 0000000..72fa2f0
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * tmalloc.h
+ *
+ * Copyright (c) 2019, Urban Wallasch
+ * BSD 3-Clause License, see LICENSE file for more details.
+ *
+ * Functions to supplement the tmalloc.c heap checker.  Inclusion of
+ * this header file is only necessary, if any of the functions declared
+ * here is actually used, the heap checker will work either way.
+ *
+ */
+
+
+#ifndef TMALLOC_H_INCLUDED
+#define TMALLOC_H_INCLUDED
+
+#ifdef cplusplus
+#warning "You probably do not want to use this code in a C++ project!"
+extern "C" {
+#endif
+
+enum {
+    TM_LOG_ERROR,
+    TM_LOG_WARNING,
+    TM_LOG_INFO,
+    TM_LOG_TRACE,
+    TM_LOG_DEBUG,
+};
+
+/*
+ * Set the file descriptor to direct any logging output at.
+ * The default is STDERR_FILENO.
+ */
+extern void tmalloc_set_log_fd(int fd);
+
+/*
+ * Set the trace log verbosity level.
+ * The default setting is TM_LOG_TRACE.
+ */
+extern void tmalloc_set_log_level(int v);
+
+/*
+ * Enable or disable program abortion on error.
+ * Default: 0 (do not abort)
+ */
+extern void tmalloc_set_abort_on_error(int a);
+
+/*
+ * Check all known allocated blocks for intact sentinel values
+ * and report any problems detected.
+ */
+extern void tmalloc_check(void);
+
+/*
+ * Print an overview of all known allocated blocks.
+ * Verbosity depends on currently set log level.
+ */
+extern void tmalloc_stat(void);
+
+
+#ifdef cplusplus
+}
+#endif
+
+#endif /* ndef TMALLOC_H_INCLUDED */
diff --git a/tmalloc/tmalloc_test.c b/tmalloc/tmalloc_test.c
new file mode 100644 (file)
index 0000000..06da94b
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * tmalloc_test.c
+ *
+ * Copyright (c) 2019, Urban Wallasch
+ * BSD 3-Clause License, see LICENSE file for more details.
+ *
+ * Simple, non-exhaustive quick check for tmalloc.[ch].
+ *
+ * Build with:
+ * gcc -Wall -Wextra -Werror -ltmalloc -otmalloc_test tmalloc_test.c
+ */
+
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tmalloc.h"
+
+int main(void) {
+
+    void *p = malloc(1337);
+    //tmalloc_stat();
+    p = realloc(p, 2000);
+    //tmalloc_stat();
+    p = realloc(p, 42);
+    tmalloc_stat();
+
+    fputs( "-----\n", stderr );
+
+    void *q = calloc(100, 15);
+    //tmalloc_stat();
+    q = realloc(q, 3000);
+    //tmalloc_stat();
+    q = realloc(q, 30);
+    tmalloc_stat();
+
+    fputs( "-----\n", stderr );
+
+    char *s = strdup("hello, world!");
+    //tmalloc_stat();
+    s = realloc(s, 111);
+    //tmalloc_stat();
+    s = realloc(s, 44);
+    tmalloc_set_log_level(TM_LOG_DEBUG);
+    tmalloc_stat();
+
+    fputs( "-----\n", stderr );
+    tmalloc_check();
+    free(p);
+    //tmalloc_stat();
+    free(q);
+    //tmalloc_stat();
+    fputs( "-----\n", stderr );
+    fputs( "Deliberate out of bounds access:\n", stderr );
+    *(s-1) = '#';
+    *(s+44) = '+';
+    tmalloc_check();
+    free(s);
+    tmalloc_stat();
+
+    fputs( "-----\n", stderr );
+    tmalloc_set_abort_on_error(1);
+    fputs( "Deliberate double free:\n", stderr );
+    free(s);
+    fputs( "Just plain broken:\n", stderr );
+    free((void*)-1);
+
+    return 0;
+}