From: Urban Wallasch Date: Sat, 9 Nov 2019 10:20:14 +0000 (+0100) Subject: * tmalloc: initial commit X-Git-Url: https://git.packet-gain.de/?a=commitdiff_plain;h=586a2a871e2dbaafaa8c3f52eb8e94cc8ff9e7aa;p=oddbits.git * tmalloc: initial commit * updated .gitignore --- diff --git a/.gitignore b/.gitignore index 261745f..8ae237f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.[oa] +*.so *_test diff --git a/tmalloc/run_test.sh b/tmalloc/run_test.sh new file mode 100755 index 0000000..ee52ce5 --- /dev/null +++ b/tmalloc/run_test.sh @@ -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 index 0000000..d09e4e4 --- /dev/null +++ b/tmalloc/tmalloc.c @@ -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 +#include +#include +#include +#include +#include /* 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 index 0000000..72fa2f0 --- /dev/null +++ b/tmalloc/tmalloc.h @@ -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 index 0000000..06da94b --- /dev/null +++ b/tmalloc/tmalloc_test.c @@ -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 +#include +#include +#include +#include + +#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; +}