Added memory corruption checks to stacktrace.c
authorPekka Riikonen <priikone@silcnet.org>
Mon, 11 Feb 2008 14:45:24 +0000 (16:45 +0200)
committerPekka Riikonen <priikone@silcnet.org>
Mon, 11 Feb 2008 14:45:24 +0000 (16:45 +0200)
The stacktrace now produces backtraces using backtrace().

The stacktrace now supports basic memory corruption checks in
addition of memory leak checks.  It supports double free checks,
invalid free checks and out of bound checks.

Environment variables SILC_MALLOC_NO_FREE and SILC_MALLOC_DUMP
can be used to get even more information out of it.

configure.ad
lib/silcutil/silcenv.c
lib/silcutil/silcmemory.h
lib/silcutil/stacktrace.c
lib/silcutil/stacktrace.h

index 4899dd6ef42369fa5a10c6e7f44c829c72929da9..5b6c9510ea798d671642b2a8eaa34673ff01374d 100644 (file)
@@ -260,7 +260,7 @@ AC_HEADER_STAT
 
 # More header checking
 #
-AC_CHECK_HEADERS(unistd.h string.h errno.h fcntl.h assert.h)
+AC_CHECK_HEADERS(unistd.h string.h errno.h fcntl.h assert.h execinfo.h)
 AC_CHECK_HEADERS(sys/types.h sys/stat.h sys/time.h stddef.h)
 AC_CHECK_HEADERS(netinet/in.h netinet/tcp.h xti.h netdb.h sys/resource.h)
 AC_CHECK_HEADERS(pwd.h grp.h termcap.h paths.h)
@@ -382,7 +382,7 @@ AC_CHECK_FUNCS(chmod fcntl stat fstat getenv putenv strerror)
 AC_CHECK_FUNCS(getpid getgid getsid getpgid getpgrp getuid sched_yield)
 AC_CHECK_FUNCS(setgroups initgroups nl_langinfo nanosleep)
 AC_CHECK_FUNCS(strchr snprintf strstr strcpy strncpy memcpy memset memmove)
-AC_CHECK_FUNCS(setenv getenv putenv unsetenv clearenv)
+AC_CHECK_FUNCS(setenv getenv putenv unsetenv clearenv backtrace)
 
 # Check getopt_long
 AC_CHECK_FUNC(getopt_long,
@@ -725,6 +725,7 @@ AC_ARG_ENABLE(stack-trace,
     yes)
       AC_MSG_RESULT(yes)
       AC_DEFINE([SILC_STACKTRACE], [], [SILC_STACKTRACE])
+      CFLAGS="$CFLAGS -rdynamic"
       ;;
     *)
       AC_MSG_RESULT(no)
index fe50269d030b102dae660812c9c2a9cad7ed645f..9d22b09d800a013f7a73ce86191b10199872d861 100644 (file)
@@ -23,7 +23,6 @@
 
 SilcBool silc_setenv(const char *variable, const char *value)
 {
-  SILC_LOG_DEBUG(("Set %s=%s", variable, value));
 #if defined(HAVE_SETENV)
   return setenv(variable, value, TRUE) == 0;
 #elif defined (HAVE_PUTENV)
@@ -38,7 +37,6 @@ SilcBool silc_setenv(const char *variable, const char *value)
 
 const char *silc_getenv(const char *variable)
 {
-  SILC_LOG_DEBUG(("Get %s value", variable));
 #if defined(HAVE_GETENV)
   return (const char *)getenv(variable);
 #endif /* HAVE_GETENV */
@@ -49,7 +47,6 @@ const char *silc_getenv(const char *variable)
 
 SilcBool silc_unsetenv(const char *variable)
 {
-  SILC_LOG_DEBUG(("Unset %s value", variable));
 #if defined(HAVE_UNSETENV)
   return unsetenv(variable) == 0;
 #endif /* HAVE_GETENV */
@@ -60,7 +57,6 @@ SilcBool silc_unsetenv(const char *variable)
 
 SilcBool silc_clearenv(void)
 {
-  SILC_LOG_DEBUG(("Clear allenvironment variables"));
 #if defined(HAVE_CLEARENV)
   return clearenv() == 0;
 #endif /* HAVE_GETENV */
index 2389a4bf5288efa2708153fd009e02bab0b9d95f..74974e96309472784dd3616f446cb129648980e4 100644 (file)
@@ -131,10 +131,6 @@ void *silc_memdup(const void *ptr, size_t size);
 char *silc_strdup(const char *str);
 
 #else
-#ifndef SILC_DIST_TOOLKIT
-#error "The stack trace is not supported in this distribution"
-#endif /* SILC_DIST_TOOLKIT */
-
 #include "stacktrace.h"
 #endif /* SILC_STACKTRACE */
 
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)
index bfe8ae035e34badbcb7ed1de5b74f363aba705ce..ef4db5d80ca468601b6327ad7e8a980ebcc37372 100644 (file)
@@ -24,7 +24,7 @@
 #error "Do not include internal header file directly"
 #endif
 
-#if defined(__GNUC__) && defined(__i386__)
+#if defined(__GNUC__) && defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE)
 
 #undef strdup
 #define silc_malloc(s)      silc_st_malloc((s), __FILE__, __LINE__)
@@ -32,6 +32,7 @@
 #define silc_realloc(p, s)  silc_st_realloc((p), (s), __FILE__, __LINE__)
 #define silc_free(p)        silc_st_free((p), __FILE__, __LINE__)
 #define silc_memdup(p, s)   silc_st_memdup((p), (s), __FILE__, __LINE__)
+#define silc_strdup(s)      silc_st_strdup((s), __FILE__, __LINE__)
 #define strdup(s)           silc_st_strdup((s), __FILE__, __LINE__)
 
 void *silc_st_malloc(size_t size, const char *file, int line);
@@ -44,6 +45,6 @@ void silc_st_dump(void);
 
 #else
 #error "memory allocation stack trace not supported on this platform"
-#endif /* __GNUC__ && __i386__ */
+#endif /* __GNUC__ && HAVE_EXECINFO_H && HAVE_BACKTRACE */
 
 #endif /* STACKTRACE_H */