5 Author: Pekka Riikonen <priikone@silcnet.org>
7 Copyright (C) 2002 - 2008 Pekka Riikonen
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.
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.
20 /* This file implements memory leak checker and basic memory corruption
21 and double free checker. It is multi-thread safe. It does the
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.
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.
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.
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
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.
43 The following environment variables can be used:
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.
53 SILC_MALLOC_NO_ALLOCATED
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.
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.
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.
71 To work correctly this of course expects that code uses SILC memory
72 allocation and access routines.
76 #include "silcruntime.h"
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;
94 #define SILC_ST_DEPTH 15
96 #define SILC_ST_DEPTH 8
97 #endif /* SILC_DEBUG */
99 /* Memory block with stack trace */
100 typedef struct SilcStBlockStruct {
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 */
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)
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))
133 SilcCompareValue silc_st_compare(void *val1, void *val2, void *context)
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;
142 void silc_st_abort(SilcStBlock stack, const char *file, int line,
145 void *bt[SILC_ST_DEPTH];
151 vfprintf(stderr, fmt, va);
154 fprintf(stderr, "----- BACKTRACE -----\n%s:%d:\n", file, line);
155 btc = backtrace(bt, SILC_ST_DEPTH);
156 backtrace_symbols_fd(bt, btc, 2);
159 fprintf(stderr, "----- MEMORY TRACE -----\n");
160 if (stack->free_file)
161 fprintf(stderr, "Freed at: %s:%d\n", stack->free_file,
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);
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);
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");
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);
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");
205 void silc_st_sigsegv(int sig, siginfo_t *si, void *context)
207 SilcStBlock orig, stack = (SilcStBlock)si->si_addr;
209 /* Make the page accessible again */
210 mprotect(si->si_addr, pg, PROT_READ | PROT_WRITE);
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);
217 silc_st_abort(stack, __FILE__, __LINE__,
218 "SILC_MALLOC: access violation (overflow)\n");
221 void silc_st_memtrace_init(void)
225 silc_tree_init(st_blocks, SILC_TREE_AVL, silc_st_compare, NULL,
226 silc_offsetof(struct SilcStBlockStruct, h), FALSE);
228 atexit(silc_st_dump);
231 var = silc_getenv("SILC_MALLOC_NO_FREE");
232 if (var && *var == '1')
235 var = silc_getenv("SILC_MALLOC_NO_ALLOCATED");
236 if (var && *var == '1')
239 var = silc_getenv("SILC_MALLOC_DUMP");
240 if (var && *var == '1')
243 var = silc_getenv("SILC_MALLOC_PROTECT");
244 if (var && *var == '1') {
247 sa.sa_flags = SA_SIGINFO;
248 sa.sa_sigaction = silc_st_sigsegv;
249 sigemptyset(&sa.sa_mask);
250 sigaction(SIGSEGV, &sa, NULL);
252 #if defined(_SC_PAGESIZE)
253 pg = sysconf(_SC_PAGESIZE);
254 #elif defined(_SC_PAGE_SIZE)
255 pg = sysconf(_SC_PAGE_SIZE);
258 #endif /* _SC_PAGESIZE */
261 /* Linux libc malloc check */
262 silc_setenv("MALLOC_CHECK_", "3");
264 /* NetBSD malloc check */
265 silc_setenv("MALLOC_OPTIONS", "AJ");
267 silc_mutex_alloc(&lock);
270 void *silc_st_malloc(size_t size, const char *file, int line)
274 if (silc_unlikely(!dump))
275 silc_st_memtrace_init();
280 if (posix_memalign((void *)&ptr, pg,
281 SILC_ST_GET_SIZE_ALIGN(size, pg) + pg))
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));
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));
296 /* Get the accessible page */
297 stack = SILC_ST_GET_STACK_ALIGN(ptr, size, pg);
299 stack = (SilcStBlock)malloc(SILC_ST_GET_SIZE(size));
304 memset(&stack->h, 0, sizeof(stack->h));
307 stack->free_file = NULL;
310 stack->bound = SILC_ST_TOP_BOUND;
311 stack->depth = backtrace(stack->stack, SILC_ST_DEPTH);
313 silc_mutex_lock(lock);
314 silc_tree_add(st_blocks, stack);
316 silc_mutex_unlock(lock);
319 *SILC_ST_GET_BOUND(stack, size) = SILC_ST_BOTTOM_BOUND;
321 return SILC_ST_GET_PTR(stack);
324 void *silc_st_calloc(size_t items, size_t size, const char *file, int line)
326 void *addr = (void *)silc_st_malloc(items * size, file, line);
328 memset(addr, 0, items * size);
332 void *silc_st_realloc(void *ptr, size_t size, const char *file, int line)
337 return silc_st_malloc(size, file, line);
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;
348 void *addr = (void *)silc_st_malloc(size, file, line);
350 memcpy(addr, ptr, size > stack->size ? stack->size : size);
351 silc_st_free(ptr, file, line);
357 void silc_st_free(void *ptr, const char *file, int line)
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));
371 stack = SILC_ST_GET_STACK(ptr);
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",
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",
387 silc_mutex_lock(lock);
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);
394 if (!silc_tree_del(st_blocks, stack))
395 silc_st_abort(NULL, file, line,
396 "SILC_MALLOC: %p was never allocated\n", stack);
398 silc_mutex_unlock(lock);
400 stack->free_file = file;
401 stack->free_line = line;
404 memset(stack, 0x47, 8);
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));
415 memset(stack, 0x47, SILC_ST_GET_SIZE(stack->size));
420 void *silc_st_memdup(const void *ptr, size_t size, const char *file, int line)
422 unsigned char *addr = (unsigned char *)silc_st_malloc(size + 1, file, line);
424 memcpy((void *)addr, ptr, size);
430 void *silc_st_strdup(const char *string, const char *file, int line)
432 return silc_st_memdup(string, strlen(string), file, line);
435 /* Dumps the stack into file if there are leaks. */
437 void silc_st_dump(void)
439 SilcStBlock stack, s;
440 unsigned long leaks = 0, blocks, bytes;
450 for (stack = silc_tree_enumerate(st_blocks, NULL); stack != NULL;
451 stack = silc_tree_enumerate(st_blocks, stack)) {
460 fp = fopen("memtrace.log", "wb");
465 /* Get symbol names */
466 syms = backtrace_symbols(stack->stack, stack->depth);
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])))) {
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++) {
488 cp = strchr(cp, '(') + 1;
489 else if (strchr(cp, ' '))
490 cp = strchr(cp, ' ') + 1;
492 *strchr(cp, ')') = ' ';
493 fprintf(fp, "\t%s\n", cp);
495 fprintf(fp, "\tpc=%p\n", stack->stack[i]);
504 fprintf(stderr, "\nNo memory leaks\n");
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);
517 if (fp && fp != stderr)
521 #endif /* SILC_MEMTRACE */