/* $FreeBSD$ */ /* xmalloc.c - Simple malloc debugging library implementation This software is released under a BSD-style license. See the file LICENSE for details and copyright. */ /* TODO: - red zones - group dumps by source location */ #include #include #include #define XMALLOC_INTERNAL 1 #include "xmalloc.h" /* Internal stuff. */ typedef struct hashTableItemRec { void *ptr; int bytes; const char *file; int line; const char *func; struct hashTableItemRec *next; } hashTableItem; typedef struct { hashTableItem **table; } hashTable; static int xmalloc_peak; static int xmalloc_current; static int xmalloc_peak_blocks; static int xmalloc_current_blocks; static int xmalloc_fail_after; #define TABLE_BITS 8 #define TABLE_MASK ((1 << TABLE_BITS) - 1) #define TABLE_SIZE (1 << TABLE_BITS) static hashTable * hash_table_new(void) { hashTable *tbl; tbl = malloc(sizeof(*tbl)); if (tbl != NULL) { tbl->table = calloc(TABLE_SIZE, sizeof(*tbl->table)); if (tbl->table == NULL) { free(tbl); return NULL; } } return tbl; } static int hash_void_ptr(void *ptr) { int hash; int i; /* I took this hash function just off the top of my head, I have no idea whether it is bad or very bad. */ hash = 0; for (i = 0; i < (int)sizeof(ptr)*8 / TABLE_BITS; i++) { hash ^= (unsigned long)ptr >> i*8; hash += i * 17; hash &= TABLE_MASK; } return hash; } static void hash_table_add(hashTable *tbl, void *ptr, int bytes, const char *file, int line, const char *func) { int i; hashTableItem *item, *new; i = hash_void_ptr(ptr); item = tbl->table[i]; if (item != NULL) while (item->next != NULL) item = item->next; new = malloc(sizeof(*new)); assert(new != NULL); new->ptr = ptr; new->bytes = bytes; new->file = file; new->line = line; new->func = func; new->next = NULL; if (item != NULL) item->next = new; else tbl->table[i] = new; xmalloc_current += bytes; if (xmalloc_current > xmalloc_peak) xmalloc_peak = xmalloc_current; xmalloc_current_blocks++; if (xmalloc_current_blocks > xmalloc_peak_blocks) xmalloc_peak_blocks = xmalloc_current_blocks; } static void hash_table_del(hashTable *tbl, void *ptr) { int i; hashTableItem *item, *prev; i = hash_void_ptr(ptr); item = tbl->table[i]; if (item == NULL) { printf("xfree: invalid ptr %p\n", ptr); abort(); } prev = NULL; while (item->ptr != ptr) { prev = item; item = item->next; } if (item->ptr != ptr) { printf("xfree: invalid ptr %p\n", ptr); abort(); } xmalloc_current -= item->bytes; xmalloc_current_blocks--; if (prev != NULL) { prev->next = item->next; free(item); } else { tbl->table[i] = item->next; free(item); } } static hashTable *xmalloc_table = NULL; static void xmalloc_init(void) { if (xmalloc_table == NULL) { xmalloc_table = hash_table_new(); xmalloc_peak = 0; xmalloc_peak_blocks = 0; xmalloc_current = 0; xmalloc_current_blocks = 0; xmalloc_fail_after = -1; } assert(xmalloc_table != NULL); assert(xmalloc_table->table != NULL); } /* Public API. */ void xmalloc_configure(int fail_after) { xmalloc_init(); xmalloc_fail_after = fail_after; } int xmalloc_dump_leaks(void) { int i; int num_leaks = 0; int leaked_bytes = 0; hashTableItem *item; xmalloc_init(); for (i = 0; i < TABLE_SIZE; i++) { item = xmalloc_table->table[i]; while (item != NULL) { printf("%s:%d: %s: %d bytes at %p not freed\n", item->file, item->line, item->func, item->bytes, item->ptr); num_leaks++; leaked_bytes += item->bytes; item = item->next; } } if (num_leaks == 0) printf("No memory leaks.\n"); else printf("%d unfreed memory chuncks, total %d unfreed bytes.\n", num_leaks, leaked_bytes); printf("Peak memory consumption %d bytes (%.1f kB, %.1f MB) in %d blocks ", xmalloc_peak, (double)xmalloc_peak / 1024, (double)xmalloc_peak / (1024*1024), xmalloc_peak_blocks); printf("(average "); if (xmalloc_peak_blocks) printf("%d", ((xmalloc_peak + xmalloc_peak_blocks / 2) / xmalloc_peak_blocks)); else printf("N/A"); printf(" bytes per block).\n"); return num_leaks; } void * xmalloc_impl(size_t size, const char *file, int line, const char *func) { void *ptr; xmalloc_init(); assert(size > 0); if (xmalloc_fail_after == 0) { xmalloc_fail_after = -2; #if 0 printf("xmalloc: forced failure %s:%d: %s\n", file, line, func); #endif return NULL; } else if (xmalloc_fail_after == -2) { printf("xmalloc: called after failure from %s:%d: %s\n", file, line, func); assert(0); } else if (xmalloc_fail_after > 0) xmalloc_fail_after--; ptr = malloc(size); if (ptr != NULL) hash_table_add(xmalloc_table, ptr, (int)size, file, line, func); return ptr; } void * xcalloc_impl(size_t nmemb, size_t size, const char *file, int line, const char *func) { void *ptr; xmalloc_init(); assert(size > 0); if (xmalloc_fail_after == 0) { xmalloc_fail_after = -2; #if 0 printf("xcalloc: forced failure %s:%d: %s\n", file, line, func); #endif return NULL; } else if (xmalloc_fail_after == -2) { printf("xcalloc: called after failure from %s:%d: %s\n", file, line, func); assert(0); } else if (xmalloc_fail_after > 0) xmalloc_fail_after--; ptr = calloc(nmemb, size); if (ptr != NULL) hash_table_add(xmalloc_table, ptr, (int)(nmemb * size), file, line, func); return ptr; } void xfree_impl(void *ptr, const char *file, int line, const char *func) { /*LINTED*/(void)&file; /*LINTED*/(void)&line; /*LINTED*/(void)&func; xmalloc_init(); if (ptr != NULL) hash_table_del(xmalloc_table, ptr); free(ptr); } void * xrealloc_impl(void *ptr, size_t new_size, const char *file, int line, const char *func) { void *new_ptr; xmalloc_init(); assert(ptr != NULL); assert(new_size > 0); if (xmalloc_fail_after == 0) { xmalloc_fail_after = -2; return NULL; } else if (xmalloc_fail_after == -2) { printf("xrealloc: called after failure from %s:%d: %s\n", file, line, func); assert(0); } else if (xmalloc_fail_after > 0) xmalloc_fail_after--; new_ptr = realloc(ptr, new_size); if (new_ptr != NULL) { hash_table_del(xmalloc_table, ptr); hash_table_add(xmalloc_table, new_ptr, (int)new_size, file, line, func); } return new_ptr; } /* EOF */