Added memory corruption checks to stacktrace.c
[runtime.git] / lib / silcutil / stacktrace.c
index e1563415de8735a275799052d2a66af25b345b53..b5cd543a960f59cb16da6849da215c4dc3964577 100644 (file)
 
 */
 
+/* This file implements memory leak checker and basic memory corruption
+   and double free checker.  It is multi-thread safe.  It does the
+   following:
+
+   o Tracks all memory allocations and report any unfreed memory at the
+     end of the program with backtrace where the memory was allocated.
+
+   o Checks if a memory location has been freed already and abort the
+     program with the backtrace of the location of the double free.
+
+   o Checks if a given pointer has been allocated at all and abort the
+     program with the backtrace where the invalid free was given.
+
+   o Checks at the time of free if the memory was written out of bounds
+     (overflow) and abort with the backtrace of the free.  The backtrace
+     might not help to find the overflow but at least it is detected.
+     By setting SILC_MALLOC_DUMP the memory is dummped to help and see
+     what it contains.
+
+   The following environment variables can be used:
+
+   SILC_MALLOC_NO_FREE
+
+     When set to value 1, the program doesn't actually free any memory.
+     This provides more detailed information especially in case of double
+     free.  If the location of the double free cannot be located, by
+     setting this variable the program will show where the memory was
+     originally allocated and freed.
+
+   SILC_MALLOC_DUMP
+
+     When set to value 1, in case of fatal error, dumps the memory location,
+     if possible.  This can help see what the memory contains.
+
+   To work correctly this of course expects that code uses SILC memory
+   allocation and access routines.
+
+*/
+
 #include "silcruntime.h"
 
 #ifdef SILC_STACKTRACE
+#include <execinfo.h>
 
 static void *st_blocks = NULL;
 static unsigned long st_blocks_count = 0;
+static unsigned long st_num_malloc = 0;
 static SilcBool dump = FALSE;
 static SilcBool malloc_check = FALSE;
+static SilcBool no_free = FALSE;
+static SilcBool dump_mem = FALSE;
+static SilcMutex lock = NULL;
 
 #ifdef SILC_DEBUG
 #define SILC_ST_DEPTH 15
@@ -34,78 +78,153 @@ static SilcBool malloc_check = FALSE;
 
 /* Memory block with stack trace */
 typedef struct SilcStBlockStruct {
-  unsigned int dumpped  : 1;   /* Block is dumpped */
-  unsigned int depth    : 8;   /* Depth of stack trace */
-  unsigned int line     : 23;  /* Allocation line in program */
-  void *stack[SILC_ST_DEPTH];  /* Stack trace */
-  const char *file;            /* Allocation file in program */
-  unsigned long size;          /* Allocated memory size */
   struct SilcStBlockStruct *next;
   struct SilcStBlockStruct *prev;
+  void *stack[SILC_ST_DEPTH];  /* Stack trace */
+  const char *file;            /* Allocation file in program */
+  const char *free_file;       /* Free file in program */
+  SilcUInt32 size;             /* Allocated memory size */
+  SilcUInt16 line;             /* Allocation line in program */
+  SilcUInt16 free_line;                /* Free line in program */
+  SilcUInt16 depth;            /* Depth of stack trace */
+  SilcUInt16 dumpped;          /* Block is dumpped */
+  SilcUInt32 bound;            /* Top bound */
 } *SilcStBlock;
 
-/* Get current frame pointer */
-#define SILC_ST_GET_FP(ret_fp)                 \
-do {                                           \
-  register void *cfp;                          \
-  asm volatile ("movl %%ebp, %0" : "=r" (cfp));        \
-  (ret_fp) = cfp;                              \
-} while(0);
-
-#define SILC_ST_GET_SIZE(size) ((size + sizeof(struct SilcStBlockStruct)))
+#define SILC_ST_TOP_BOUND 0xfeed1977
+#define SILC_ST_BOTTOM_BOUND 0x9152beef
+#define SILC_ST_GET_SIZE(size) ((size + sizeof(struct SilcStBlockStruct) + 4))
 #define SILC_ST_GET_STACK(p) ((SilcStBlock)(((unsigned char *)p) -     \
                            sizeof(struct SilcStBlockStruct)))
 #define SILC_ST_GET_PTR(p) (((unsigned char *)p) +             \
                            sizeof(struct SilcStBlockStruct))
+#define SILC_ST_GET_BOUND(p, size) (SilcUInt32 *)(((unsigned char *)p) + \
+                                   SILC_ST_GET_SIZE(size) - 4)
 
