b979c40a74371a139d1c9f2d8b2f505c8de64088
[strongswan.git] / src / libstrongswan / utils / leak_detective.c
1 /*
2 * Copyright (C) 2006-2008 Martin Willi
3 * Hochschule fuer Technik Rapperswil
4 *
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>.
9 *
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
13 * for more details.
14 *
15 * $Id$
16 */
17
18 #ifdef HAVE_DLADDR
19 # define _GNU_SOURCE
20 # include <dlfcn.h>
21 #endif /* HAVE_DLADDR */
22
23 #include <stddef.h>
24 #include <string.h>
25 #include <stdio.h>
26 #include <malloc.h>
27 #include <signal.h>
28 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #include <arpa/inet.h>
31 #include <dlfcn.h>
32 #include <unistd.h>
33 #include <syslog.h>
34 #include <pthread.h>
35 #include <netdb.h>
36 #include <printf.h>
37 #include <locale.h>
38 #ifdef HAVE_BACKTRACE
39 # include <execinfo.h>
40 #endif /* HAVE_BACKTRACE */
41
42 #include "leak_detective.h"
43
44 #include <library.h>
45 #include <debug.h>
46
47 typedef struct private_leak_detective_t private_leak_detective_t;
48
49 /**
50 * private data of leak_detective
51 */
52 struct private_leak_detective_t {
53
54 /**
55 * public functions
56 */
57 leak_detective_t public;
58 };
59
60 /**
61 * Magic value which helps to detect memory corruption. Yummy!
62 */
63 #define MEMORY_HEADER_MAGIC 0x7ac0be11
64
65 /**
66 * Magic written to tail of allocation
67 */
68 #define MEMORY_TAIL_MAGIC 0xcafebabe
69
70 /**
71 * Pattern which is filled in memory before freeing it
72 */
73 #define MEMORY_FREE_PATTERN 0xFF
74
75 /**
76 * Pattern which is filled in newly allocated memory
77 */
78 #define MEMORY_ALLOC_PATTERN 0xEE
79
80
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 *);
86
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 *);
90
91 static u_int count_malloc = 0;
92 static u_int count_free = 0;
93 static u_int count_realloc = 0;
94
95 typedef struct memory_header_t memory_header_t;
96 typedef struct memory_tail_t memory_tail_t;
97
98 /**
99 * Header which is prepended to each allocated memory block
100 */
101 struct memory_header_t {
102
103 /**
104 * Number of bytes following after the header
105 */
106 u_int bytes;
107
108 /**
109 * Stack frames at the time of allocation
110 */
111 void *stack_frames[STACK_FRAMES_COUNT];
112
113 /**
114 * Number of stacks frames obtained in stack_frames
115 */
116 int stack_frame_count;
117
118 /**
119 * Pointer to previous entry in linked list
120 */
121 memory_header_t *previous;
122
123 /**
124 * Pointer to next entry in linked list
125 */
126 memory_header_t *next;
127
128 /**
129 * magic bytes to detect bad free or heap underflow, MEMORY_HEADER_MAGIC
130 */
131 u_int32_t magic;
132
133 }__attribute__((__packed__));
134
135 /**
136 * tail appended to each allocated memory block
137 */
138 struct memory_tail_t {
139
140 /**
141 * Magic bytes to detect heap overflow, MEMORY_TAIL_MAGIC
142 */
143 u_int32_t magic;
144
145 }__attribute__((__packed__));
146
147 /**
148 * first mem header is just a dummy to chain
149 * the others on it...
150 */
151 static memory_header_t first_header = {
152 magic: MEMORY_HEADER_MAGIC,
153 bytes: 0,
154 stack_frame_count: 0,
155 previous: NULL,
156 next: NULL
157 };
158
159 /**
160 * are the hooks currently installed?
161 */
162 static bool installed = FALSE;
163
164 /**
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...
168 */
169 static void log_stack_frames(void **stack_frames, int stack_frame_count)
170 {
171 #ifdef HAVE_BACKTRACE
172 size_t i;
173 char **strings;
174
175 strings = backtrace_symbols(stack_frames, stack_frame_count);
176
177 fprintf(stderr, " dumping %d stack frame addresses:\n", stack_frame_count);
178 for (i = 0; i < stack_frame_count; i++)
179 {
180 #ifdef HAVE_DLADDR
181 Dl_info info;
182
183 if (dladdr(stack_frames[i], &info))
184 {
185 char cmd[1024];
186 FILE *output;
187 char c;
188 void *ptr = stack_frames[i];
189
190 if (strstr(info.dli_fname, ".so"))
191 {
192 ptr = (void*)(stack_frames[i] - info.dli_fbase);
193 }
194 snprintf(cmd, sizeof(cmd), "addr2line -e %s %p", info.dli_fname, ptr);
195 if (info.dli_sname)
196 {
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]);
200 }
201 else
202 {
203 fprintf(stderr, " \e[33m%s\e[0m @ %p [%p]\n", info.dli_fname,
204 info.dli_fbase, stack_frames[i]);
205 }
206 fprintf(stderr, " -> \e[32m");
207 output = popen(cmd, "r");
208 if (output)
209 {
210 while (TRUE)
211 {
212 c = getc(output);
213 if (c == '\n' || c == EOF)
214 {
215 break;
216 }
217 fputc(c, stderr);
218 }
219 }
220 else
221 {
222 #endif /* HAVE_DLADDR */
223 fprintf(stderr, " %s\n", strings[i]);
224 #ifdef HAVE_DLADDR
225 }
226 fprintf(stderr, "\n\e[0m");
227 }
228 #endif /* HAVE_DLADDR */
229 }
230 free (strings);
231 #endif /* HAVE_BACKTRACE */
232 }
233
234 /**
235 * Leak report white list
236 *
237 * List of functions using static allocation buffers or should be suppressed
238 * otherwise on leak report.
239 */
240 char *whitelist[] = {
241 /* pthread stuff */
242 "pthread_create",
243 "pthread_setspecific",
244 /* glibc functions */
245 "mktime",
246 "tzset",
247 "inet_ntoa",
248 "strerror",
249 "getprotobynumber",
250 "getservbyport",
251 "getservbyname",
252 "register_printf_function",
253 "syslog",
254 "vsyslog",
255 "getaddrinfo",
256 "setlocale",
257 /* ignore dlopen, as we do not dlclose to get proper leak reports */
258 "dlopen",
259 /* mysql functions */
260 "mysql_init_character_set",
261 "init_client_errs",
262 "my_thread_init",
263 /* fastcgi library */
264 "FCGX_Init",
265 /* libxml */
266 "xmlInitCharEncodingHandlers",
267 "xmlInitParser",
268 "xmlInitParserCtxt",
269 /* ClearSilver */
270 "nerr_init",
271 };
272
273 /**
274 * check if a stack frame contains functions listed above
275 */
276 static bool is_whitelisted(void **stack_frames, int stack_frame_count)
277 {
278 int i, j;
279
280 #ifdef HAVE_DLADDR
281 for (i=0; i< stack_frame_count; i++)
282 {
283 Dl_info info;
284
285 if (dladdr(stack_frames[i], &info) && info.dli_sname)
286 {
287 for (j = 0; j < sizeof(whitelist)/sizeof(char*); j++)
288 {
289 if (streq(info.dli_sname, whitelist[j]))
290 {
291 return TRUE;
292 }
293 }
294 }
295 }
296 #endif /* HAVE_DLADDR */
297 return FALSE;
298 }
299
300 /**
301 * Report leaks at library destruction
302 */
303 void report_leaks()
304 {
305 memory_header_t *hdr;
306 int leaks = 0, whitelisted = 0;
307
308 for (hdr = first_header.next; hdr != NULL; hdr = hdr->next)
309 {
310 if (is_whitelisted(hdr->stack_frames, hdr->stack_frame_count))
311 {
312 whitelisted++;
313 }
314 else
315 {
316 fprintf(stderr, "Leak (%d bytes at %p):\n", hdr->bytes, hdr + 1);
317 /* skip the first frame, contains leak detective logic */
318 log_stack_frames(hdr->stack_frames + 1, hdr->stack_frame_count - 1);
319 leaks++;
320 }
321 }
322
323 switch (leaks)
324 {
325 case 0:
326 fprintf(stderr, "No leaks detected");
327 break;
328 case 1:
329 fprintf(stderr, "One leak detected");
330 break;
331 default:
332 fprintf(stderr, "%d leaks detected", leaks);
333 break;
334 }
335 fprintf(stderr, ", %d suppressed by whitelist\n", whitelisted);
336 }
337
338 /**
339 * Installs the malloc hooks, enables leak detection
340 */
341 static void install_hooks()
342 {
343 if (!installed)
344 {
345 old_malloc_hook = __malloc_hook;
346 old_realloc_hook = __realloc_hook;
347 old_free_hook = __free_hook;
348 __malloc_hook = malloc_hook;
349 __realloc_hook = realloc_hook;
350 __free_hook = free_hook;
351 installed = TRUE;
352 }
353 }
354
355 /**
356 * Uninstalls the malloc hooks, disables leak detection
357 */
358 static void uninstall_hooks()
359 {
360 if (installed)
361 {
362 __malloc_hook = old_malloc_hook;
363 __free_hook = old_free_hook;
364 __realloc_hook = old_realloc_hook;
365 installed = FALSE;
366 }
367 }
368
369 /**
370 * Hook function for malloc()
371 */
372 void *malloc_hook(size_t bytes, const void *caller)
373 {
374 memory_header_t *hdr;
375 memory_tail_t *tail;
376 pthread_t thread_id = pthread_self();
377 int oldpolicy;
378 struct sched_param oldparams, params;
379
380 pthread_getschedparam(thread_id, &oldpolicy, &oldparams);
381
382 params.__sched_priority = sched_get_priority_max(SCHED_FIFO);
383 pthread_setschedparam(thread_id, SCHED_FIFO, &params);
384
385 count_malloc++;
386 uninstall_hooks();
387 hdr = malloc(sizeof(memory_header_t) + bytes + sizeof(memory_tail_t));
388 tail = ((void*)hdr) + bytes + sizeof(memory_header_t);
389 /* set to something which causes crashes */
390 memset(hdr, MEMORY_ALLOC_PATTERN,
391 sizeof(memory_header_t) + bytes + sizeof(memory_tail_t));
392
393 hdr->magic = MEMORY_HEADER_MAGIC;
394 hdr->bytes = bytes;
395 hdr->stack_frame_count = backtrace(hdr->stack_frames, STACK_FRAMES_COUNT);
396 tail->magic = MEMORY_TAIL_MAGIC;
397 install_hooks();
398
399 /* insert at the beginning of the list */
400 hdr->next = first_header.next;
401 if (hdr->next)
402 {
403 hdr->next->previous = hdr;
404 }
405 hdr->previous = &first_header;
406 first_header.next = hdr;
407
408 pthread_setschedparam(thread_id, oldpolicy, &oldparams);
409
410 return hdr + 1;
411 }
412
413 /**
414 * Hook function for free()
415 */
416 void free_hook(void *ptr, const void *caller)
417 {
418 void *stack_frames[STACK_FRAMES_COUNT];
419 int stack_frame_count;
420 memory_header_t *hdr;
421 memory_tail_t *tail;
422 pthread_t thread_id = pthread_self();
423 int oldpolicy;
424 struct sched_param oldparams, params;
425
426 /* allow freeing of NULL */
427 if (ptr == NULL)
428 {
429 return;
430 }
431 hdr = ptr - sizeof(memory_header_t);
432 tail = ptr + hdr->bytes;
433
434 pthread_getschedparam(thread_id, &oldpolicy, &oldparams);
435
436 params.__sched_priority = sched_get_priority_max(SCHED_FIFO);
437 pthread_setschedparam(thread_id, SCHED_FIFO, &params);
438
439 count_free++;
440 uninstall_hooks();
441 if (hdr->magic != MEMORY_HEADER_MAGIC ||
442 tail->magic != MEMORY_TAIL_MAGIC)
443 {
444 fprintf(stderr, "freeing invalid memory (%p): "
445 "header magic 0x%x, tail magic 0x%x:\n",
446 ptr, hdr->magic, tail->magic);
447 stack_frame_count = backtrace(stack_frames, STACK_FRAMES_COUNT);
448 log_stack_frames(stack_frames, stack_frame_count);
449 }
450 else
451 {
452 /* remove item from list */
453 if (hdr->next)
454 {
455 hdr->next->previous = hdr->previous;
456 }
457 hdr->previous->next = hdr->next;
458
459 /* clear MAGIC, set mem to something remarkable */
460 memset(hdr, MEMORY_FREE_PATTERN, hdr->bytes + sizeof(memory_header_t));
461
462 free(hdr);
463 }
464
465 install_hooks();
466 pthread_setschedparam(thread_id, oldpolicy, &oldparams);
467 }
468
469 /**
470 * Hook function for realloc()
471 */
472 void *realloc_hook(void *old, size_t bytes, const void *caller)
473 {
474 memory_header_t *hdr;
475 void *stack_frames[STACK_FRAMES_COUNT];
476 int stack_frame_count;
477 memory_tail_t *tail;
478 pthread_t thread_id = pthread_self();
479 int oldpolicy;
480 struct sched_param oldparams, params;
481
482 /* allow reallocation of NULL */
483 if (old == NULL)
484 {
485 return malloc_hook(bytes, caller);
486 }
487
488 hdr = old - sizeof(memory_header_t);
489 tail = old + hdr->bytes;
490
491 pthread_getschedparam(thread_id, &oldpolicy, &oldparams);
492
493 params.__sched_priority = sched_get_priority_max(SCHED_FIFO);
494 pthread_setschedparam(thread_id, SCHED_FIFO, &params);
495
496 count_realloc++;
497 uninstall_hooks();
498 if (hdr->magic != MEMORY_HEADER_MAGIC ||
499 tail->magic != MEMORY_TAIL_MAGIC)
500 {
501 fprintf(stderr, "reallocating invalid memory (%p): "
502 "header magic 0x%x, tail magic 0x%x:\n",
503 old, hdr->magic, tail->magic);
504 stack_frame_count = backtrace(stack_frames, STACK_FRAMES_COUNT);
505 log_stack_frames(stack_frames, stack_frame_count);
506 }
507 /* clear tail magic, allocate, set tail magic */
508 memset(&tail->magic, MEMORY_ALLOC_PATTERN, sizeof(tail->magic));
509 hdr = realloc(hdr, sizeof(memory_header_t) + bytes + sizeof(memory_tail_t));
510 tail = ((void*)hdr) + bytes + sizeof(memory_header_t);
511 tail->magic = MEMORY_TAIL_MAGIC;
512
513 /* update statistics */
514 hdr->bytes = bytes;
515 hdr->stack_frame_count = backtrace(hdr->stack_frames, STACK_FRAMES_COUNT);
516
517 /* update header of linked list neighbours */
518 if (hdr->next)
519 {
520 hdr->next->previous = hdr;
521 }
522 hdr->previous->next = hdr;
523 install_hooks();
524 pthread_setschedparam(thread_id, oldpolicy, &oldparams);
525 return hdr + 1;
526 }
527
528 /**
529 * Implementation of leak_detective_t.destroy
530 */
531 static void destroy(private_leak_detective_t *this)
532 {
533 if (installed)
534 {
535 uninstall_hooks();
536 report_leaks();
537 }
538 free(this);
539 }
540
541 /*
542 * see header file
543 */
544 leak_detective_t *leak_detective_create()
545 {
546 private_leak_detective_t *this = malloc_thing(private_leak_detective_t);
547
548 this->public.destroy = (void(*)(leak_detective_t*))destroy;
549
550 if (getenv("LEAK_DETECTIVE_DISABLE") == NULL)
551 {
552 install_hooks();
553 }
554 return &this->public;
555 }
556