replaced mutex in leak detective with thread scheduling
[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 static u_int count_malloc = 0;
88 static u_int count_free = 0;
89 static u_int count_realloc = 0;
90
91 typedef struct memory_header_t memory_header_t;
92 typedef struct memory_tail_t memory_tail_t;
93
94 /**
95 * Header which is prepended to each allocated memory block
96 */
97 struct memory_header_t {
98
99 /**
100 * Number of bytes following after the header
101 */
102 u_int bytes;
103
104 /**
105 * Stack frames at the time of allocation
106 */
107 void *stack_frames[STACK_FRAMES_COUNT];
108
109 /**
110 * Number of stacks frames obtained in stack_frames
111 */
112 int stack_frame_count;
113
114 /**
115 * Pointer to previous entry in linked list
116 */
117 memory_header_t *previous;
118
119 /**
120 * Pointer to next entry in linked list
121 */
122 memory_header_t *next;
123
124 /**
125 * magic bytes to detect bad free or heap underflow, MEMORY_HEADER_MAGIC
126 */
127 u_int32_t magic;
128
129 }__attribute__((__packed__));
130
131 /**
132 * tail appended to each allocated memory block
133 */
134 struct memory_tail_t {
135
136 /**
137 * Magic bytes to detect heap overflow, MEMORY_TAIL_MAGIC
138 */
139 u_int32_t magic;
140
141 }__attribute__((__packed__));
142
143 /**
144 * first mem header is just a dummy to chain
145 * the others on it...
146 */
147 static memory_header_t first_header = {
148 magic: MEMORY_HEADER_MAGIC,
149 bytes: 0,
150 stack_frame_count: 0,
151 previous: NULL,
152 next: NULL
153 };
154
155 /**
156 * standard hooks, used to temparily remove hooking
157 */
158 static void *old_malloc_hook, *old_realloc_hook, *old_free_hook;
159
160 /**
161 * are the hooks currently installed?
162 */
163 static bool installed = FALSE;
164
165 /**
166 * log stack frames queried by backtrace()
167 * TODO: Dump symbols of static functions. This could be done with
168 * the addr2line utility or the GNU BFD Library...
169 */
170 static void log_stack_frames(void **stack_frames, int stack_frame_count)
171 {
172 #ifdef HAVE_BACKTRACE
173 char **strings;
174 size_t i;
175
176 strings = backtrace_symbols(stack_frames, stack_frame_count);
177
178 fprintf(stderr, " dumping %d stack frame addresses\n", stack_frame_count);
179
180 for (i = 0; i < stack_frame_count; i++)
181 {
182 #ifdef HAVE_DLADDR
183 Dl_info info;
184
185 /* TODO: this is quite hackish, but it works. A more proper solution
186 * would execve addr2strongline and pipe the output to DBG1() */
187 if (dladdr(stack_frames[i], &info))
188 {
189 char cmd[1024];
190 void *ptr = stack_frames[i];
191
192 if (strstr(info.dli_fname, ".so"))
193 {
194 ptr = (void*)(stack_frames[i] - info.dli_fbase);
195 }
196 snprintf(cmd, sizeof(cmd), "addr2line -e %s %p", info.dli_fname, ptr);
197 if (info.dli_sname)
198 {
199 fprintf(stderr, " \e[33m%s\e[0m @ %p (\e[31m%s+0x%x\e[0m) [%p]\n",
200 info.dli_fname, info.dli_fbase, info.dli_sname,
201 stack_frames[i] - info.dli_saddr, stack_frames[i]);
202 }
203 else
204 {
205 fprintf(stderr, " \e[33m%s\e[0m @ %p [%p]\n", info.dli_fname,
206 info.dli_fbase, stack_frames[i]);
207 }
208 fprintf(stderr, " -> \e[32m");
209 system(cmd);
210 fprintf(stderr, "\e[0m");
211 }
212 else
213 #endif /* HAVE_DLADDR */
214 {
215 fprintf(stderr, " %s\n", strings[i]);
216 }
217 }
218 free (strings);
219 #endif /* HAVE_BACKTRACE */
220 }
221
222 /**
223 * Leak report white list
224 *
225 * List of functions using static allocation buffers or should be suppressed
226 * otherwise on leak report.
227 */
228 char *whitelist[] = {
229 /* pthread stuff */
230 "pthread_create",
231 "pthread_setspecific",
232 /* glibc functions */
233 "mktime",
234 "tzset",
235 "inet_ntoa",
236 "strerror",
237 "getprotobynumber",
238 "getservbyport",
239 "getservbyname",
240 "register_printf_function",
241 "syslog",
242 "vsyslog",
243 "getaddrinfo",
244 "setlocale",
245 /* ignore dlopen, as we do not dlclose to get proper leak reports */
246 "dlopen",
247 /* mysql functions */
248 "mysql_init_character_set",
249 "init_client_errs",
250 "my_thread_init",
251 /* fastcgi library */
252 "FCGX_Init",
253 /* libxml */
254 "xmlInitCharEncodingHandlers",
255 "xmlInitParser",
256 "xmlInitParserCtxt",
257 /* ClearSilver */
258 "nerr_init",
259 };
260
261 /**
262 * check if a stack frame contains functions listed above
263 */
264 static bool is_whitelisted(void **stack_frames, int stack_frame_count)
265 {
266 int i, j;
267
268 #ifdef HAVE_DLADDR
269 for (i=0; i< stack_frame_count; i++)
270 {
271 Dl_info info;
272
273 if (dladdr(stack_frames[i], &info) && info.dli_sname)
274 {
275 for (j = 0; j < sizeof(whitelist)/sizeof(char*); j++)
276 {
277 if (streq(info.dli_sname, whitelist[j]))
278 {
279 return TRUE;
280 }
281 }
282 }
283 }
284 #endif /* HAVE_DLADDR */
285 return FALSE;
286 }
287
288 /**
289 * Report leaks at library destruction
290 */
291 void report_leaks()
292 {
293 memory_header_t *hdr;
294 int leaks = 0, whitelisted = 0;
295
296 for (hdr = first_header.next; hdr != NULL; hdr = hdr->next)
297 {
298 if (is_whitelisted(hdr->stack_frames, hdr->stack_frame_count))
299 {
300 whitelisted++;
301 }
302 else
303 {
304 fprintf(stderr, "Leak (%d bytes at %p):\n", hdr->bytes, hdr + 1);
305 /* skip the first frame, contains leak detective logic */
306 log_stack_frames(hdr->stack_frames + 1, hdr->stack_frame_count - 1);
307 leaks++;
308 }
309 }
310
311 switch (leaks)
312 {
313 case 0:
314 fprintf(stderr, "No leaks detected");
315 break;
316 case 1:
317 fprintf(stderr, "One leak detected");
318 break;
319 default:
320 fprintf(stderr, "%d leaks detected", leaks);
321 break;
322 }
323 fprintf(stderr, ", %d suppressed by whitelist\n", whitelisted);
324 }
325
326 /**
327 * Installs the malloc hooks, enables leak detection
328 */
329 static void install_hooks()
330 {
331 if (!installed)
332 {
333 old_malloc_hook = __malloc_hook;
334 old_realloc_hook = __realloc_hook;
335 old_free_hook = __free_hook;
336 __malloc_hook = malloc_hook;
337 __realloc_hook = realloc_hook;
338 __free_hook = free_hook;
339 installed = TRUE;
340 }
341 }
342
343 /**
344 * Uninstalls the malloc hooks, disables leak detection
345 */
346 static void uninstall_hooks()
347 {
348 if (installed)
349 {
350 __malloc_hook = old_malloc_hook;
351 __free_hook = old_free_hook;
352 __realloc_hook = old_realloc_hook;
353 installed = FALSE;
354 }
355 }
356
357 /**
358 * Hook function for malloc()
359 */
360 void *malloc_hook(size_t bytes, const void *caller)
361 {
362 memory_header_t *hdr;
363 memory_tail_t *tail;
364 pthread_t thread_id = pthread_self();
365 int oldpolicy;
366 struct sched_param oldparams, params;
367
368 pthread_getschedparam(thread_id, &oldpolicy, &oldparams);
369
370 params.__sched_priority = sched_get_priority_max(SCHED_FIFO);
371 pthread_setschedparam(thread_id, SCHED_FIFO, &params);
372
373 count_malloc++;
374 uninstall_hooks();
375 hdr = malloc(sizeof(memory_header_t) + bytes + sizeof(memory_tail_t));
376 tail = ((void*)hdr) + bytes + sizeof(memory_header_t);
377 /* set to something which causes crashes */
378 memset(hdr, MEMORY_ALLOC_PATTERN,
379 sizeof(memory_header_t) + bytes + sizeof(memory_tail_t));
380
381 hdr->magic = MEMORY_HEADER_MAGIC;
382 hdr->bytes = bytes;
383 hdr->stack_frame_count = backtrace(hdr->stack_frames, STACK_FRAMES_COUNT);
384 tail->magic = MEMORY_TAIL_MAGIC;
385 install_hooks();
386
387 /* insert at the beginning of the list */
388 hdr->next = first_header.next;
389 if (hdr->next)
390 {
391 hdr->next->previous = hdr;
392 }
393 hdr->previous = &first_header;
394 first_header.next = hdr;
395
396 pthread_setschedparam(thread_id, oldpolicy, &oldparams);
397
398 return hdr + 1;
399 }
400
401 /**
402 * Hook function for free()
403 */
404 void free_hook(void *ptr, const void *caller)
405 {
406 void *stack_frames[STACK_FRAMES_COUNT];
407 int stack_frame_count;
408 memory_header_t *hdr;
409 memory_tail_t *tail;
410 pthread_t thread_id = pthread_self();
411 int oldpolicy;
412 struct sched_param oldparams, params;
413
414 /* allow freeing of NULL */
415 if (ptr == NULL)
416 {
417 return;
418 }
419 hdr = ptr - sizeof(memory_header_t);
420 tail = ptr + hdr->bytes;
421
422 pthread_getschedparam(thread_id, &oldpolicy, &oldparams);
423
424 params.__sched_priority = sched_get_priority_max(SCHED_FIFO);
425 pthread_setschedparam(thread_id, SCHED_FIFO, &params);
426
427 count_free++;
428 uninstall_hooks();
429 if (hdr->magic != MEMORY_HEADER_MAGIC)
430 {
431 fprintf(stderr, "freeing memory with corrupted header "
432 "(%p, MAGIC 0x%x != 0x%x):\n",
433 ptr, hdr->magic, MEMORY_HEADER_MAGIC);
434 stack_frame_count = backtrace(stack_frames, STACK_FRAMES_COUNT);
435 log_stack_frames(stack_frames, stack_frame_count);
436 install_hooks();
437 pthread_setschedparam(thread_id, oldpolicy, &params);
438 return;
439 }
440 if (tail->magic != MEMORY_TAIL_MAGIC)
441 {
442 fprintf(stderr, "freeing memory with corrupted tail "
443 "(%p, MAGIC 0x%x != 0x%x):\n",
444 ptr, tail->magic, MEMORY_TAIL_MAGIC);
445 stack_frame_count = backtrace(stack_frames, STACK_FRAMES_COUNT);
446 log_stack_frames(stack_frames, stack_frame_count);
447 install_hooks();
448 pthread_setschedparam(thread_id, oldpolicy, &oldparams);
449 return;
450 }
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 install_hooks();
464
465 pthread_setschedparam(thread_id, oldpolicy, &params);
466 }
467
468 /**
469 * Hook function for realloc()
470 */
471 void *realloc_hook(void *old, size_t bytes, const void *caller)
472 {
473 memory_header_t *hdr;
474 void *stack_frames[STACK_FRAMES_COUNT];
475 int stack_frame_count;
476 memory_tail_t *tail;
477 pthread_t thread_id = pthread_self();
478 int oldpolicy;
479 struct sched_param oldparams, params;
480
481 /* allow reallocation of NULL */
482 if (old == NULL)
483 {
484 return malloc_hook(bytes, caller);
485 }
486
487 hdr = old - sizeof(memory_header_t);
488 tail = old + hdr->bytes;
489
490 pthread_getschedparam(thread_id, &oldpolicy, &oldparams);
491
492 params.__sched_priority = sched_get_priority_max(SCHED_FIFO);
493 pthread_setschedparam(thread_id, SCHED_FIFO, &params);
494
495 count_realloc++;
496 uninstall_hooks();
497 if (hdr->magic != MEMORY_HEADER_MAGIC)
498 {
499 fprintf(stderr, "reallocating memory with corrupted header "
500 "(%p, MAGIC 0x%x != 0x%x):\n",
501 old, hdr->magic, MEMORY_HEADER_MAGIC);
502 stack_frame_count = backtrace(stack_frames, STACK_FRAMES_COUNT);
503 log_stack_frames(stack_frames, stack_frame_count);
504 install_hooks();
505 pthread_setschedparam(thread_id, oldpolicy, &oldparams);
506 raise(SIGKILL);
507 return NULL;
508 }
509 if (tail->magic != MEMORY_TAIL_MAGIC)
510 {
511 fprintf(stderr, "reallocating memory with corrupted tail "
512 "(%p, MAGIC 0x%x != 0x%x):\n",
513 old, tail->magic, MEMORY_TAIL_MAGIC);
514 stack_frame_count = backtrace(stack_frames, STACK_FRAMES_COUNT);
515 log_stack_frames(stack_frames, stack_frame_count);
516 install_hooks();
517 pthread_setschedparam(thread_id, oldpolicy, &oldparams);
518 raise(SIGKILL);
519 return NULL;
520 }
521 /* clear tail magic, allocate, set tail magic */
522 memset(&tail->magic, MEMORY_ALLOC_PATTERN, sizeof(tail->magic));
523 hdr = realloc(hdr, sizeof(memory_header_t) + bytes + sizeof(memory_tail_t));
524 tail = ((void*)hdr) + bytes + sizeof(memory_header_t);
525 tail->magic = MEMORY_TAIL_MAGIC;
526
527 /* update statistics */
528 hdr->bytes = bytes;
529 hdr->stack_frame_count = backtrace(hdr->stack_frames, STACK_FRAMES_COUNT);
530
531 /* update header of linked list neighbours */
532 if (hdr->next)
533 {
534 hdr->next->previous = hdr;
535 }
536 hdr->previous->next = hdr;
537 install_hooks();
538 pthread_setschedparam(thread_id, oldpolicy, &oldparams);
539 return hdr + 1;
540 }
541
542 /**
543 * Implementation of leak_detective_t.destroy
544 */
545 static void destroy(private_leak_detective_t *this)
546 {
547 if (installed)
548 {
549 uninstall_hooks();
550 report_leaks();
551 }
552 free(this);
553 }
554
555 /*
556 * see header file
557 */
558 leak_detective_t *leak_detective_create()
559 {
560 private_leak_detective_t *this = malloc_thing(private_leak_detective_t);
561
562 this->public.destroy = (void(*)(leak_detective_t*))destroy;
563
564 if (getenv("LEAK_DETECTIVE_DISABLE") == NULL)
565 {
566 install_hooks();
567 }
568 return &this->public;
569 }
570