-void silc_st_stacktrace(SilcStBlock stack)
+void silc_st_abort(SilcStBlock stack, const char *file, int line,
+                  char *fmt, ...)
 {
-  void *fp;
+  void *bt[SILC_ST_DEPTH];
+  SilcUInt32 *bound;
+  va_list va;
+  int btc;
+
+  va_start(va, fmt);
+  vfprintf(stderr, fmt, va);
+  va_end(va);
+
+  fprintf(stderr, "----- BACKTRACE -----\n%s:%d:\n", file, line);
+  btc = backtrace(bt, SILC_ST_DEPTH);
+  backtrace_symbols_fd(bt, btc, 2);
+
+  if (stack) {
+    fprintf(stderr, "----- MEMORY TRACE -----\n");
+    if (stack->free_file)
+      fprintf(stderr, "Freed at: %s:%d\n", stack->free_file,
+             stack->free_line);
+    fprintf(stderr, "Originally allocated at:\n");
+    fprintf(stderr, "%s:%d:\n", stack->file, stack->line);
+    backtrace_symbols_fd(stack->stack, stack->depth, 2);
+    fflush(stderr);
+
+    if (dump_mem) {
+      fprintf(stderr, "----- MEMORY HEADER -----\n");
+      fprintf(stderr, "Header length: %lu, total length %lu\n",
+             sizeof(struct SilcStBlockStruct), SILC_ST_GET_SIZE(stack->size));
+      silc_hexdump((void *)stack, sizeof(struct SilcStBlockStruct), stderr);
+      fflush(stderr);
+      fprintf(stderr, "Header bound is: %p\n",
+             SILC_32_TO_PTR(stack->bound));
+      if (stack->bound != SILC_ST_TOP_BOUND) {
+       fprintf(stderr, "Header bound should be: %p\n",
+               SILC_32_TO_PTR(SILC_ST_TOP_BOUND));
+        fprintf(stderr, "MEMORY IS CORRUPTED (UNDERFLOW)!\n");
+      }
+
+      fprintf(stderr, "----- USER MEMORY -----\n");
+      fprintf(stderr, "Length: %d\n", stack->size);
+      silc_hexdump(((unsigned char *)stack) +
+                   sizeof(struct SilcStBlockStruct), stack->size, stderr);
+      fflush(stderr);
+
+      fprintf(stderr, "----- MEMORY FOOTER -----\n");
+      bound = SILC_ST_GET_BOUND(stack, stack->size);
+      silc_hexdump((unsigned char *)bound, 4, stderr);
+      fprintf(stderr, "Footer bound is: %p\n", SILC_32_TO_PTR(*bound));
+      if (*bound != SILC_ST_BOTTOM_BOUND) {
+       fprintf(stderr, "Footer bound should be: %p\n",
+               SILC_32_TO_PTR(SILC_ST_BOTTOM_BOUND));
+        fprintf(stderr, "MEMORY IS CORRUPTED (OVERFLOW)!\n");
+      }
+    }
+  }
 
+  fflush(stderr);
+
+  abort();
+}
+
+void silc_st_stacktrace(SilcStBlock stack)
+{
   if (!dump) {
     atexit(silc_st_dump);
     dump = TRUE;
   }
 
   if (!malloc_check) {
+    const char *var;
+
+    var = silc_getenv("SILC_MALLOC_NO_FREE");
+    if (var && *var == '1')
+      no_free = TRUE;
+
+    var = silc_getenv("SILC_MALLOC_DUMP");
+    if (var && *var == '1')
+      dump_mem = TRUE;
+
     /* Linux libc malloc check */
-    setenv("MALLOC_CHECK_", "2", 1);
+    silc_setenv("MALLOC_CHECK_", "3");
 
     /* NetBSD malloc check */
-    setenv("MALLOC_OPTIONS", "AJ", 1);
+    silc_setenv("MALLOC_OPTIONS", "AJ");
 
     malloc_check = TRUE;
-  }
 
-  /* Save the stack */
-  SILC_ST_GET_FP(fp);
-  for (stack->depth = 0; fp; stack->depth++) {
-    if (stack->depth == SILC_ST_DEPTH)
-      break;
-
-    /* Get program pointer and frame pointer from this frame */
-    stack->stack[stack->depth] = *((void **)(((unsigned char *)fp) + 4));
-    fp = *((void **)fp);
+    silc_mutex_alloc(&lock);
   }
+
+  /* Get backtrace */
+  stack->depth = backtrace(stack->stack, SILC_ST_DEPTH);
 }
 
 void *silc_st_malloc(size_t size, const char *file, int line)
 {
   SilcStBlock stack = (SilcStBlock)malloc(SILC_ST_GET_SIZE(size));
-  assert(stack != NULL);
+
+  if (!stack)
+    return NULL;
 
   stack->dumpped = 0;
   stack->file = file;
+  stack->free_file = NULL;
   stack->line = line;
   stack->size = size;
+  stack->bound = SILC_ST_TOP_BOUND;
   silc_st_stacktrace(stack);
 
+  silc_mutex_lock(lock);
+
   stack->next = st_blocks;
   stack->prev = NULL;
   if (st_blocks)
     ((SilcStBlock)st_blocks)->prev = stack;
   st_blocks = stack;
   st_blocks_count++;
+  st_num_malloc++;
+
+  silc_mutex_unlock(lock);
+
+  *SILC_ST_GET_BOUND(stack, size) = SILC_ST_BOTTOM_BOUND;
 
   return SILC_ST_GET_PTR(stack);
 }
