Added memory corruption checks to stacktrace.c
[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    The following environment variables can be used:
40
41    SILC_MALLOC_NO_FREE
42
43      When set to value 1, the program doesn't actually free any memory.
44      This provides more detailed information especially in case of double
45      free.  If the location of the double free cannot be located, by
46      setting this variable the program will show where the memory was
47      originally allocated and freed.
48
49    SILC_MALLOC_DUMP
50
51      When set to value 1, in case of fatal error, dumps the memory location,
52      if possible.  This can help see what the memory contains.
53
54    To work correctly this of course expects that code uses SILC memory
55    allocation and access routines.
56
57 */
58
59 #include "silcruntime.h"
60
61 #ifdef SILC_STACKTRACE
62 #include <execinfo.h>
63
64 static void *st_blocks = NULL;
65 static unsigned long st_blocks_count = 0;
66 static unsigned long st_num_malloc = 0;
67 static SilcBool dump = FALSE;
68 static SilcBool malloc_check = FALSE;
69 static SilcBool no_free = FALSE;
70 static SilcBool dump_mem = FALSE;
71 static SilcMutex lock = NULL;
72
73 #ifdef SILC_DEBUG
74 #define SILC_ST_DEPTH 15
75 #else
76 #define SILC_ST_DEPTH 8
77 #endif /* SILC_DEBUG */
78
79 /* Memory block with stack trace */
80 typedef struct SilcStBlockStruct {
81   struct SilcStBlockStruct *next;
82   struct SilcStBlockStruct *prev;
83   void *stack[SILC_ST_DEPTH];   /* Stack trace */
84   const char *file;             /* Allocation file in program */
85   const char *free_file;        /* Free file in program */
86   SilcUInt32 size;              /* Allocated memory size */
87   SilcUInt16 line;              /* Allocation line in program */
88   SilcUInt16 free_line;         /* Free line in program */
89   SilcUInt16 depth;             /* Depth of stack trace */
90   SilcUInt16 dumpped;           /* Block is dumpped */
91   SilcUInt32 bound;             /* Top bound */
92 } *SilcStBlock;
93
94 #define SILC_ST_TOP_BOUND 0xfeed1977
95 #define SILC_ST_BOTTOM_BOUND 0x9152beef
96 #define SILC_ST_GET_SIZE(size) ((size + sizeof(struct SilcStBlockStruct) + 4))
97 #define SILC_ST_GET_STACK(p) ((SilcStBlock)(((unsigned char *)p) -      \
98                             sizeof(struct SilcStBlockStruct)))
99 #define SILC_ST_GET_PTR(p) (((unsigned char *)p) +              \
100                             sizeof(struct SilcStBlockStruct))
101 #define SILC_ST_GET_BOUND(p, size) (SilcUInt32 *)(((unsigned char *)p) + \
102                                     SILC_ST_GET_SIZE(size) - 4)
103
104 void silc_st_abort(SilcStBlock stack, const char *file, int line,
105                    char *fmt, ...)
106 {
107   void *bt[SILC_ST_DEPTH];
108   SilcUInt32 *bound;
109   va_list va;
110   int btc;
111
112   va_start(va, fmt);
113   vfprintf(stderr, fmt, va);
114   va_end(va);
115
116   fprintf(stderr, "----- BACKTRACE -----\n%s:%d:\n", file, line);
117   btc = backtrace(bt, SILC_ST_DEPTH);
118   backtrace_symbols_fd(bt, btc, 2);
119
120   if (stack) {
121     fprintf(stderr, "----- MEMORY TRACE -----\n");
122     if (stack->free_file)
123       fprintf(stderr, "Freed at: %s:%d\n", stack->free_file,
124               stack->free_line);
125     fprintf(stderr, "Originally allocated at:\n");
126     fprintf(stderr, "%s:%d:\n", stack->file, stack->line);
127     backtrace_symbols_fd(stack->stack, stack->depth, 2);
128     fflush(stderr);
129
130     if (dump_mem) {
131       fprintf(stderr, "----- MEMORY HEADER -----\n");
132       fprintf(stderr, "Header length: %lu, total length %lu\n",
133               sizeof(struct SilcStBlockStruct), SILC_ST_GET_SIZE(stack->size));
134       silc_hexdump((void *)stack, sizeof(struct SilcStBlockStruct), stderr);
135       fflush(stderr);
136       fprintf(stderr, "Header bound is: %p\n",
137               SILC_32_TO_PTR(stack->bound));
138       if (stack->bound != SILC_ST_TOP_BOUND) {
139         fprintf(stderr, "Header bound should be: %p\n",
140                 SILC_32_TO_PTR(SILC_ST_TOP_BOUND));
141         fprintf(stderr, "MEMORY IS CORRUPTED (UNDERFLOW)!\n");
142       }
143
144       fprintf(stderr, "----- USER MEMORY -----\n");
145       fprintf(stderr, "Length: %d\n", stack->size);
146       silc_hexdump(((unsigned char *)stack) +
147                     sizeof(struct SilcStBlockStruct), stack->size, stderr);
148       fflush(stderr);
149
150       fprintf(stderr, "----- MEMORY FOOTER -----\n");
151       bound = SILC_ST_GET_BOUND(stack, stack->size);
152       silc_hexdump((unsigned char *)bound, 4, stderr);
153       fprintf(stderr, "Footer bound is: %p\n", SILC_32_TO_PTR(*bound));
154       if (*bound != SILC_ST_BOTTOM_BOUND) {
155         fprintf(stderr, "Footer bound should be: %p\n",
156                 SILC_32_TO_PTR(SILC_ST_BOTTOM_BOUND));
157         fprintf(stderr, "MEMORY IS CORRUPTED (OVERFLOW)!\n");
158       }
159     }
160   }
161
162   fflush(stderr);
163
164   abort();
165 }
166
167 void silc_st_stacktrace(SilcStBlock stack)
168 {
169   if (!dump) {
170     atexit(silc_st_dump);
171     dump = TRUE;
172   }
173
174   if (!malloc_check) {
175     const char *var;
176
177     var = silc_getenv("SILC_MALLOC_NO_FREE");
178     if (var && *var == '1')
179       no_free = TRUE;
180
181     var = silc_getenv("SILC_MALLOC_DUMP");
182     if (var && *var == '1')
183       dump_mem = TRUE;
184
185     /* Linux libc malloc check */
186     silc_setenv("MALLOC_CHECK_", "3");
187
188     /* NetBSD malloc check */
189     silc_setenv("MALLOC_OPTIONS", "AJ");
190
191     malloc_check = TRUE;
192
193     silc_mutex_alloc(&lock);
194   }
195
196   /* Get backtrace */
197   stack->depth = backtrace(stack->stack, SILC_ST_DEPTH);
198 }
199
200 void *silc_st_malloc(size_t size, const char *file, int line)
201 {
202   SilcStBlock stack = (SilcStBlock)malloc(SILC_ST_GET_SIZE(size));
203
204   if (!stack)
205     return NULL;
206
207   stack->dumpped = 0;
208   stack->file = file;
209   stack->free_file = NULL;
210   stack->line = line;
211   stack->size = size;
212   stack->bound = SILC_ST_TOP_BOUND;
213   silc_st_stacktrace(stack);
214
215   silc_mutex_lock(lock);
216
217   stack->next = st_blocks;
218   stack->prev = NULL;
219   if (st_blocks)
220     ((SilcStBlock)st_blocks)->prev = stack;
221   st_blocks = stack;
222   st_blocks_count++;
223   st_num_malloc++;
224
225   silc_mutex_unlock(lock);
226
227   *SILC_ST_GET_BOUND(stack, size) = SILC_ST_BOTTOM_BOUND;
228
229   return SILC_ST_GET_PTR(stack);
230 }
231
232 void *silc_st_calloc(size_t items, size_t size, const char *file, int line)
233 {
234   void *addr = (void *)silc_st_malloc(items * size, file, line);
235   memset(addr, 0, items * size);
236   return addr;
237 }
238
239 void *silc_st_realloc(void *ptr, size_t size, const char *file, int line)
240 {
241   SilcStBlock stack;
242
243   if (!ptr)
244     return silc_st_malloc(size, file, line);
245
246   stack = SILC_ST_GET_STACK(ptr);
247   if (stack->size >= size) {
248     /* Must update footer when the size changes */
249     if (stack->size != size)
250       *SILC_ST_GET_BOUND(stack, size) = SILC_ST_BOTTOM_BOUND;
251
252     stack->size = size;
253     return ptr;
254   } else {
255     void *addr = (void *)silc_st_malloc(size, file, line);
256     memcpy(addr, ptr, stack->size);
257     silc_st_free(ptr, file, line);
258     return addr;
259   }
260 }
261
262 void silc_st_free(void *ptr, const char *file, int line)
263 {
264   SilcStBlock stack, s;
265
266   if (!ptr)
267     return;
268
269   /* Check for double free */
270   if (!memcmp((unsigned char *)ptr - sizeof(struct SilcStBlockStruct),
271               "\x47\x47\x47\x47", 4))
272     silc_st_abort(no_free ? ptr - sizeof(struct SilcStBlockStruct) : NULL,
273                   file, line, "SILC_MALLOC: double free: %p already freed\n",
274                   ptr - sizeof(struct SilcStBlockStruct));
275
276   stack = SILC_ST_GET_STACK(ptr);
277
278   silc_mutex_lock(lock);
279
280   /* Check if we have ever made this allocation */
281   for (s = st_blocks; s; s = s->next)
282     if (s == stack)
283       break;
284   if (s == NULL)
285     silc_st_abort(NULL, file, line,
286                   "SILC_MALLOC: %p was never allocated\n", stack);
287
288   /* Check for underflow */
289   if (stack->bound != SILC_ST_TOP_BOUND)
290     silc_st_abort(stack, file, line,
291                   "SILC_MALLOC: %p was written out of bounds (underflow)\n",
292                   stack);
293
294   /* Check for overflow */
295   if (*SILC_ST_GET_BOUND(stack, stack->size) != SILC_ST_BOTTOM_BOUND)
296     silc_st_abort(stack, file, line,
297                   "SILC_MALLOC: %p was written out of bounds (overflow)\n",
298                   stack);
299
300   if (stack->next)
301     stack->next->prev = stack->prev;
302   if (stack->prev)
303     stack->prev->next = stack->next;
304   else
305     st_blocks = stack->next;
306
307   st_blocks_count--;
308
309   silc_mutex_unlock(lock);
310
311   stack->free_file = file;
312   stack->free_line = line;
313
314   if (no_free) {
315     memset(stack, 0x47, 8);
316     return;
317   }
318
319   memset(stack, 0x47, SILC_ST_GET_SIZE(stack->size));
320
321   free(stack);
322 }
323
324 void *silc_st_memdup(const void *ptr, size_t size, const char *file, int line)
325 {
326   unsigned char *addr = (unsigned char *)silc_st_malloc(size + 1, file, line);
327   memcpy((void *)addr, ptr, size);
328   addr[size] = '\0';
329   return (void *)addr;
330 }
331
332 void *silc_st_strdup(const char *string, const char *file, int line)
333 {
334   return silc_st_memdup(string, strlen(string), file, line);
335 }
336
337 /* Dumps the stack into file if there are leaks. */
338
339 void silc_st_dump(void)
340 {
341   SilcStBlock stack, s;
342   unsigned long leaks = 0, blocks, bytes;
343   FILE *fp = NULL;
344   char **syms, *cp;
345   int i;
346   SilcMutex l;
347
348   l = lock;
349   lock = NULL;
350   silc_mutex_free(l);
351
352   for (stack = st_blocks; stack; stack = stack->next) {
353     bytes = blocks = 0;
354
355     if (stack->dumpped)
356       continue;
357
358     leaks++;
359
360     if (!fp) {
361       fp = fopen("stacktrace.log", "wb");
362       if (!fp)
363         fp = stderr;
364     }
365
366     /* Get symbol names */
367     syms = backtrace_symbols(stack->stack, stack->depth);
368
369     /* Find number of leaks and bytes leaked for this leak */
370     for (s = stack; s; s = s->next) {
371       if (s->file == stack->file && s->line == stack->line &&
372           s->depth == stack->depth &&
373           !memcmp(s->stack, stack->stack,
374                   (s->depth * sizeof(stack->stack[0])))) {
375         blocks++;
376         bytes += s->size;
377         s->dumpped = 1;
378       }
379     }
380
381     if (blocks) {
382       fprintf(fp, "<leak>%s:%d: #blocks=%lu, bytes=%lu\n",
383               stack->file, stack->line, blocks, bytes);
384       for (i = 0; i < stack->depth; i++) {
385         if (syms) {
386           cp = syms[i];
387           if (strchr(cp, '('))
388             cp = strchr(cp, '(') + 1;
389           else if (strchr(cp, ' '))
390             cp = strchr(cp, ' ') + 1;
391           if (strchr(cp, ')'))
392             *strchr(cp, ')') = ' ';
393           fprintf(fp, "\t%s\n", cp);
394         } else {
395           fprintf(fp, "\tpc=%p\n", stack->stack[i]);
396         }
397       }
398       fprintf(fp, "\n");
399       free(syms);
400     }
401   }
402
403   if (!leaks) {
404     fprintf(stderr, "\nNo memory leaks\n");
405   } else {
406     fprintf(stderr,
407             "-----------------------------------------\n"
408             "-----------------------------------------\n"
409             " Memory leaks dumped to 'stacktrace.log'\n"
410             " Leaks: %lu leaks, %lu blocks\n"
411             "-----------------------------------------\n"
412             "-----------------------------------------\n",
413             leaks, st_blocks_count);
414     fprintf(stderr, "Number of allocations: %lu\n", st_num_malloc);
415   }
416
417   if (fp && fp != stderr)
418     fclose(fp);
419 }
420
421 #endif /* SILC_STACKTRACE */