X-Git-Url: http://git.silcnet.org/gitweb/?p=runtime.git;a=blobdiff_plain;f=lib%2Fsilcutil%2Fstacktrace.c;h=b5cd543a960f59cb16da6849da215c4dc3964577;hp=e1563415de8735a275799052d2a66af25b345b53;hb=adf03e69c4e96ffdac88e7cfe9c2b4583166eb6e;hpb=2ede5da9520e7e8508ae28656d542824c4c29793 diff --git a/lib/silcutil/stacktrace.c b/lib/silcutil/stacktrace.c index e1563415..b5cd543a 100644 --- a/lib/silcutil/stacktrace.c +++ b/lib/silcutil/stacktrace.c @@ -17,14 +17,58 @@ */ +/* 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 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, "%s:%d: #blocks=%lu, bytes=%lu\n", + fprintf(fp, "%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)