d4ba26ba1d0db0c40cc8bec030ca823b1b267163
[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 "__gmtime_r",
247 "tzset",
248 "inet_ntoa",
249 "strerror",
250 "getprotobynumber",
251 "getservbyport",
252 "getservbyname",
253 "getpwnam_r",
254 "getgrnam_r",
255 "register_printf_function",
256 "syslog",
257 "vsyslog",
258 "getaddrinfo",
259 "setlocale",
260 /* ignore dlopen, as we do not dlclose to get proper leak reports */
261 "dlopen",
262 /* mysql functions */
263 "mysql_init_character_set",
264 "init_client_errs",
265 "my_thread_init",
266 /* fastcgi library */
267 "FCGX_Init",
268 /* libxml */
269 "xmlInitCharEncodingHandlers",
270 "xmlInitParser",
271 "xmlInitParserCtxt",
272 /* ClearSilver */
273 "nerr_init",
274 };
275
276 /**
277 * check if a stack frame contains functions listed above
278 */
279 static bool is_whitelisted(void **stack_frames, int stack_frame_count)
280 {
281 int i, j;
282
283 #ifdef HAVE_DLADDR
284 for (i=0; i< stack_frame_count; i++)
285 {
286 Dl_info info;
287
288 if (dladdr(stack_frames[i], &info) && info.dli_sname)
289 {
290 for (j = 0; j < sizeof(whitelist)/sizeof(char*); j++)
291 {
292 if (streq(info.dli_sname, whitelist[j]))
293 {
294 return TRUE;
295 }
296 }
297 }
298 }
299 #endif /* HAVE_DLADDR */
300 return FALSE;
301 }
302
303 /**
304 * Report leaks at library destruction
305 */
306 void report_leaks()
307 {
308 memory_header_t *hdr;
309 int leaks = 0, whitelisted = 0;
310
311 for (hdr = first_header.next; hdr != NULL; hdr = hdr->next)
312 {
313 if (is_whitelisted(hdr->stack_frames, hdr->stack_frame_count))
314 {
315 whitelisted++;
316 }
317 else
318 {
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);
322 leaks++;
323 }
324 }
325
326 switch (leaks)
327 {
328 case 0:
329 fprintf(stderr, "No leaks detected");
330 break;
331 case 1:
332 fprintf(stderr, "One leak detected");
333 break;
334 default:
335 fprintf(stderr, "%d leaks detected", leaks);
336 break;
337 }
338 fprintf(stderr, ", %d suppressed by whitelist\n", whitelisted);
339 }
340
341 /**
342 * Installs the malloc hooks, enables leak detection
343 */
344 static void install_hooks()
345 {
346 if (!installed)
347 {
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;
354 installed = TRUE;
355 }
356 }
357
358 /**
359 * Uninstalls the malloc hooks, disables leak detection
360 */
361 static void uninstall_hooks()
362 {
363 if (installed)
364 {
365 __malloc_hook = old_malloc_hook;
366 __free_hook = old_free_hook;
367 __realloc_hook = old_realloc_hook;
368 installed = FALSE;
369 }
370 }
371
372 /**
373 * Hook function for malloc()
374 */
375 void *malloc_hook(size_t bytes, const void *caller)
376 {
377 memory_header_t *hdr;
378 memory_tail_t *tail;
379 pthread_t thread_id = pthread_self();
380 int oldpolicy;
381 struct sched_param oldparams, params;
382
383 pthread_getschedparam(thread_id, &oldpolicy, &oldparams);
384
385 params.__sched_priority = sched_get_priority_max(SCHED_FIFO);
386 pthread_setschedparam(thread_id, SCHED_FIFO, &params);
387
388 count_malloc++;
389 uninstall_hooks();
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));
395
396 hdr->magic = MEMORY_HEADER_MAGIC;
397 hdr->bytes = bytes;
398 hdr->stack_frame_count = backtrace(hdr->stack_frames, STACK_FRAMES_COUNT);
399 tail->magic = MEMORY_TAIL_MAGIC;
400 install_hooks();
401
402 /* insert at the beginning of the list */
403 hdr->next = first_header.next;
404 if (hdr->next)
405 {
406 hdr->next->previous = hdr;
407 }
408 hdr->previous = &first_header;
409 first_header.next = hdr;
410
411 pthread_setschedparam(thread_id, oldpolicy, &oldparams);
412
413 return hdr + 1;
414 }
415
416 /**
417 * Hook function for free()
418 */
419 void free_hook(void *ptr, const void *caller)
420 {
421 void *stack_frames[STACK_FRAMES_COUNT];
422 int stack_frame_count;
423 memory_header_t *hdr;
424 memory_tail_t *tail;
425 pthread_t thread_id = pthread_self();
426 int oldpolicy;
427 struct sched_param oldparams, params;
428
429 /* allow freeing of NULL */
430 if (ptr == NULL)
431 {
432 return;
433 }
434 hdr = ptr - sizeof(memory_header_t);
435 tail = ptr + hdr->bytes;
436
437 pthread_getschedparam(thread_id, &oldpolicy, &oldparams);
438
439 params.__sched_priority = sched_get_priority_max(SCHED_FIFO);
440 pthread_setschedparam(thread_id, SCHED_FIFO, &params);
441
442 count_free++;
443 uninstall_hooks();
444 if (hdr->magic != MEMORY_HEADER_MAGIC ||
445 tail->magic != MEMORY_TAIL_MAGIC)
446 {
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);
452 }
453 else
454 {
455 /* remove item from list */
456 if (hdr->next)
457 {
458 hdr->next->previous = hdr->previous;
459 }
460 hdr->previous->next = hdr->next;
461
462 /* clear MAGIC, set mem to something remarkable */
463 memset(hdr, MEMORY_FREE_PATTERN, hdr->bytes + sizeof(memory_header_t));
464
465 free(hdr);
466 }
467
468 install_hooks();
469 pthread_setschedparam(thread_id, oldpolicy, &oldparams);
470 }
471
472 /**
473 * Hook function for realloc()
474 */
475 void *realloc_hook(void *old, size_t bytes, const void *caller)
476 {
477 memory_header_t *hdr;
478 void *stack_frames[STACK_FRAMES_COUNT];
479 int stack_frame_count;
480 memory_tail_t *tail;
481 pthread_t thread_id = pthread_self();
482 int oldpolicy;
483 struct sched_param oldparams, params;
484
485 /* allow reallocation of NULL */
486 if (old == NULL)
487 {
488 return malloc_hook(bytes, caller);
489 }
490
491 hdr = old - sizeof(memory_header_t);
492 tail = old + hdr->bytes;
493
494 pthread_getschedparam(thread_id, &oldpolicy, &oldparams);
495
496 params.__sched_priority = sched_get_priority_max(SCHED_FIFO);
497 pthread_setschedparam(thread_id, SCHED_FIFO, &params);
498
499 count_realloc++;
500 uninstall_hooks();
501 if (hdr->magic != MEMORY_HEADER_MAGIC ||
502 tail->magic != MEMORY_TAIL_MAGIC)
503 {
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);
509 }
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;
515
516 /* update statistics */
517 hdr->bytes = bytes;
518 hdr->stack_frame_count = backtrace(hdr->stack_frames, STACK_FRAMES_COUNT);
519
520 /* update header of linked list neighbours */
521 if (hdr->next)
522 {
523 hdr->next->previous = hdr;
524 }
525 hdr->previous->next = hdr;
526 install_hooks();
527 pthread_setschedparam(thread_id, oldpolicy, &oldparams);
528 return hdr + 1;
529 }
530
531 /**
532 * Implementation of leak_detective_t.destroy
533 */
534 static void destroy(private_leak_detective_t *this)
535 {
536 if (installed)
537 {
538 uninstall_hooks();
539 report_leaks();
540 }
541 free(this);
542 }
543
544 /*
545 * see header file
546 */
547 leak_detective_t *leak_detective_create()
548 {
549 private_leak_detective_t *this = malloc_thing(private_leak_detective_t);
550
551 this->public.destroy = (void(*)(leak_detective_t*))destroy;
552
553 if (getenv("LEAK_DETECTIVE_DISABLE") == NULL)
554 {
555 install_hooks();
556 }
557 return &this->public;
558 }
559