silcd: fixed memory leaks
[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 #include <execinfo.h>
74 #include <signal.h>
75 #include <malloc.h>
76 #include <sys/mman.h>
77
78 static void *st_blocks = NULL;
79 static unsigned long st_blocks_count = 0;
80 static unsigned long st_num_malloc = 0;
81 static SilcBool dump = FALSE;
82 static SilcBool no_free = FALSE;
83 static SilcBool dump_mem = FALSE;
84 static SilcUInt32 pg = 0;
85 static SilcMutex lock = NULL;
86
87 #ifdef SILC_DEBUG
88 #define SILC_ST_DEPTH 15
89 #else
90 #define SILC_ST_DEPTH 8
91 #endif /* SILC_DEBUG */
92
93 /* Memory block with stack trace */
94 typedef struct SilcStBlockStruct {
95   struct SilcStBlockStruct *next;
96   struct SilcStBlockStruct *prev;
97   void *stack[SILC_ST_DEPTH];   /* Stack trace */
98   const char *file;             /* Allocation file in program */
99   const char *free_file;        /* Free file in program */
100   SilcUInt32 size;              /* Allocated memory size */
101   SilcUInt16 line;              /* Allocation line in program */
102   SilcUInt16 free_line;         /* Free line in program */
103   SilcUInt16 depth;             /* Depth of stack trace */
104   SilcUInt16 dumpped;           /* Block is dumpped */
105   SilcUInt32 bound;             /* Top bound */
106 } *SilcStBlock;
107
108 #define SILC_ST_TOP_BOUND 0xfeed1977
109 #define SILC_ST_BOTTOM_BOUND 0x9152beef
110 #define SILC_ST_GET_SIZE(size) ((size + sizeof(struct SilcStBlockStruct) + 4))
111 #define SILC_ST_GET_STACK(p) ((SilcStBlock)(((unsigned char *)p) -      \
112                             sizeof(struct SilcStBlockStruct)))
113 #define SILC_ST_GET_PTR(p) (((unsigned char *)p) +              \
114                             sizeof(struct SilcStBlockStruct))
115 #define SILC_ST_GET_BOUND(p, size) (SilcUInt32 *)(((unsigned char *)p) + \
116                                     SILC_ST_GET_SIZE(size) - 4)
117
118 #define SILC_ST_ALIGN(bytes, align) (((bytes) + (align - 1)) & ~(align - 1))
119 #define SILC_ST_GET_SIZE_ALIGN(size, align) \
120   SILC_ST_ALIGN(SILC_ST_GET_SIZE(size) - 4, align)
121 #define SILC_ST_GET_PTR_ALIGN(stack, align)                               \
122   (((unsigned char *)stack) - (SILC_ST_GET_SIZE_ALIGN(stack->size, pg) -  \
123                                SILC_ST_GET_SIZE(stack->size)) - 4)
124 #define SILC_ST_GET_STACK_ALIGN(p, size, align)                             \
125   ((SilcStBlock)(((unsigned char *)p) + (SILC_ST_GET_SIZE_ALIGN(size, pg) - \
126                                          SILC_ST_GET_SIZE(size)) + 4))
127
128 #define silc_hexdump(ptr, size, file) \
129   silc_log_output_hexdump("", "", 0, ptr, size, "")
130
131 void silc_st_abort(SilcStBlock stack, const char *file, int line,
132                    char *fmt, ...)
133 {
134   void *bt[SILC_ST_DEPTH];
135   SilcUInt32 *bound;
136   va_list va;
137   int btc;
138
139   va_start(va, fmt);
140   vfprintf(stderr, fmt, va);
141   va_end(va);
142
143   fprintf(stderr, "----- BACKTRACE -----\n%s:%d:\n", file, line);
144   btc = backtrace(bt, SILC_ST_DEPTH);
145   backtrace_symbols_fd(bt, btc, 2);
146
147   if (stack) {
148     fprintf(stderr, "----- MEMORY TRACE -----\n");
149     if (stack->free_file)
150       fprintf(stderr, "Freed at: %s:%d\n", stack->free_file,
151               stack->free_line);
152     fprintf(stderr, "Originally allocated at:\n");
153     fprintf(stderr, "%s:%d:\n", stack->file, stack->line);
154     backtrace_symbols_fd(stack->stack, stack->depth, 2);
155     fflush(stderr);
156
157     if (dump_mem) {
158       fprintf(stderr, "----- MEMORY HEADER -----\n");
159       fprintf(stderr, "Header length: %lu, total length %lu\n",
160               sizeof(struct SilcStBlockStruct), SILC_ST_GET_SIZE(stack->size));
161       silc_hexdump((void *)stack, sizeof(struct SilcStBlockStruct), stderr);
162       fflush(stderr);
163       fprintf(stderr, "Header bound is: %p\n",
164               SILC_32_TO_PTR(stack->bound));
165       if (stack->bound != SILC_ST_TOP_BOUND) {
166         fprintf(stderr, "Header bound should be: %p\n",
167                 SILC_32_TO_PTR(SILC_ST_TOP_BOUND));
168         fprintf(stderr, "MEMORY IS CORRUPTED (UNDERFLOW)!\n");
169       }
170
171       fprintf(stderr, "----- USER MEMORY -----\n");
172       fprintf(stderr, "Length: %d\n", stack->size);
173       silc_hexdump(((unsigned char *)stack) +
174                     sizeof(struct SilcStBlockStruct), stack->size, stderr);
175       fflush(stderr);
176
177       fprintf(stderr, "----- MEMORY FOOTER -----\n");
178       bound = SILC_ST_GET_BOUND(stack, stack->size);
179       silc_hexdump((unsigned char *)bound, 4, stderr);
180       fprintf(stderr, "Footer bound is: %p\n", SILC_32_TO_PTR(*bound));
181       if (*bound != SILC_ST_BOTTOM_BOUND) {
182         fprintf(stderr, "Footer bound should be: %p\n",
183                 SILC_32_TO_PTR(SILC_ST_BOTTOM_BOUND));
184         fprintf(stderr, "MEMORY IS CORRUPTED (OVERFLOW)!\n");
185       }
186     }
187   }
188
189   fflush(stderr);
190
191   abort();
192 }
193
194 void silc_st_sigsegv(int sig, siginfo_t *si, void *context)
195 {
196   SilcStBlock orig, stack = (SilcStBlock)si->si_addr;
197
198   /* Make the page accessible again */
199   mprotect(si->si_addr, pg, PROT_READ | PROT_WRITE);
200
201   /* Get the original page from the violated page */
202   orig = (SilcStBlock)(((unsigned char *)si->si_addr) -
203                         SILC_ST_GET_SIZE_ALIGN(stack->size, pg));
204   stack = SILC_ST_GET_STACK_ALIGN(orig, stack->size, pg);
205
206   silc_st_abort(stack, __FILE__, __LINE__,
207                 "SILC_MALLOC: access violation (overflow)\n");
208 }
209
210 void silc_st_stacktrace_init(void)
211 {
212   const char *var;
213
214   atexit(silc_st_dump);
215   dump = TRUE;
216
217   var = getenv("SILC_MALLOC_NO_FREE");
218   if (var && *var == '1')
219     no_free = TRUE;
220
221   var = getenv("SILC_MALLOC_DUMP");
222   if (var && *var == '1')
223     dump_mem = TRUE;
224
225   var = getenv("SILC_MALLOC_PROTECT");
226   if (var && *var == '1') {
227     struct sigaction sa;
228
229     sa.sa_flags = SA_SIGINFO;
230     sa.sa_sigaction = silc_st_sigsegv;
231     sigemptyset(&sa.sa_mask);
232     sigaction(SIGSEGV, &sa, NULL);
233
234 #if defined(_SC_PAGESIZE)
235     pg = sysconf(_SC_PAGESIZE);
236 #elif defined(_SC_PAGE_SIZE)
237     pg = sysconf(_SC_PAGE_SIZE);
238 #else
239     pg = getpagesize();
240 #endif /* _SC_PAGESIZE */
241   }
242
243   /* Linux libc malloc check */
244   setenv("MALLOC_CHECK_", "3", 1);
245
246   /* NetBSD malloc check */
247   setenv("MALLOC_OPTIONS", "AJ", 1);
248
249   silc_mutex_alloc(&lock);
250 }
251
252 void *silc_st_malloc(size_t size, const char *file, int line)
253 {
254   SilcStBlock stack;
255
256   if (silc_unlikely(!dump))
257     silc_st_stacktrace_init();
258
259   if (pg) {
260     unsigned char *ptr;
261
262     if (posix_memalign((void *)&ptr, pg,
263                        SILC_ST_GET_SIZE_ALIGN(size, pg) + pg))
264       return NULL;
265
266     /* The inaccessible page too will include the allocation information
267        so that we can get it when access violation occurs in that page. */
268     stack = (SilcStBlock)(ptr + SILC_ST_GET_SIZE_ALIGN(size, pg));
269     stack->size = size;
270
271     /* Protect the page */
272     if (mprotect(stack, pg, PROT_NONE))
273       silc_st_abort(NULL, file, line, "SILC_MALLOC: mprotect() error: %s\n",
274                     errno == ENOMEM ? "Cannot allocate memory. \nYour program "
275                     "leaks memory, allocates too much or system \n"
276                     "is out of memory.  The SILC_MALLOC_PROTECT cannot "
277                     "be used." : strerror(errno));
278
279     /* Get the accessible page */
280     stack = SILC_ST_GET_STACK_ALIGN(ptr, size, pg);
281   } else {
282     stack = (SilcStBlock)malloc(SILC_ST_GET_SIZE(size));
283     if (!stack)
284       return NULL;
285   }
286
287   stack->dumpped = 0;
288   stack->file = file;
289   stack->free_file = NULL;
290   stack->line = line;
291   stack->size = size;
292   stack->bound = SILC_ST_TOP_BOUND;
293   stack->depth = backtrace(stack->stack, SILC_ST_DEPTH);
294
295   silc_mutex_lock(lock);
296
297   stack->next = st_blocks;
298   stack->prev = NULL;
299   if (st_blocks)
300     ((SilcStBlock)st_blocks)->prev = stack;
301   st_blocks = stack;
302   st_blocks_count++;
303   st_num_malloc++;
304
305   silc_mutex_unlock(lock);
306
307   if (!pg)
308     *SILC_ST_GET_BOUND(stack, size) = SILC_ST_BOTTOM_BOUND;
309
310   return SILC_ST_GET_PTR(stack);
311 }
312
313 void *silc_st_calloc(size_t items, size_t size, const char *file, int line)
314 {
315   void *addr = (void *)silc_st_malloc(items * size, file, line);
316   if (addr)
317     memset(addr, 0, items * size);
318   return addr;
319 }
320
321 void *silc_st_realloc(void *ptr, size_t size, const char *file, int line)
322 {
323   SilcStBlock stack;
324
325   if (!ptr)
326     return silc_st_malloc(size, file, line);
327
328   stack = SILC_ST_GET_STACK(ptr);
329   if (!pg && stack->size >= size) {
330     /* Must update footer when the size changes */
331     if (stack->size != size)
332       *SILC_ST_GET_BOUND(stack, size) = SILC_ST_BOTTOM_BOUND;
333
334     stack->size = size;
335     return ptr;
336   } else {
337     void *addr = (void *)silc_st_malloc(size, file, line);
338     if (addr) {
339       memcpy(addr, ptr, size > stack->size ? stack->size : size);
340       silc_st_free(ptr, file, line);
341     }
342     return addr;
343   }
344 }
345
346 void silc_st_free(void *ptr, const char *file, int line)
347 {
348   SilcStBlock stack, s;
349
350   if (!ptr)
351     return;
352
353   /* Check for double free */
354   if (!memcmp((unsigned char *)ptr - sizeof(struct SilcStBlockStruct),
355               "\x47\x47\x47\x47", 4))
356     silc_st_abort(no_free ? ptr - sizeof(struct SilcStBlockStruct) : NULL,
357                   file, line, "SILC_MALLOC: double free: %p already freed\n",
358                   ptr - sizeof(struct SilcStBlockStruct));
359
360   stack = SILC_ST_GET_STACK(ptr);
361
362   silc_mutex_lock(lock);
363
364   /* Check if we have ever made this allocation */
365   for (s = st_blocks; s; s = s->next)
366     if (s == stack)
367       break;
368   if (s == NULL)
369     silc_st_abort(NULL, file, line,
370                   "SILC_MALLOC: %p was never allocated\n", stack);
371
372   if (!pg) {
373     /* Check for underflow */
374     if (stack->bound != SILC_ST_TOP_BOUND)
375       silc_st_abort(stack, file, line,
376                     "SILC_MALLOC: %p was written out of bounds (underflow)\n",
377                     stack);
378
379     /* Check for overflow */
380     if (*SILC_ST_GET_BOUND(stack, stack->size) != SILC_ST_BOTTOM_BOUND)
381       silc_st_abort(stack, file, line,
382                     "SILC_MALLOC: %p was written out of bounds (overflow)\n",
383                     stack);
384   }
385
386   if (stack->next)
387     stack->next->prev = stack->prev;
388   if (stack->prev)
389     stack->prev->next = stack->next;
390   else
391     st_blocks = stack->next;
392
393   st_blocks_count--;
394
395   silc_mutex_unlock(lock);
396
397   stack->free_file = file;
398   stack->free_line = line;
399
400   if (no_free) {
401     memset(stack, 0x47, 8);
402     return;
403   }
404
405   if (pg) {
406     ptr = SILC_ST_GET_PTR_ALIGN(stack, pg);
407     mprotect(ptr + SILC_ST_GET_SIZE_ALIGN(stack->size, pg), pg,
408              PROT_READ | PROT_WRITE);
409     memset(ptr, 0x47, SILC_ST_GET_SIZE_ALIGN(stack->size, pg));
410     free(ptr);
411   } else {
412     memset(stack, 0x47, SILC_ST_GET_SIZE(stack->size));
413     free(stack);
414   }
415 }
416
417 void *silc_st_memdup(const void *ptr, size_t size, const char *file, int line)
418 {
419   unsigned char *addr = (unsigned char *)silc_st_malloc(size + 1, file, line);
420   if (addr) {
421     memcpy((void *)addr, ptr, size);
422     addr[size] = '\0';
423   }
424   return (void *)addr;
425 }
426
427 void *silc_st_strdup(const char *string, const char *file, int line)
428 {
429   return silc_st_memdup(string, strlen(string), file, line);
430 }
431
432 /* Dumps the stack into file if there are leaks. */
433
434 void silc_st_dump(void)
435 {
436   SilcStBlock stack, s;
437   unsigned long leaks = 0, blocks, bytes;
438   FILE *fp = NULL;
439   char **syms, *cp;
440   int i;
441   SilcMutex l;
442
443   l = lock;
444   lock = NULL;
445   silc_mutex_free(l);
446
447   for (stack = st_blocks; stack; stack = stack->next) {
448     bytes = blocks = 0;
449
450     if (stack->dumpped)
451       continue;
452
453     leaks++;
454
455     if (!fp) {
456       fp = fopen("stacktrace.log", "wb");
457       if (!fp)
458         fp = stderr;
459     }
460
461     /* Get symbol names */
462     syms = backtrace_symbols(stack->stack, stack->depth);
463
464     /* Find number of leaks and bytes leaked for this leak */
465     for (s = stack; s; s = s->next) {
466       if (s->file == stack->file && s->line == stack->line &&
467           s->depth == stack->depth &&
468           !memcmp(s->stack, stack->stack,
469                   (s->depth * sizeof(stack->stack[0])))) {
470         blocks++;
471         bytes += s->size;
472         s->dumpped = 1;
473       }
474     }
475
476     if (blocks) {
477       fprintf(fp, "<leak>%s:%d: #blocks=%lu, bytes=%lu\n",
478               stack->file, stack->line, blocks, bytes);
479       for (i = 0; i < stack->depth; i++) {
480         if (syms) {
481           cp = syms[i];
482           if (strchr(cp, '('))
483             cp = strchr(cp, '(') + 1;
484           else if (strchr(cp, ' '))
485             cp = strchr(cp, ' ') + 1;
486           if (strchr(cp, ')'))
487             *strchr(cp, ')') = ' ';
488           fprintf(fp, "\t%s\n", cp);
489         } else {
490           fprintf(fp, "\tpc=%p\n", stack->stack[i]);
491         }
492       }
493       fprintf(fp, "\n");
494       free(syms);
495     }
496   }
497
498   if (!leaks) {
499     fprintf(stderr, "\nNo memory leaks\n");
500   } else {
501     fprintf(stderr,
502             "-----------------------------------------\n"
503             "-----------------------------------------\n"
504             " Memory leaks dumped to 'stacktrace.log'\n"
505             " Leaks: %lu leaks, %lu blocks\n"
506             "-----------------------------------------\n"
507             "-----------------------------------------\n",
508             leaks, st_blocks_count);
509     fprintf(stderr, "Number of allocations: %lu\n", st_num_malloc);
510   }
511
512   if (fp && fp != stderr)
513     fclose(fp);
514 }
515
516 #endif /* SILC_STACKTRACE */