2 * Copyright (C) 2006-2008 Martin Willi
3 * Hochschule fuer Technik Rapperswil
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21 #endif /* HAVE_DLADDR */
28 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #include <arpa/inet.h>
39 # include <execinfo.h>
40 #endif /* HAVE_BACKTRACE */
42 #include "leak_detective.h"
47 typedef struct private_leak_detective_t private_leak_detective_t
;
50 * private data of leak_detective
52 struct private_leak_detective_t
{
57 leak_detective_t
public;
61 * Magic value which helps to detect memory corruption. Yummy!
63 #define MEMORY_HEADER_MAGIC 0x7ac0be11
66 * Magic written to tail of allocation
68 #define MEMORY_TAIL_MAGIC 0xcafebabe
71 * Pattern which is filled in memory before freeing it
73 #define MEMORY_FREE_PATTERN 0xFF
76 * Pattern which is filled in newly allocated memory
78 #define MEMORY_ALLOC_PATTERN 0xEE
81 static void install_hooks(void);
82 static void uninstall_hooks(void);
83 static void *malloc_hook(size_t, const void *);
84 static void *realloc_hook(void *, size_t, const void *);
85 static void free_hook(void*, const void *);
87 void *(*old_malloc_hook
)(size_t, const void *);
88 void *(*old_realloc_hook
)(void *, size_t, const void *);
89 void (*old_free_hook
)(void*, const void *);
91 static u_int count_malloc
= 0;
92 static u_int count_free
= 0;
93 static u_int count_realloc
= 0;
95 typedef struct memory_header_t memory_header_t
;
96 typedef struct memory_tail_t memory_tail_t
;
99 * Header which is prepended to each allocated memory block
101 struct memory_header_t
{
104 * Number of bytes following after the header
109 * Stack frames at the time of allocation
111 void *stack_frames
[STACK_FRAMES_COUNT
];
114 * Number of stacks frames obtained in stack_frames
116 int stack_frame_count
;
119 * Pointer to previous entry in linked list
121 memory_header_t
*previous
;
124 * Pointer to next entry in linked list
126 memory_header_t
*next
;
129 * magic bytes to detect bad free or heap underflow, MEMORY_HEADER_MAGIC
133 }__attribute__((__packed__
));
136 * tail appended to each allocated memory block
138 struct memory_tail_t
{
141 * Magic bytes to detect heap overflow, MEMORY_TAIL_MAGIC
145 }__attribute__((__packed__
));
148 * first mem header is just a dummy to chain
149 * the others on it...
151 static memory_header_t first_header
= {
152 magic
: MEMORY_HEADER_MAGIC
,
154 stack_frame_count
: 0,
160 * are the hooks currently installed?
162 static bool installed
= FALSE
;
165 * log stack frames queried by backtrace()
166 * TODO: Dump symbols of static functions. This could be done with
167 * the addr2line utility or the GNU BFD Library...
169 static void log_stack_frames(void **stack_frames
, int stack_frame_count
)
171 #ifdef HAVE_BACKTRACE
175 strings
= backtrace_symbols(stack_frames
, stack_frame_count
);
177 fprintf(stderr
, " dumping %d stack frame addresses:\n", stack_frame_count
);
178 for (i
= 0; i
< stack_frame_count
; i
++)
183 if (dladdr(stack_frames
[i
], &info
))
188 void *ptr
= stack_frames
[i
];
190 if (strstr(info
.dli_fname
, ".so"))
192 ptr
= (void*)(stack_frames
[i
] - info
.dli_fbase
);
194 snprintf(cmd
, sizeof(cmd
), "addr2line -e %s %p", info
.dli_fname
, ptr
);
197 fprintf(stderr
, " \e[33m%s\e[0m @ %p (\e[31m%s\e[0m+0x%x) [%p]\n",
198 info
.dli_fname
, info
.dli_fbase
, info
.dli_sname
,
199 stack_frames
[i
] - info
.dli_saddr
, stack_frames
[i
]);
203 fprintf(stderr
, " \e[33m%s\e[0m @ %p [%p]\n", info
.dli_fname
,
204 info
.dli_fbase
, stack_frames
[i
]);
206 fprintf(stderr
, " -> \e[32m");
207 output
= popen(cmd
, "r");
213 if (c
== '\n' || c
== EOF
)
222 #endif /* HAVE_DLADDR */
223 fprintf(stderr
, " %s\n", strings
[i
]);
226 fprintf(stderr
, "\n\e[0m");
228 #endif /* HAVE_DLADDR */
231 #endif /* HAVE_BACKTRACE */
235 * Leak report white list
237 * List of functions using static allocation buffers or should be suppressed
238 * otherwise on leak report.
240 char *whitelist
[] = {
243 "pthread_setspecific",
244 /* glibc functions */
255 "register_printf_function",
260 /* ignore dlopen, as we do not dlclose to get proper leak reports */
262 /* mysql functions */
263 "mysql_init_character_set",
266 /* fastcgi library */
269 "xmlInitCharEncodingHandlers",
277 * check if a stack frame contains functions listed above
279 static bool is_whitelisted(void **stack_frames
, int stack_frame_count
)
284 for (i
=0; i
< stack_frame_count
; i
++)
288 if (dladdr(stack_frames
[i
], &info
) && info
.dli_sname
)
290 for (j
= 0; j
< sizeof(whitelist
)/sizeof(char*); j
++)
292 if (streq(info
.dli_sname
, whitelist
[j
]))
299 #endif /* HAVE_DLADDR */
304 * Report leaks at library destruction
308 memory_header_t
*hdr
;
309 int leaks
= 0, whitelisted
= 0;
311 for (hdr
= first_header
.next
; hdr
!= NULL
; hdr
= hdr
->next
)
313 if (is_whitelisted(hdr
->stack_frames
, hdr
->stack_frame_count
))
319 fprintf(stderr
, "Leak (%d bytes at %p):\n", hdr
->bytes
, hdr
+ 1);
320 /* skip the first frame, contains leak detective logic */
321 log_stack_frames(hdr
->stack_frames
+ 1, hdr
->stack_frame_count
- 1);
329 fprintf(stderr
, "No leaks detected");
332 fprintf(stderr
, "One leak detected");
335 fprintf(stderr
, "%d leaks detected", leaks
);
338 fprintf(stderr
, ", %d suppressed by whitelist\n", whitelisted
);
342 * Installs the malloc hooks, enables leak detection
344 static void install_hooks()
348 old_malloc_hook
= __malloc_hook
;
349 old_realloc_hook
= __realloc_hook
;
350 old_free_hook
= __free_hook
;
351 __malloc_hook
= malloc_hook
;
352 __realloc_hook
= realloc_hook
;
353 __free_hook
= free_hook
;
359 * Uninstalls the malloc hooks, disables leak detection
361 static void uninstall_hooks()
365 __malloc_hook
= old_malloc_hook
;
366 __free_hook
= old_free_hook
;
367 __realloc_hook
= old_realloc_hook
;
373 * Hook function for malloc()
375 void *malloc_hook(size_t bytes
, const void *caller
)
377 memory_header_t
*hdr
;
379 pthread_t thread_id
= pthread_self();
381 struct sched_param oldparams
, params
;
383 pthread_getschedparam(thread_id
, &oldpolicy
, &oldparams
);
385 params
.__sched_priority
= sched_get_priority_max(SCHED_FIFO
);
386 pthread_setschedparam(thread_id
, SCHED_FIFO
, ¶ms
);
390 hdr
= malloc(sizeof(memory_header_t
) + bytes
+ sizeof(memory_tail_t
));
391 tail
= ((void*)hdr
) + bytes
+ sizeof(memory_header_t
);
392 /* set to something which causes crashes */
393 memset(hdr
, MEMORY_ALLOC_PATTERN
,
394 sizeof(memory_header_t
) + bytes
+ sizeof(memory_tail_t
));
396 hdr
->magic
= MEMORY_HEADER_MAGIC
;
398 hdr
->stack_frame_count
= backtrace(hdr
->stack_frames
, STACK_FRAMES_COUNT
);
399 tail
->magic
= MEMORY_TAIL_MAGIC
;
402 /* insert at the beginning of the list */
403 hdr
->next
= first_header
.next
;
406 hdr
->next
->previous
= hdr
;
408 hdr
->previous
= &first_header
;
409 first_header
.next
= hdr
;
411 pthread_setschedparam(thread_id
, oldpolicy
, &oldparams
);
417 * Hook function for free()
419 void free_hook(void *ptr
, const void *caller
)
421 void *stack_frames
[STACK_FRAMES_COUNT
];
422 int stack_frame_count
;
423 memory_header_t
*hdr
;
425 pthread_t thread_id
= pthread_self();
427 struct sched_param oldparams
, params
;
429 /* allow freeing of NULL */
434 hdr
= ptr
- sizeof(memory_header_t
);
435 tail
= ptr
+ hdr
->bytes
;
437 pthread_getschedparam(thread_id
, &oldpolicy
, &oldparams
);
439 params
.__sched_priority
= sched_get_priority_max(SCHED_FIFO
);
440 pthread_setschedparam(thread_id
, SCHED_FIFO
, ¶ms
);
444 if (hdr
->magic
!= MEMORY_HEADER_MAGIC
||
445 tail
->magic
!= MEMORY_TAIL_MAGIC
)
447 fprintf(stderr
, "freeing invalid memory (%p): "
448 "header magic 0x%x, tail magic 0x%x:\n",
449 ptr
, hdr
->magic
, tail
->magic
);
450 stack_frame_count
= backtrace(stack_frames
, STACK_FRAMES_COUNT
);
451 log_stack_frames(stack_frames
, stack_frame_count
);
455 /* remove item from list */
458 hdr
->next
->previous
= hdr
->previous
;
460 hdr
->previous
->next
= hdr
->next
;
462 /* clear MAGIC, set mem to something remarkable */
463 memset(hdr
, MEMORY_FREE_PATTERN
, hdr
->bytes
+ sizeof(memory_header_t
));
469 pthread_setschedparam(thread_id
, oldpolicy
, &oldparams
);
473 * Hook function for realloc()
475 void *realloc_hook(void *old
, size_t bytes
, const void *caller
)
477 memory_header_t
*hdr
;
478 void *stack_frames
[STACK_FRAMES_COUNT
];
479 int stack_frame_count
;
481 pthread_t thread_id
= pthread_self();
483 struct sched_param oldparams
, params
;
485 /* allow reallocation of NULL */
488 return malloc_hook(bytes
, caller
);
491 hdr
= old
- sizeof(memory_header_t
);
492 tail
= old
+ hdr
->bytes
;
494 pthread_getschedparam(thread_id
, &oldpolicy
, &oldparams
);
496 params
.__sched_priority
= sched_get_priority_max(SCHED_FIFO
);
497 pthread_setschedparam(thread_id
, SCHED_FIFO
, ¶ms
);
501 if (hdr
->magic
!= MEMORY_HEADER_MAGIC
||
502 tail
->magic
!= MEMORY_TAIL_MAGIC
)
504 fprintf(stderr
, "reallocating invalid memory (%p): "
505 "header magic 0x%x, tail magic 0x%x:\n",
506 old
, hdr
->magic
, tail
->magic
);
507 stack_frame_count
= backtrace(stack_frames
, STACK_FRAMES_COUNT
);
508 log_stack_frames(stack_frames
, stack_frame_count
);
510 /* clear tail magic, allocate, set tail magic */
511 memset(&tail
->magic
, MEMORY_ALLOC_PATTERN
, sizeof(tail
->magic
));
512 hdr
= realloc(hdr
, sizeof(memory_header_t
) + bytes
+ sizeof(memory_tail_t
));
513 tail
= ((void*)hdr
) + bytes
+ sizeof(memory_header_t
);
514 tail
->magic
= MEMORY_TAIL_MAGIC
;
516 /* update statistics */
518 hdr
->stack_frame_count
= backtrace(hdr
->stack_frames
, STACK_FRAMES_COUNT
);
520 /* update header of linked list neighbours */
523 hdr
->next
->previous
= hdr
;
525 hdr
->previous
->next
= hdr
;
527 pthread_setschedparam(thread_id
, oldpolicy
, &oldparams
);
532 * Implementation of leak_detective_t.destroy
534 static void destroy(private_leak_detective_t
*this)
547 leak_detective_t
*leak_detective_create()
549 private_leak_detective_t
*this = malloc_thing(private_leak_detective_t
);
551 this->public.destroy
= (void(*)(leak_detective_t
*))destroy
;
553 if (getenv("LEAK_DETECTIVE_DISABLE") == NULL
)
557 return &this->public;