6bf4d63cdc2358c56518150f37b22152b2a59244
[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
16 #define _GNU_SOURCE
17 #include <sched.h>
18 #include <stddef.h>
19 #include <string.h>
20 #include <stdio.h>
21 #include <malloc.h>
22 #include <signal.h>
23 #include <sys/socket.h>
24 #include <netinet/in.h>
25 #include <arpa/inet.h>
26 #include <unistd.h>
27 #include <syslog.h>
28 #include <pthread.h>
29 #include <netdb.h>
30 #include <locale.h>
31
32 #include "leak_detective.h"
33
34 #include <library.h>
35 #include <utils/debug.h>
36 #include <utils/backtrace.h>
37 #include <collections/hashtable.h>
38
39 typedef struct private_leak_detective_t private_leak_detective_t;
40
41 /**
42 * private data of leak_detective
43 */
44 struct private_leak_detective_t {
45
46 /**
47 * public functions
48 */
49 leak_detective_t public;
50 };
51
52 /**
53 * Magic value which helps to detect memory corruption. Yummy!
54 */
55 #define MEMORY_HEADER_MAGIC 0x7ac0be11
56
57 /**
58 * Magic written to tail of allocation
59 */
60 #define MEMORY_TAIL_MAGIC 0xcafebabe
61
62 /**
63 * Pattern which is filled in memory before freeing it
64 */
65 #define MEMORY_FREE_PATTERN 0xFF
66
67 /**
68 * Pattern which is filled in newly allocated memory
69 */
70 #define MEMORY_ALLOC_PATTERN 0xEE
71
72
73 static void install_hooks(void);
74 static void uninstall_hooks(void);
75 static void *malloc_hook(size_t, const void *);
76 static void *realloc_hook(void *, size_t, const void *);
77 static void free_hook(void*, const void *);
78
79 void *(*old_malloc_hook)(size_t, const void *);
80 void *(*old_realloc_hook)(void *, size_t, const void *);
81 void (*old_free_hook)(void*, const void *);
82
83 static u_int count_malloc = 0;
84 static u_int count_free = 0;
85 static u_int count_realloc = 0;
86
87 typedef struct memory_header_t memory_header_t;
88 typedef struct memory_tail_t memory_tail_t;
89
90 /**
91 * Header which is prepended to each allocated memory block
92 */
93 struct memory_header_t {
94
95 /**
96 * Pointer to previous entry in linked list
97 */
98 memory_header_t *previous;
99
100 /**
101 * Pointer to next entry in linked list
102 */
103 memory_header_t *next;
104
105 /**
106 * backtrace taken during (re-)allocation
107 */
108 backtrace_t *backtrace;
109
110 /**
111 * Number of bytes following after the header
112 */
113 u_int32_t bytes;
114
115 /**
116 * magic bytes to detect bad free or heap underflow, MEMORY_HEADER_MAGIC
117 */
118 u_int32_t magic;
119
120 }__attribute__((__packed__));
121
122 /**
123 * tail appended to each allocated memory block
124 */
125 struct memory_tail_t {
126
127 /**
128 * Magic bytes to detect heap overflow, MEMORY_TAIL_MAGIC
129 */
130 u_int32_t magic;
131
132 }__attribute__((__packed__));
133
134 /**
135 * first mem header is just a dummy to chain
136 * the others on it...
137 */
138 static memory_header_t first_header = {
139 magic: MEMORY_HEADER_MAGIC,
140 bytes: 0,
141 backtrace: NULL,
142 previous: NULL,
143 next: NULL
144 };
145
146 /**
147 * are the hooks currently installed?
148 */
149 static bool installed = FALSE;
150
151 /**
152 * Installs the malloc hooks, enables leak detection
153 */
154 static void install_hooks()
155 {
156 if (!installed)
157 {
158 old_malloc_hook = __malloc_hook;
159 old_realloc_hook = __realloc_hook;
160 old_free_hook = __free_hook;
161 __malloc_hook = malloc_hook;
162 __realloc_hook = realloc_hook;
163 __free_hook = free_hook;
164 installed = TRUE;
165 }
166 }
167
168 /**
169 * Uninstalls the malloc hooks, disables leak detection
170 */
171 static void uninstall_hooks()
172 {
173 if (installed)
174 {
175 __malloc_hook = old_malloc_hook;
176 __free_hook = old_free_hook;
177 __realloc_hook = old_realloc_hook;
178 installed = FALSE;
179 }
180 }
181
182 /**
183 * Leak report white list
184 *
185 * List of functions using static allocation buffers or should be suppressed
186 * otherwise on leak report.
187 */
188 char *whitelist[] = {
189 /* backtraces, including own */
190 "backtrace_create",
191 "safe_strerror",
192 /* pthread stuff */
193 "pthread_create",
194 "pthread_setspecific",
195 "__pthread_setspecific",
196 /* glibc functions */
197 "mktime",
198 "ctime",
199 "__gmtime_r",
200 "localtime_r",
201 "tzset",
202 "time_printf_hook",
203 "inet_ntoa",
204 "strerror",
205 "getprotobyname",
206 "getprotobynumber",
207 "getservbyport",
208 "getservbyname",
209 "gethostbyname",
210 "gethostbyname2",
211 "gethostbyname_r",
212 "gethostbyname2_r",
213 "getnetbyname",
214 "getpwnam_r",
215 "getgrnam_r",
216 "register_printf_function",
217 "register_printf_specifier",
218 "syslog",
219 "vsyslog",
220 "__syslog_chk",
221 "__vsyslog_chk",
222 "getaddrinfo",
223 "setlocale",
224 "getpass",
225 "getpwent_r",
226 "setpwent",
227 "endpwent",
228 "getspnam_r",
229 "getpwuid_r",
230 "initgroups",
231 /* ignore dlopen, as we do not dlclose to get proper leak reports */
232 "dlopen",
233 "dlerror",
234 "dlclose",
235 "dlsym",
236 /* mysql functions */
237 "mysql_init_character_set",
238 "init_client_errs",
239 "my_thread_init",
240 /* fastcgi library */
241 "FCGX_Init",
242 /* libxml */
243 "xmlInitCharEncodingHandlers",
244 "xmlInitParser",
245 "xmlInitParserCtxt",
246 /* libcurl */
247 "Curl_client_write",
248 /* ClearSilver */
249 "nerr_init",
250 /* OpenSSL */
251 "RSA_new_method",
252 "DH_new_method",
253 "ENGINE_load_builtin_engines",
254 "OPENSSL_config",
255 "ecdsa_check",
256 "ERR_put_error",
257 /* libgcrypt */
258 "gcry_control",
259 "gcry_check_version",
260 "gcry_randomize",
261 "gcry_create_nonce",
262 /* NSPR */
263 "PR_CallOnce",
264 /* libapr */
265 "apr_pool_create_ex",
266 /* glib */
267 "g_type_init_with_debug_flags",
268 "g_type_register_static",
269 "g_type_class_ref",
270 "g_type_create_instance",
271 "g_type_add_interface_static",
272 "g_type_interface_add_prerequisite",
273 "g_socket_connection_factory_lookup_type",
274 /* libgpg */
275 "gpg_err_init",
276 /* gnutls */
277 "gnutls_global_init",
278 };
279
280
281 /**
282 * Hashtable hash function
283 */
284 static u_int hash(backtrace_t *key)
285 {
286 enumerator_t *enumerator;
287 void *addr;
288 u_int hash = 0;
289
290 enumerator = key->create_frame_enumerator(key);
291 while (enumerator->enumerate(enumerator, &addr))
292 {
293 hash = chunk_hash_inc(chunk_from_thing(addr), hash);
294 }
295 enumerator->destroy(enumerator);
296
297 return hash;
298 }
299
300 /**
301 * Hashtable equals function
302 */
303 static bool equals(backtrace_t *a, backtrace_t *b)
304 {
305 return a->equals(a, b);
306 }
307
308 /**
309 * Summarize and print backtraces
310 */
311 static int print_traces(private_leak_detective_t *this,
312 FILE *out, int thresh, bool detailed, int *whitelisted)
313 {
314 int leaks = 0;
315 memory_header_t *hdr;
316 enumerator_t *enumerator;
317 hashtable_t *entries;
318 struct {
319 /** associated backtrace */
320 backtrace_t *backtrace;
321 /** total size of all allocations */
322 size_t bytes;
323 /** number of allocations */
324 u_int count;
325 } *entry;
326
327 uninstall_hooks();
328
329 entries = hashtable_create((hashtable_hash_t)hash,
330 (hashtable_equals_t)equals, 1024);
331 for (hdr = first_header.next; hdr != NULL; hdr = hdr->next)
332 {
333 if (whitelisted &&
334 hdr->backtrace->contains_function(hdr->backtrace,
335 whitelist, countof(whitelist)))
336 {
337 (*whitelisted)++;
338 continue;
339 }
340 entry = entries->get(entries, hdr->backtrace);
341 if (entry)
342 {
343 entry->bytes += hdr->bytes;
344 entry->count++;
345 }
346 else
347 {
348 INIT(entry,
349 .backtrace = hdr->backtrace,
350 .bytes = hdr->bytes,
351 .count = 1,
352 );
353 entries->put(entries, hdr->backtrace, entry);
354 }
355 leaks++;
356 }
357 enumerator = entries->create_enumerator(entries);
358 while (enumerator->enumerate(enumerator, NULL, &entry))
359 {
360 if (!thresh || entry->bytes >= thresh)
361 {
362 fprintf(out, "%d bytes total, %d allocations, %d bytes average:\n",
363 entry->bytes, entry->count, entry->bytes / entry->count);
364 entry->backtrace->log(entry->backtrace, out, detailed);
365 }
366 free(entry);
367 }
368 enumerator->destroy(enumerator);
369 entries->destroy(entries);
370
371 install_hooks();
372 return leaks;
373 }
374
375 METHOD(leak_detective_t, report, void,
376 private_leak_detective_t *this, bool detailed)
377 {
378 if (lib->leak_detective)
379 {
380 int leaks = 0, whitelisted = 0;
381
382 leaks = print_traces(this, stderr, 0, detailed, &whitelisted);
383 switch (leaks)
384 {
385 case 0:
386 fprintf(stderr, "No leaks detected");
387 break;
388 case 1:
389 fprintf(stderr, "One leak detected");
390 break;
391 default:
392 fprintf(stderr, "%d leaks detected", leaks);
393 break;
394 }
395 fprintf(stderr, ", %d suppressed by whitelist\n", whitelisted);
396 }
397 else
398 {
399 fprintf(stderr, "Leak detective disabled\n");
400 }
401 }
402
403 METHOD(leak_detective_t, set_state, bool,
404 private_leak_detective_t *this, bool enable)
405 {
406 static struct sched_param oldparams;
407 static int oldpolicy;
408 struct sched_param params;
409 pthread_t thread_id;
410
411 if (enable == installed)
412 {
413 return installed;
414 }
415 thread_id = pthread_self();
416 if (enable)
417 {
418 install_hooks();
419 pthread_setschedparam(thread_id, oldpolicy, &oldparams);
420 }
421 else
422 {
423 pthread_getschedparam(thread_id, &oldpolicy, &oldparams);
424 params.__sched_priority = sched_get_priority_max(SCHED_FIFO);
425 pthread_setschedparam(thread_id, SCHED_FIFO, &params);
426 uninstall_hooks();
427 }
428 installed = enable;
429 return !installed;
430 }
431
432 METHOD(leak_detective_t, usage, void,
433 private_leak_detective_t *this, FILE *out)
434 {
435 int oldpolicy, thresh;
436 bool detailed;
437 pthread_t thread_id = pthread_self();
438 struct sched_param oldparams, params;
439
440 thresh = lib->settings->get_int(lib->settings,
441 "libstrongswan.leak_detective.usage_threshold", 10240);
442 detailed = lib->settings->get_bool(lib->settings,
443 "libstrongswan.leak_detective.detailed", TRUE);
444
445 pthread_getschedparam(thread_id, &oldpolicy, &oldparams);
446 params.__sched_priority = sched_get_priority_max(SCHED_FIFO);
447 pthread_setschedparam(thread_id, SCHED_FIFO, &params);
448
449 print_traces(this, out, thresh, detailed, NULL);
450
451 pthread_setschedparam(thread_id, oldpolicy, &oldparams);
452 }
453
454 /**
455 * Hook function for malloc()
456 */
457 void *malloc_hook(size_t bytes, const void *caller)
458 {
459 memory_header_t *hdr;
460 memory_tail_t *tail;
461 pthread_t thread_id = pthread_self();
462 int oldpolicy;
463 struct sched_param oldparams, params;
464
465 pthread_getschedparam(thread_id, &oldpolicy, &oldparams);
466
467 params.__sched_priority = sched_get_priority_max(SCHED_FIFO);
468 pthread_setschedparam(thread_id, SCHED_FIFO, &params);
469
470 count_malloc++;
471 uninstall_hooks();
472 hdr = malloc(sizeof(memory_header_t) + bytes + sizeof(memory_tail_t));
473 tail = ((void*)hdr) + bytes + sizeof(memory_header_t);
474 /* set to something which causes crashes */
475 memset(hdr, MEMORY_ALLOC_PATTERN,
476 sizeof(memory_header_t) + bytes + sizeof(memory_tail_t));
477
478 hdr->magic = MEMORY_HEADER_MAGIC;
479 hdr->bytes = bytes;
480 hdr->backtrace = backtrace_create(2);
481 tail->magic = MEMORY_TAIL_MAGIC;
482 install_hooks();
483
484 /* insert at the beginning of the list */
485 hdr->next = first_header.next;
486 if (hdr->next)
487 {
488 hdr->next->previous = hdr;
489 }
490 hdr->previous = &first_header;
491 first_header.next = hdr;
492
493 pthread_setschedparam(thread_id, oldpolicy, &oldparams);
494
495 return hdr + 1;
496 }
497
498 /**
499 * Hook function for free()
500 */
501 void free_hook(void *ptr, const void *caller)
502 {
503 memory_header_t *hdr, *current;
504 memory_tail_t *tail;
505 backtrace_t *backtrace;
506 pthread_t thread_id = pthread_self();
507 int oldpolicy;
508 struct sched_param oldparams, params;
509 bool found = FALSE;
510
511 /* allow freeing of NULL */
512 if (ptr == NULL)
513 {
514 return;
515 }
516 hdr = ptr - sizeof(memory_header_t);
517 tail = ptr + hdr->bytes;
518
519 pthread_getschedparam(thread_id, &oldpolicy, &oldparams);
520
521 params.__sched_priority = sched_get_priority_max(SCHED_FIFO);
522 pthread_setschedparam(thread_id, SCHED_FIFO, &params);
523
524 count_free++;
525 uninstall_hooks();
526 if (hdr->magic != MEMORY_HEADER_MAGIC ||
527 tail->magic != MEMORY_TAIL_MAGIC)
528 {
529 for (current = &first_header; current != NULL; current = current->next)
530 {
531 if (current == hdr)
532 {
533 found = TRUE;
534 break;
535 }
536 }
537 if (found)
538 {
539 /* memory was allocated by our hooks but is corrupted */
540 fprintf(stderr, "freeing corrupted memory (%p): "
541 "header magic 0x%x, tail magic 0x%x:\n",
542 ptr, hdr->magic, tail->magic);
543 }
544 else
545 {
546 /* memory was not allocated by our hooks */
547 fprintf(stderr, "freeing invalid memory (%p)", ptr);
548 }
549 backtrace = backtrace_create(2);
550 backtrace->log(backtrace, stderr, TRUE);
551 backtrace->destroy(backtrace);
552 }
553 else
554 {
555 /* remove item from list */
556 if (hdr->next)
557 {
558 hdr->next->previous = hdr->previous;
559 }
560 hdr->previous->next = hdr->next;
561 hdr->backtrace->destroy(hdr->backtrace);
562
563 /* clear MAGIC, set mem to something remarkable */
564 memset(hdr, MEMORY_FREE_PATTERN,
565 sizeof(memory_header_t) + hdr->bytes + sizeof(memory_tail_t));
566
567 free(hdr);
568 }
569
570 install_hooks();
571 pthread_setschedparam(thread_id, oldpolicy, &oldparams);
572 }
573
574 /**
575 * Hook function for realloc()
576 */
577 void *realloc_hook(void *old, size_t bytes, const void *caller)
578 {
579 memory_header_t *hdr;
580 memory_tail_t *tail;
581 backtrace_t *backtrace;
582 pthread_t thread_id = pthread_self();
583 int oldpolicy;
584 struct sched_param oldparams, params;
585
586 /* allow reallocation of NULL */
587 if (old == NULL)
588 {
589 return malloc_hook(bytes, caller);
590 }
591
592 hdr = old - sizeof(memory_header_t);
593 tail = old + hdr->bytes;
594
595 pthread_getschedparam(thread_id, &oldpolicy, &oldparams);
596
597 params.__sched_priority = sched_get_priority_max(SCHED_FIFO);
598 pthread_setschedparam(thread_id, SCHED_FIFO, &params);
599
600 count_realloc++;
601 uninstall_hooks();
602 if (hdr->magic != MEMORY_HEADER_MAGIC ||
603 tail->magic != MEMORY_TAIL_MAGIC)
604 {
605 fprintf(stderr, "reallocating invalid memory (%p):\n"
606 "header magic 0x%x:\n", old, hdr->magic);
607 backtrace = backtrace_create(2);
608 backtrace->log(backtrace, stderr, TRUE);
609 backtrace->destroy(backtrace);
610 }
611 else
612 {
613 /* clear tail magic, allocate, set tail magic */
614 memset(&tail->magic, MEMORY_ALLOC_PATTERN, sizeof(tail->magic));
615 }
616 hdr = realloc(hdr, sizeof(memory_header_t) + bytes + sizeof(memory_tail_t));
617 tail = ((void*)hdr) + bytes + sizeof(memory_header_t);
618 tail->magic = MEMORY_TAIL_MAGIC;
619
620 /* update statistics */
621 hdr->bytes = bytes;
622 hdr->backtrace->destroy(hdr->backtrace);
623 hdr->backtrace = backtrace_create(2);
624
625 /* update header of linked list neighbours */
626 if (hdr->next)
627 {
628 hdr->next->previous = hdr;
629 }
630 hdr->previous->next = hdr;
631 install_hooks();
632 pthread_setschedparam(thread_id, oldpolicy, &oldparams);
633 return hdr + 1;
634 }
635
636 METHOD(leak_detective_t, destroy, void,
637 private_leak_detective_t *this)
638 {
639 if (installed)
640 {
641 uninstall_hooks();
642 }
643 free(this);
644 }
645
646 /*
647 * see header file
648 */
649 leak_detective_t *leak_detective_create()
650 {
651 private_leak_detective_t *this;
652
653 INIT(this,
654 .public = {
655 .report = _report,
656 .usage = _usage,
657 .set_state = _set_state,
658 .destroy = _destroy,
659 },
660 );
661
662 if (getenv("LEAK_DETECTIVE_DISABLE") == NULL)
663 {
664 cpu_set_t mask;
665
666 CPU_ZERO(&mask);
667 CPU_SET(0, &mask);
668
669 if (sched_setaffinity(0, sizeof(cpu_set_t), &mask) != 0)
670 {
671 fprintf(stderr, "setting CPU affinity failed: %m");
672 }
673
674 install_hooks();
675 }
676 return &this->public;
677 }
678