Use backtrace() in stack tracing for prettier output
[silc.git] / lib / silcutil / stacktrace.c
1 /*
2
3   stacktrace.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2002 - 2008 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; version 2 of the License.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18 */
19
20 /* This file implements memory leak checker and basic memory corruption
21    and double free checker.  It is multi-thread safe.  It does the
22    following:
23
24    o Tracks all memory allocations and report any unfreed memory at the
25      end of the program with backtrace where the memory was allocated.
26
27    o Checks if a memory location has been freed already and abort the
28      program with the backtrace of the location of the double free.
29
30    o Checks if a given pointer has been allocated at all and abort the
31      program with the backtrace where the invalid free was given.
32
33    o Checks at the time of free if the memory was written out of bounds
34      (overflow) and abort with the backtrace of the free.  The backtrace
35      might not help to find the overflow but at least it is detected.
36      By setting SILC_MALLOC_DUMP the memory is dummped to help and see
37      what it contains.
38
39    o Can detect if the memory is read or written out of bounds (overflow)
40      and abort immediately the with the backtrace when the illegal access
41      occurs.  This can be enabled by using SILC_MALLOC_PROTECT.
42
43    The following environment variables can be used:
44
45    SILC_MALLOC_NO_FREE
46
47      When set to value 1, the program doesn't actually free any memory.
48      This provides more detailed information especially in case of double
49      free.  If the location of the double free cannot be located, by
50      setting this variable the program will show where the memory was
51      originally allocated and freed.
52
53    SILC_MALLOC_DUMP
54
55      When set to value 1, in case of fatal error, dumps the memory location,
56      if possible.  This can help see what the memory contains.
57
58    SILC_MALLOC_PROTECT
59
60      When set to value 1 each allocation will have an inaccesible memory
61      page following the allocated memory area.  This will detect if the
62      the memory is accessed (read or write) beyond its boundaries.  This
63      will help to identify the place where illegal memory access occurs.
64
65    To work correctly this of course expects that code uses SILC memory
66    allocation and access routines.
67
68 */
69
70 #include "silc.h"
71
72 #ifdef SILC_STACKTRACE
73 #if defined(HAVE_BACKTRACE)
74 #include <execinfo.h>
75 #endif /* HAVE_BACKTRACE */
76 #include <signal.h>
77 #if !defined(__APPLE__)
78 #include <malloc.h>
79 #else
80 #include <sys/malloc.h>
81 #endif
82 #include <sys/mman.h>
83
84 static void *st_blocks = NULL;
85 static unsigned long st_blocks_count = 0;
86 static unsigned long st_num_malloc = 0;
87 static SilcBool dump = FALSE;
88 static SilcBool no_free = FALSE;
89 static SilcBool dump_mem = FALSE;
90 static SilcUInt32 pg = 0;
91 static SilcMutex lock = NULL;
92
93 #ifdef SILC_DEBUG
94 #define SILC_ST_DEPTH 15
95 #else
96 #define SILC_ST_DEPTH 8
97 #endif /* SILC_DEBUG */
98
99 /* Memory block with stack trace */
100 typedef struct SilcStBlockStruct {
101   struct SilcStBlockStruct *next;
102   struct SilcStBlockStruct *prev;
103   void *stack[SILC_ST_DEPTH];   /* Stack trace */
104   const char *file;             /* Allocation file in program */
105   const char *free_file;        /* Free file in program */
106   SilcUInt32 size;              /* Allocated memory size */
107   SilcUInt16 line;              /* Allocation line in program */
108   SilcUInt16 free_line;         /* Free line in program */
109   SilcUInt16 depth;             /* Depth of stack trace */
110   SilcUInt16 dumpped;           /* Block is dumpped */
111   SilcUInt32 bound;             /* Top bound */
112 } *SilcStBlock;
113
114 #define SILC_ST_TOP_BOUND 0xfeed1977
115 #define SILC_ST_BOTTOM_BOUND 0x9152beef
116 #define SILC_ST_GET_SIZE(size) ((size + sizeof(struct SilcStBlockStruct) + 4))
117 #define SILC_ST_GET_STACK(p) ((SilcStBlock)(((unsigned char *)p) -      \
118                             sizeof(struct SilcStBlockStruct)))
119 #define SILC_ST_GET_PTR(p) (((unsigned char *)p) +              \
120                             sizeof(struct SilcStBlockStruct))
121 #define SILC_ST_GET_BOUND(p, size) (SilcUInt32 *)(((unsigned char *)p) + \
122                                     SILC_ST_GET_SIZE(size) - 4)
123
124 #define SILC_ST_ALIGN(bytes, align) (((bytes) + (align - 1)) & ~(align - 1))
125 #define SILC_ST_GET_SIZE_ALIGN(size, align) \
126   SILC_ST_ALIGN(SILC_ST_GET_SIZE(size) - 4, align)
127 #define SILC_ST_GET_PTR_ALIGN(stack, align)                               \
128   (((unsigned char *)stack) - (SILC_ST_GET_SIZE_ALIGN(stack->size, pg) -  \
129                                SILC_ST_GET_SIZE(stack->size)) - 4)
130 #define SILC_ST_GET_STACK_ALIGN(p, size, align)                             \
131   ((SilcStBlock)(((unsigned char *)p) + (SILC_ST_GET_SIZE_ALIGN(size, pg) - \
132                                          SILC_ST_GET_SIZE(size)) + 4))
133
134 #define silc_hexdump(ptr, size, file) \
135   silc_log_output_hexdump("", "", 0, ptr, size, "")
136
137 int silc_st_backtrace(void **stack)
138 {
139 #if defined(HAVE_BACKTRACE)
140   return backtrace(stack, SILC_ST_DEPTH);
141 #else
142   void *fp;
143   int depth;
144   asm volatile ("movl %%ebp, %0" : "=r" (fp));
145   for (depth = 0; fp; depth++) {
146     if (depth == SILC_ST_DEPTH)
147       break;
148
149     /* Get program pointer and frame pointer from this frame */
150     stack[depth] = *((void **)(((unsigned char *)fp) + 4));
151     fp = *((void **)fp);
152   }
153   return depth;
154 #endif /* HAVE_BACKTRACE */
155 }
156
157 char **silc_st_backtrace_symbols(void **stack, int depth)
158 {
159 #if defined(HAVE_BACKTRACE)
160   return backtrace_symbols(stack, depth);
161 #else
162   return NULL;
163 #endif /* HAVE_BACKTRACE */
164 }
165
166 void silc_st_backtrace_symbols_stderr(void **stack, int depth)
167 {
168 #if defined(HAVE_BACKTRACE)
169   backtrace_symbols_fd(stack, depth, 2);
170 #else
171   int i;
172   for (i = 0; i < depth; i++)
173     fprintf(stderr, "? [%p]\n", stack[i]);
174 #endif /* HAVE_BACKTRACE */
175 }
176
177 void silc_st_abort(SilcStBlock stack, const char *file, int line,
178                    char *fmt, ...)
179 {
180   void *bt[SILC_ST_DEPTH];
181   SilcUInt32 *bound;
182   va_list va;
183   int btc;
184
185   va_start(va, fmt);
186   vfprintf(stderr, fmt, va);
187   va_end(va);
188
189   fprintf(stderr, "----- BACKTRACE -----\n%s:%d:\n", file, line);
190   btc = silc_st_backtrace(bt);
191   silc_st_backtrace_symbols_stderr(bt, btc);
192
193   if (stack) {
194     fprintf(stderr, "----- MEMORY TRACE -----\n");
195     if (stack->free_file)
196       fprintf(stderr, "Freed at: %s:%d\n", stack->free_file,
197               stack->free_line);
198     fprintf(stderr, "Originally allocated at:\n");
199     fprintf(stderr, "%s:%d:\n", stack->file, stack->line);
200     silc_st_backtrace_symbols(stack->stack, stack->depth);
201     fflush(stderr);
202
203     if (dump_mem) {
204       fprintf(stderr, "----- MEMORY HEADER -----\n");
205       fprintf(stderr, "Header length: %lu, total length %lu\n",
206               sizeof(struct SilcStBlockStruct), SILC_ST_GET_SIZE(stack->size));
207       silc_hexdump((void *)stack, sizeof(struct SilcStBlockStruct), stderr);
208       fflush(stderr);
209       fprintf(stderr, "Header bound is: %p\n",
210               SILC_32_TO_PTR(stack->bound));
211       if (stack->bound != SILC_ST_TOP_BOUND) {
212         fprintf(stderr, "Header bound should be: %p\n",
213                 SILC_32_TO_PTR(SILC_ST_TOP_BOUND));
214         fprintf(stderr, "MEMORY IS CORRUPTED (UNDERFLOW)!\n");
215       }
216
217       fprintf(stderr, "----- USER MEMORY -----\n");
218       fprintf(stderr, "Length: %d\n", stack->size);
219       silc_hexdump(((unsigned char *)stack) +
220                     sizeof(struct SilcStBlockStruct), stack->size, stderr);
221       fflush(stderr);
222
223       fprintf(stderr, "----- MEMORY FOOTER -----\n");
224       bound = SILC_ST_GET_BOUND(stack, stack->size);
225       silc_hexdump((unsigned char *)bound, 4, stderr);
226       fprintf(stderr, "Footer bound is: %p\n", SILC_32_TO_PTR(*bound));
227       if (*bound != SILC_ST_BOTTOM_BOUND) {
228         fprintf(stderr, "Footer bound should be: %p\n",
229                 SILC_32_TO_PTR(SILC_ST_BOTTOM_BOUND));
230         fprintf(stderr, "MEMORY IS CORRUPTED (OVERFLOW)!\n");
231       }
232     }
233   }
234
235   fflush(stderr);
236
237   abort();
238 }
239
240 void silc_st_sigsegv(int sig, siginfo_t *si, void *context)
241 {
242   SilcStBlock orig, stack = (SilcStBlock)si->si_addr;
243
244   /* Make the page accessible again */
245   mprotect(si->si_addr, pg, PROT_READ | PROT_WRITE);
246
247   /* Get the original page from the violated page */
248   orig = (SilcStBlock)(((unsigned char *)si->si_addr) -
249                         SILC_ST_GET_SIZE_ALIGN(stack->size, pg));
250   stack = SILC_ST_GET_STACK_ALIGN(orig, stack->size, pg);
251
252   silc_st_abort(stack, __FILE__, __LINE__,
253                 "SILC_MALLOC: access violation (overflow)\n");
254 }
255
256 void silc_st_stacktrace_init(void)
257 {
258   const char *var;
259
260   atexit(silc_st_dump);
261   dump = TRUE;
262
263   var = getenv("SILC_MALLOC_NO_FREE");
264   if (var && *var == '1')
265     no_free = TRUE;
266
267   var = getenv("SILC_MALLOC_DUMP");
268   if (var && *var == '1')
269     dump_mem = TRUE;
270
271   var = getenv("SILC_MALLOC_PROTECT");
272   if (var && *var == '1') {
273     struct sigaction sa;
274
275     sa.sa_flags = SA_SIGINFO;
276     sa.sa_sigaction = silc_st_sigsegv;
277     sigemptyset(&sa.sa_mask);
278     sigaction(SIGSEGV, &sa, NULL);
279
280 #if defined(_SC_PAGESIZE)
281     pg = sysconf(_SC_PAGESIZE);
282 #elif defined(_SC_PAGE_SIZE)
283     pg = sysconf(_SC_PAGE_SIZE);
284 #else
285     pg = getpagesize();
286 #endif /* _SC_PAGESIZE */
287   }
288
289   /* Linux libc malloc check */
290   setenv("MALLOC_CHECK_", "3", 1);
291
292   /* NetBSD malloc check */
293   setenv("MALLOC_OPTIONS", "AJ", 1);
294
295   silc_mutex_alloc(&lock);
296 }
297
298 void *silc_st_malloc(size_t size, const char *file, int line)
299 {
300   SilcStBlock stack;
301
302   if (silc_unlikely(!dump))
303     silc_st_stacktrace_init();
304
305   if (pg) {
306     unsigned char *ptr;
307
308 #if defined(HAVE_POSIX_MEMALIGN)
309     if (posix_memalign((void *)&ptr, pg,
310                        SILC_ST_GET_SIZE_ALIGN(size, pg) + pg))
311 #endif /* HAVE_POSIX_MEMALIGN */
312       return NULL;
313
314     /* The inaccessible page too will include the allocation information
315        so that we can get it when access violation occurs in that page. */
316     stack = (SilcStBlock)(ptr + SILC_ST_GET_SIZE_ALIGN(size, pg));
317     stack->size = size;
318
319     /* Protect the page */
320     if (mprotect(stack, pg, PROT_NONE))
321       silc_st_abort(NULL, file, line, "SILC_MALLOC: mprotect() error: %s\n",
322                     errno == ENOMEM ? "Cannot allocate memory. \nYour program "
323                     "leaks memory, allocates too much or system \n"
324                     "is out of memory.  The SILC_MALLOC_PROTECT cannot "
325                     "be used." : strerror(errno));
326
327     /* Get the accessible page */
328     stack = SILC_ST_GET_STACK_ALIGN(ptr, size, pg);
329   } else {
330     stack = (SilcStBlock)malloc(SILC_ST_GET_SIZE(size));
331     if (!stack)
332       return NULL;
333   }
334
335   stack->dumpped = 0;
336   stack->file = file;
337   stack->free_file = NULL;
338   stack->line = line;
339   stack->size = size;
340   stack->bound = SILC_ST_TOP_BOUND;
341   stack->depth = silc_st_backtrace(stack->stack);
342
343   silc_mutex_lock(lock);
344
345   stack->next = st_blocks;
346   stack->prev = NULL;
347   if (st_blocks)
348     ((SilcStBlock)st_blocks)->prev = stack;
349   st_blocks = stack;
350   st_blocks_count++;
351   st_num_malloc++;
352
353   silc_mutex_unlock(lock);
354
355   if (!pg)
356     *SILC_ST_GET_BOUND(stack, size) = SILC_ST_BOTTOM_BOUND;
357
358   return SILC_ST_GET_PTR(stack);
359 }
360
361 void *silc_st_calloc(size_t items, size_t size, const char *file, int line)
362 {
363   void *addr = (void *)silc_st_malloc(items * size, file, line);
364   if (addr)
365     memset(addr, 0, items * size);
366   return addr;
367 }
368
369 void *silc_st_realloc(void *ptr, size_t size, const char *file, int line)
370 {
371   SilcStBlock stack;
372
373   if (!ptr)
374     return silc_st_malloc(size, file, line);
375
376   stack = SILC_ST_GET_STACK(ptr);
377   if (!pg && stack->size >= size) {
378     /* Must update footer when the size changes */
379     if (stack->size != size)
380       *SILC_ST_GET_BOUND(stack, size) = SILC_ST_BOTTOM_BOUND;
381
382     stack->size = size;
383     return ptr;
384   } else {
385     void *addr = (void *)silc_st_malloc(size, file, line);
386     if (addr) {
387       memcpy(addr, ptr, size > stack->size ? stack->size : size);
388       silc_st_free(ptr, file, line);
389     }
390     return addr;
391   }
392 }
393
394 void silc_st_free(void *ptr, const char *file, int line)
395 {
396   SilcStBlock stack, s;
397
398   if (!ptr)
399     return;
400
401   /* Check for double free */
402   if (!memcmp((unsigned char *)ptr - sizeof(struct SilcStBlockStruct),
403               "\x47\x47\x47\x47", 4))
404     silc_st_abort(no_free ? ptr - sizeof(struct SilcStBlockStruct) : NULL,
405                   file, line, "SILC_MALLOC: double free: %p already freed\n",
406                   ptr - sizeof(struct SilcStBlockStruct));
407
408   stack = SILC_ST_GET_STACK(ptr);
409
410   silc_mutex_lock(lock);
411
412   /* Check if we have ever made this allocation */
413   for (s = st_blocks; s; s = s->next)
414     if (s == stack)
415       break;
416   if (s == NULL)
417     silc_st_abort(NULL, file, line,
418                   "SILC_MALLOC: %p was never allocated\n", stack);
419
420   if (!pg) {
421     /* Check for underflow */
422     if (stack->bound != SILC_ST_TOP_BOUND)
423       silc_st_abort(stack, file, line,
424                     "SILC_MALLOC: %p was written out of bounds (underflow)\n",
425                     stack);
426
427     /* Check for overflow */
428     if (*SILC_ST_GET_BOUND(stack, stack->size) != SILC_ST_BOTTOM_BOUND)
429       silc_st_abort(stack, file, line,
430                     "SILC_MALLOC: %p was written out of bounds (overflow)\n",
431                     stack);
432   }
433
434   if (stack->next)
435     stack->next->prev = stack->prev;
436   if (stack->prev)
437     stack->prev->next = stack->next;
438   else
439     st_blocks = stack->next;
440
441   st_blocks_count--;
442
443   silc_mutex_unlock(lock);
444
445   stack->free_file = file;
446   stack->free_line = line;
447
448   if (no_free) {
449     memset(stack, 0x47, 8);
450     return;
451   }
452
453   if (pg) {
454     ptr = SILC_ST_GET_PTR_ALIGN(stack, pg);
455     mprotect(ptr + SILC_ST_GET_SIZE_ALIGN(stack->size, pg), pg,
456              PROT_READ | PROT_WRITE);
457     memset(ptr, 0x47, SILC_ST_GET_SIZE_ALIGN(stack->size, pg));
458     free(ptr);
459   } else {
460     memset(stack, 0x47, SILC_ST_GET_SIZE(stack->size));
461     free(stack);
462   }
463 }
464
465 void *silc_st_memdup(const void *ptr, size_t size, const char *file, int line)
466 {
467   unsigned char *addr = (unsigned char *)silc_st_malloc(size + 1, file, line);
468   if (addr) {
469     memcpy((void *)addr, ptr, size);
470     addr[size] = '\0';
471   }
472   return (void *)addr;
473 }
474
475 void *silc_st_strdup(const char *string, const char *file, int line)
476 {
477   return silc_st_memdup(string, strlen(string), file, line);
478 }
479
480 /* Dumps the stack into file if there are leaks. */
481
482 void silc_st_dump(void)
483 {
484   SilcStBlock stack, s;
485   unsigned long leaks = 0, blocks, bytes;
486   FILE *fp = NULL;
487   char **syms, *cp;
488   int i;
489   SilcMutex l;
490
491   l = lock;
492   lock = NULL;
493   silc_mutex_free(l);
494
495   for (stack = st_blocks; stack; stack = stack->next) {
496     bytes = blocks = 0;
497
498     if (stack->dumpped)
499       continue;
500
501     leaks++;
502
503     if (!fp) {
504       fp = fopen("stacktrace.log", "wb");
505       if (!fp)
506         fp = stderr;
507     }
508
509     /* Get symbol names */
510     syms = silc_st_backtrace_symbols(stack->stack, stack->depth);
511
512     /* Find number of leaks and bytes leaked for this leak */
513     for (s = stack; s; s = s->next) {
514       if (s->file == stack->file && s->line == stack->line &&
515           s->depth == stack->depth &&
516           !memcmp(s->stack, stack->stack,
517                   (s->depth * sizeof(stack->stack[0])))) {
518         blocks++;
519         bytes += s->size;
520         s->dumpped = 1;
521       }
522     }
523
524     if (blocks) {
525       fprintf(fp, "<leak>%s:%d: #blocks=%lu, bytes=%lu\n",
526               stack->file, stack->line, blocks, bytes);
527       for (i = 0; i < stack->depth; i++) {
528         if (syms) {
529           cp = syms[i];
530           if (strchr(cp, '('))
531             cp = strchr(cp, '(') + 1;
532           else if (strchr(cp, ' '))
533             cp = strchr(cp, ' ') + 1;
534           if (strchr(cp, ')'))
535             *strchr(cp, ')') = ' ';
536           fprintf(fp, "\t%s\n", cp);
537         } else {
538           fprintf(fp, "\tpc=%p\n", stack->stack[i]);
539         }
540       }
541       fprintf(fp, "\n");
542       free(syms);
543     }
544   }
545
546   if (!leaks) {
547     fprintf(stderr, "\nNo memory leaks\n");
548   } else {
549     fprintf(stderr,
550             "-----------------------------------------\n"
551             "-----------------------------------------\n"
552             " Memory leaks dumped to 'stacktrace.log'\n"
553             " Leaks: %lu leaks, %lu blocks\n"
554             "-----------------------------------------\n"
555             "-----------------------------------------\n",
556             leaks, st_blocks_count);
557     fprintf(stderr, "Number of allocations: %lu\n", st_num_malloc);
558   }
559
560   if (fp && fp != stderr)
561     fclose(fp);
562 }
563
564 #endif /* SILC_STACKTRACE */