/* Copyright (C) 2000-1 drscholl@users.sourceforge.net This is free software distributed under the terms of the GNU Public License. See the file COPYING for details. $Id: debug.c,v 1.21 2001/09/24 04:06:26 drscholl Exp $ */ /* This is a very simple memory management debugger. It's useful for detecting memory leaks, references to uninitialzed memory, bad pointers, buffer overflow and getting an idea of how much memory is used by a program. */ #if HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "debug.h" #ifdef DEBUG #define MIN(a,b) ((a> SHIFT; return ((hash & 0x7fffffff) % SIZE); } static int debug_overflow (BLOCK * block, const char *func) { if (*((unsigned char *) block->val + block->len) != END_BYTE) { fprintf (stderr, "debug_%s: buffer overflow detected in data allocated at %s:%d\n", func, block->file, block->line); return 1; } return 0; } static void debug_exhausted (const char *file, int line) { fprintf (stderr, "debug_malloc(): memory exhausted at %s:%d (%d bytes allocated)\n", file, line, Memory_Usage); } void * debug_malloc (int bytes, const char *file, int line) { BLOCK *block, **ptr; int offset; if (bytes == 0) { fprintf (stderr, "debug_malloc(): 0 bytes requested at %s:%d\n", file, line); return 0; } block = malloc (sizeof (BLOCK)); if (!block) { debug_exhausted (file, line); return 0; } block->val = malloc (bytes + 1); if (!block->val) { debug_exhausted (__FILE__, __LINE__); free (block); return 0; } Memory_Usage += bytes; block->len = bytes; block->file = file; block->line = line; memset (block->val, ALLOC_BYTE, bytes); *((unsigned char *) block->val + bytes) = END_BYTE; offset = debug_hash (block->val); for (ptr = &Allocation[offset]; *ptr; ptr = &(*ptr)->next) { if (block->val < (*ptr)->val) break; } block->next = *ptr; *ptr = block; return block->val; } void * debug_calloc (int count, int bytes, const char *file, int line) { void *ptr = debug_malloc (count * bytes, file, line); if (!ptr) return 0; memset (ptr, 0, count * bytes); return ptr; } static BLOCK * find_block (void *ptr) { int offset = debug_hash (ptr); BLOCK *block; for (block = Allocation[offset]; block && ptr > block->val; block = block->next); return ((block != 0 && ptr == block->val) ? block : 0); } void * debug_realloc (void *ptr, int bytes, const char *file, int line) { void *newptr; BLOCK *block = 0; if (bytes == 0) { debug_free (ptr, file, line); return 0; } if (ptr) { block = find_block (ptr); if (!block) { fprintf (stderr, "debug_realloc(): invalid pointer at %s:%d\n", file, line); return 0; } debug_overflow (block, "realloc"); } newptr = debug_malloc (bytes, file, line); if (!newptr) return 0; if (ptr) { memcpy (newptr, ptr, MIN (bytes, block->len)); debug_free (ptr, file, line); } return newptr; } void debug_free (void *ptr, const char *file, int line) { BLOCK **list, *block = 0; int offset; if (!ptr) { fprintf (stderr, "debug_free: attempt to free NULL pointer at %s:%d\n", file, line); return; } offset = debug_hash (ptr); if (!Allocation[offset]) { fprintf (stderr, "debug_free: attempt to free bogus pointer at %s:%d\n", file, line); return; } for (list = &Allocation[offset]; *list; list = &(*list)->next) { if ((*list)->val == ptr) break; } if (!*list) { fprintf (stderr, "debug_free: attempt to free bogus pointer at %s:%d\n", file, line); return; } block = *list; /* remove the block from the list */ *list = (*list)->next; debug_overflow (block, "free"); memset (block->val, FREE_BYTE, block->len); free (block->val); Memory_Usage -= block->len; free (block); } #if 0 /* display the contents of an allocated block */ static void debug_dump (BLOCK * block) { int i; fputc ('\t', stderr); for (i = 0; i < block->len && i < 8; i++) fprintf (stderr, "%02x ", *((unsigned char *) block->val + i)); fputc ('\t', stderr); for (i = 0; i < block->len && i < 8; i++) fprintf (stderr, "%c", isprint (*((unsigned char *) block->val + i)) ? *((unsigned char *) block->val + i) : '.'); fputc ('\n', stderr); } #endif void debug_cleanup (void) { int i; BLOCK *block; for (i = 0; i < SIZE; i++) { for (block = Allocation[i]; block; block = block->next) { debug_overflow (block, "cleanup"); fprintf (stderr, "debug_cleanup: %d bytes allocated at %s:%d\n", block->len, block->file, block->line); #if 0 debug_dump (block); #endif } } if (Memory_Usage) fprintf (stderr, "debug_cleanup: %d bytes total\n", Memory_Usage); } char * debug_strdup (const char *s, const char *file, int line) { char *r; r = debug_malloc (strlen (s) + 1, file, line); if (!r) return 0; strcpy (r, s); return r; } /* check to see if a pointer is valid. ptr can be an offset into an allocation block, so don't use find_block() */ int debug_valid (void *ptr, int len) { BLOCK *block; int offset; offset = debug_hash (ptr); for (block = Allocation[offset]; block && (char *) ptr > (char *) block->val + block->len; block = block->next) ; if (!block) { fprintf (stderr, "debug_valid: invalid pointer\n"); return 0; /* not found */ } /* ensure the pointer is within this block */ if (ptr < block->val) { fprintf (stderr, "debug_valid: invalid pointer\n"); return 0; } if (debug_overflow (block, "valid")) return 0; /* ensure that there are at least `len' bytes available */ return (((char *) ptr + len <= (char *) block->val + block->len)); } int debug_usage (void) { return Memory_Usage; } #endif /* DEBUG */