@@ -126,6 +245,10 @@ void *silc_st_realloc(void *ptr, size_t size, const char *file, int line)
 
   stack = SILC_ST_GET_STACK(ptr);
   if (stack->size >= size) {
+    /* Must update footer when the size changes */
+    if (stack->size != size)
+      *SILC_ST_GET_BOUND(stack, size) = SILC_ST_BOTTOM_BOUND;
+
     stack->size = size;
     return ptr;
   } else {
@@ -138,12 +261,42 @@ void *silc_st_realloc(void *ptr, size_t size, const char *file, int line)
 
 void silc_st_free(void *ptr, const char *file, int line)
 {
-  SilcStBlock stack;
+  SilcStBlock stack, s;
 
   if (!ptr)
     return;
 
+  /* Check for double free */
+  if (!memcmp((unsigned char *)ptr - sizeof(struct SilcStBlockStruct),
+             "\x47\x47\x47\x47", 4))
+    silc_st_abort(no_free ? ptr - sizeof(struct SilcStBlockStruct) : NULL,
+                 file, line, "SILC_MALLOC: double free: %p already freed\n",
+                 ptr - sizeof(struct SilcStBlockStruct));
+
   stack = SILC_ST_GET_STACK(ptr);
+
+  silc_mutex_lock(lock);
+
+  /* Check if we have ever made this allocation */
+  for (s = st_blocks; s; s = s->next)
+    if (s == stack)
+      break;
+  if (s == NULL)
+    silc_st_abort(NULL, file, line,
+                 "SILC_MALLOC: %p was never allocated\n", stack);
+
+  /* Check for underflow */
+  if (stack->bound != SILC_ST_TOP_BOUND)
+    silc_st_abort(stack, file, line,
+                 "SILC_MALLOC: %p was written out of bounds (underflow)\n",
+                 stack);
+
+  /* Check for overflow */
+  if (*SILC_ST_GET_BOUND(stack, stack->size) != SILC_ST_BOTTOM_BOUND)
+    silc_st_abort(stack, file, line,
+                 "SILC_MALLOC: %p was written out of bounds (overflow)\n",
+                 stack);
+
   if (stack->next)
     stack->next->prev = stack->prev;
   if (stack->prev)
@@ -153,7 +306,18 @@ void silc_st_free(void *ptr, const char *file, int line)
 
   st_blocks_count--;
 
-  memset(stack, 'F', SILC_ST_GET_SIZE(stack->size));
+  silc_mutex_unlock(lock);
+
+  stack->free_file = file;
+  stack->free_line = line;
+
+  if (no_free) {
+    memset(stack, 0x47, 8);
+    return;
+  }
+
+  memset(stack, 0x47, SILC_ST_GET_SIZE(stack->size));
+
   free(stack);
 }
 
@@ -170,15 +334,20 @@ void *silc_st_strdup(const char *string, const char *file, int line)
   return silc_st_memdup(string, strlen(string), file, line);
 }
 
-/* Dumps the stack into file if there are leaks.  The file can be read
-   with a special stacktrace tool. */
+/* Dumps the stack into file if there are leaks. */
 
 void silc_st_dump(void)
 {
   SilcStBlock stack, s;
   unsigned long leaks = 0, blocks, bytes;
   FILE *fp = NULL;
+  char **syms, *cp;
   int i;
+  SilcMutex l;
+
+  l = lock;
+  lock = NULL;
+  silc_mutex_free(l);
 
   for (stack = st_blocks; stack; stack = stack->next) {
     bytes = blocks = 0;
@@ -194,6 +363,10 @@ void silc_st_dump(void)
        fp = stderr;
     }
 
+    /* Get symbol names */
+    syms = backtrace_symbols(stack->stack, stack->depth);
+
+    /* Find number of leaks and bytes leaked for this leak */
     for (s = stack; s; s = s->next) {
       if (s->file == stack->file && s->line == stack->line &&
          s->depth == stack->depth &&
@@ -206,10 +379,24 @@ void silc_st_dump(void)
     }
 
     if (blocks) {
-      fprintf(fp, "<stacktrace>%s:%d: #blocks=%lu, bytes=%lu\n",
+      fprintf(fp, "<leak>%s:%d: #blocks=%lu, bytes=%lu\n",
              stack->file, stack->line, blocks, bytes);
-      for (i = 0; i < stack->depth; i++)
-       fprintf(fp, "\tpc=%p\n", stack->stack[i]);
+      for (i = 0; i < stack->depth; i++) {
+       if (syms) {
+         cp = syms[i];
+         if (strchr(cp, '('))
+           cp = strchr(cp, '(') + 1;
+         else if (strchr(cp, ' '))
+           cp = strchr(cp, ' ') + 1;
+         if (strchr(cp, ')'))
+           *strchr(cp, ')') = ' ';
+         fprintf(fp, "\t%s\n", cp);
+       } else {
+         fprintf(fp, "\tpc=%p\n", stack->stack[i]);
+       }
+      }
+      fprintf(fp, "\n");
+      free(syms);
     }
   }
 
@@ -224,6 +411,7 @@ void silc_st_dump(void)
            "-----------------------------------------\n"
            "-----------------------------------------\n",
            leaks, st_blocks_count);
+    fprintf(stderr, "Number of allocations: %lu\n", st_num_malloc);
   }
 
   if (fp && fp != stderr)