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