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