]>
Commit | Line | Data |
---|---|---|
5113680f | 1 | /* |
17211b6b MW |
2 | * Copyright (C) 2013 Tobias Brunner |
3 | * Copyright (C) 2006-2013 Martin Willi | |
5113680f MW |
4 | * Hochschule fuer Technik Rapperswil |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms of the GNU General Public License as published by the | |
8 | * Free Software Foundation; either version 2 of the License, or (at your | |
9 | * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, but | |
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | |
13 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
14 | * for more details. | |
15 | */ | |
7daf5226 | 16 | |
7a485e90 | 17 | #define _GNU_SOURCE |
5113680f | 18 | #include <stddef.h> |
5113680f MW |
19 | #include <string.h> |
20 | #include <stdio.h> | |
7daf5226 | 21 | #include <signal.h> |
5113680f MW |
22 | #include <sys/socket.h> |
23 | #include <netinet/in.h> | |
24 | #include <arpa/inet.h> | |
1d025fbc | 25 | #include <unistd.h> |
8bc96e08 | 26 | #include <syslog.h> |
b1904247 | 27 | #include <netdb.h> |
552cc11b | 28 | #include <locale.h> |
17211b6b | 29 | #include <dlfcn.h> |
7e3f6299 | 30 | #include <time.h> |
d8f6f0c0 MW |
31 | #include <errno.h> |
32 | ||
33 | #ifdef __APPLE__ | |
34 | #include <sys/mman.h> | |
35 | #include <malloc/malloc.h> | |
36 | /* overload some of our types clashing with mach */ | |
37 | #define host_t strongswan_host_t | |
38 | #define processor_t strongswan_processor_t | |
39 | #define thread_t strongswan_thread_t | |
40 | #endif /* __APPLE__ */ | |
5113680f MW |
41 | |
42 | #include "leak_detective.h" | |
43 | ||
60356f33 | 44 | #include <library.h> |
f05b4272 | 45 | #include <utils/debug.h> |
f7237cf3 | 46 | #include <utils/backtrace.h> |
12642a68 | 47 | #include <collections/hashtable.h> |
17211b6b MW |
48 | #include <threading/thread_value.h> |
49 | #include <threading/spinlock.h> | |
5113680f | 50 | |
552cc11b MW |
51 | typedef struct private_leak_detective_t private_leak_detective_t; |
52 | ||
53 | /** | |
54 | * private data of leak_detective | |
55 | */ | |
56 | struct private_leak_detective_t { | |
57 | ||
58 | /** | |
59 | * public functions | |
60 | */ | |
61 | leak_detective_t public; | |
62 | }; | |
5113680f MW |
63 | |
64 | /** | |
269f7f44 | 65 | * Magic value which helps to detect memory corruption. Yummy! |
5113680f | 66 | */ |
269f7f44 MW |
67 | #define MEMORY_HEADER_MAGIC 0x7ac0be11 |
68 | ||
b7ef3f62 MW |
69 | /** |
70 | * Magic written to tail of allocation | |
71 | */ | |
72 | #define MEMORY_TAIL_MAGIC 0xcafebabe | |
73 | ||
269f7f44 MW |
74 | /** |
75 | * Pattern which is filled in memory before freeing it | |
76 | */ | |
77 | #define MEMORY_FREE_PATTERN 0xFF | |
78 | ||
79 | /** | |
80 | * Pattern which is filled in newly allocated memory | |
81 | */ | |
82 | #define MEMORY_ALLOC_PATTERN 0xEE | |
83 | ||
5113680f | 84 | typedef struct memory_header_t memory_header_t; |
b7ef3f62 | 85 | typedef struct memory_tail_t memory_tail_t; |
5113680f MW |
86 | |
87 | /** | |
88 | * Header which is prepended to each allocated memory block | |
89 | */ | |
90 | struct memory_header_t { | |
7daf5226 | 91 | |
5113680f MW |
92 | /** |
93 | * Pointer to previous entry in linked list | |
94 | */ | |
95 | memory_header_t *previous; | |
7daf5226 | 96 | |
5113680f MW |
97 | /** |
98 | * Pointer to next entry in linked list | |
99 | */ | |
100 | memory_header_t *next; | |
7daf5226 | 101 | |
f7237cf3 MW |
102 | /** |
103 | * backtrace taken during (re-)allocation | |
104 | */ | |
105 | backtrace_t *backtrace; | |
7daf5226 | 106 | |
3117824f MW |
107 | /** |
108 | * Padding to make sizeof(memory_header_t) == 32 | |
109 | */ | |
110 | u_int32_t padding[sizeof(void*) == sizeof(u_int32_t) ? 3 : 0]; | |
111 | ||
35c93479 MW |
112 | /** |
113 | * Number of bytes following after the header | |
114 | */ | |
115 | u_int32_t bytes; | |
116 | ||
b7ef3f62 MW |
117 | /** |
118 | * magic bytes to detect bad free or heap underflow, MEMORY_HEADER_MAGIC | |
119 | */ | |
120 | u_int32_t magic; | |
7daf5226 | 121 | |
b7ef3f62 MW |
122 | }__attribute__((__packed__)); |
123 | ||
124 | /** | |
125 | * tail appended to each allocated memory block | |
126 | */ | |
127 | struct memory_tail_t { | |
128 | ||
129 | /** | |
130 | * Magic bytes to detect heap overflow, MEMORY_TAIL_MAGIC | |
131 | */ | |
132 | u_int32_t magic; | |
7daf5226 | 133 | |
b7ef3f62 | 134 | }__attribute__((__packed__)); |
5113680f MW |
135 | |
136 | /** | |
7daf5226 | 137 | * first mem header is just a dummy to chain |
5113680f MW |
138 | * the others on it... |
139 | */ | |
6b3292da | 140 | static memory_header_t first_header = { |
17211b6b | 141 | .magic = MEMORY_HEADER_MAGIC, |
5113680f MW |
142 | }; |
143 | ||
1d025fbc | 144 | /** |
17211b6b | 145 | * Spinlock to access header linked list |
1d025fbc | 146 | */ |
17211b6b MW |
147 | static spinlock_t *lock; |
148 | ||
149 | /** | |
150 | * Is leak detection currently enabled? | |
151 | */ | |
152 | static bool enabled = FALSE; | |
153 | ||
154 | /** | |
155 | * Is leak detection disabled for the current thread? | |
156 | */ | |
157 | static thread_value_t *thread_disabled; | |
5113680f | 158 | |
eb4f4551 MW |
159 | /** |
160 | * Installs the malloc hooks, enables leak detection | |
161 | */ | |
17211b6b | 162 | static void enable_leak_detective() |
eb4f4551 | 163 | { |
17211b6b MW |
164 | enabled = TRUE; |
165 | } | |
166 | ||
167 | /** | |
168 | * Uninstalls the malloc hooks, disables leak detection | |
169 | */ | |
170 | static void disable_leak_detective() | |
171 | { | |
172 | enabled = FALSE; | |
173 | } | |
174 | ||
175 | /** | |
176 | * Enable/Disable leak detective for the current thread | |
177 | * | |
178 | * @return Previous value | |
179 | */ | |
180 | static bool enable_thread(bool enable) | |
181 | { | |
182 | bool before; | |
183 | ||
184 | before = thread_disabled->get(thread_disabled) == NULL; | |
185 | thread_disabled->set(thread_disabled, enable ? NULL : (void*)TRUE); | |
186 | return before; | |
187 | } | |
188 | ||
d8f6f0c0 MW |
189 | #ifdef __APPLE__ |
190 | ||
191 | /** | |
192 | * Copy of original default zone, with functions we call in hooks | |
193 | */ | |
194 | static malloc_zone_t original; | |
195 | ||
196 | /** | |
197 | * Call original malloc() | |
198 | */ | |
199 | static void* real_malloc(size_t size) | |
200 | { | |
201 | return original.malloc(malloc_default_zone(), size); | |
202 | } | |
203 | ||
204 | /** | |
205 | * Call original free() | |
206 | */ | |
207 | static void real_free(void *ptr) | |
208 | { | |
209 | original.free(malloc_default_zone(), ptr); | |
210 | } | |
211 | ||
212 | /** | |
213 | * Call original realloc() | |
214 | */ | |
215 | static void* real_realloc(void *ptr, size_t size) | |
216 | { | |
217 | return original.realloc(malloc_default_zone(), ptr, size); | |
218 | } | |
219 | ||
220 | /** | |
221 | * Hook definition: static function with _hook suffix, takes additional zone | |
222 | */ | |
223 | #define HOOK(ret, name, ...) \ | |
224 | static ret name ## _hook(malloc_zone_t *_z, __VA_ARGS__) | |
225 | ||
226 | /** | |
227 | * forward declaration of hooks | |
228 | */ | |
229 | HOOK(void*, malloc, size_t bytes); | |
230 | HOOK(void*, calloc, size_t nmemb, size_t size); | |
231 | HOOK(void*, valloc, size_t size); | |
232 | HOOK(void, free, void *ptr); | |
233 | HOOK(void*, realloc, void *old, size_t bytes); | |
234 | ||
235 | /** | |
236 | * malloc zone size(), must consider the memory header prepended | |
237 | */ | |
238 | HOOK(size_t, size, const void *ptr) | |
239 | { | |
240 | bool before; | |
241 | size_t size; | |
242 | ||
243 | if (enabled) | |
244 | { | |
245 | before = enable_thread(FALSE); | |
246 | if (before) | |
247 | { | |
248 | ptr -= sizeof(memory_header_t); | |
249 | } | |
250 | } | |
251 | size = original.size(malloc_default_zone(), ptr); | |
252 | if (enabled) | |
253 | { | |
254 | enable_thread(before); | |
255 | } | |
256 | return size; | |
257 | } | |
258 | ||
259 | /** | |
260 | * Version of malloc zones we currently support | |
261 | */ | |
262 | #define MALLOC_ZONE_VERSION 8 /* Snow Leopard */ | |
263 | ||
264 | /** | |
265 | * Hook-in our malloc functions into the default zone | |
266 | */ | |
267 | static bool register_hooks() | |
268 | { | |
269 | malloc_zone_t *zone; | |
270 | void *page; | |
271 | ||
272 | zone = malloc_default_zone(); | |
273 | if (zone->version != MALLOC_ZONE_VERSION) | |
274 | { | |
275 | DBG1(DBG_CFG, "malloc zone version %d unsupported (requiring %d)", | |
276 | zone->version, MALLOC_ZONE_VERSION); | |
277 | return FALSE; | |
278 | } | |
279 | ||
280 | original = *zone; | |
281 | ||
282 | page = (void*)((uintptr_t)zone / getpagesize() * getpagesize()); | |
283 | if (mprotect(page, getpagesize(), PROT_WRITE | PROT_READ) != 0) | |
284 | { | |
285 | DBG1(DBG_CFG, "malloc zone unprotection failed: %s", strerror(errno)); | |
286 | return FALSE; | |
287 | } | |
288 | ||
289 | zone->size = size_hook; | |
290 | zone->malloc = malloc_hook; | |
291 | zone->calloc = calloc_hook; | |
292 | zone->valloc = valloc_hook; | |
293 | zone->free = free_hook; | |
294 | zone->realloc = realloc_hook; | |
295 | ||
296 | /* those other functions can be NULLed out to not use them */ | |
297 | zone->batch_malloc = NULL; | |
298 | zone->batch_free = NULL; | |
299 | zone->memalign = NULL; | |
300 | zone->free_definite_size = NULL; | |
301 | ||
302 | return TRUE; | |
303 | } | |
304 | ||
305 | #else /* !__APPLE__ */ | |
306 | ||
17211b6b MW |
307 | /** |
308 | * dlsym() might do a malloc(), but we can't do one before we get the malloc() | |
309 | * function pointer. Use this minimalistic malloc implementation instead. | |
310 | */ | |
311 | static void* malloc_for_dlsym(size_t size) | |
312 | { | |
313 | static char buf[1024] = {}; | |
314 | static size_t used = 0; | |
315 | char *ptr; | |
316 | ||
317 | /* roundup to a multiple of 32 */ | |
318 | size = (size - 1) / 32 * 32 + 32; | |
319 | ||
320 | if (used + size > sizeof(buf)) | |
eb4f4551 | 321 | { |
17211b6b | 322 | return NULL; |
eb4f4551 | 323 | } |
17211b6b MW |
324 | ptr = buf + used; |
325 | used += size; | |
326 | return ptr; | |
eb4f4551 MW |
327 | } |
328 | ||
329 | /** | |
17211b6b MW |
330 | * Lookup a malloc function, while disabling wrappers |
331 | */ | |
332 | static void* get_malloc_fn(char *name) | |
333 | { | |
334 | bool before = FALSE; | |
335 | void *fn; | |
336 | ||
337 | if (enabled) | |
338 | { | |
339 | before = enable_thread(FALSE); | |
340 | } | |
341 | fn = dlsym(RTLD_NEXT, name); | |
342 | if (enabled) | |
343 | { | |
344 | enable_thread(before); | |
345 | } | |
346 | return fn; | |
347 | } | |
348 | ||
349 | /** | |
350 | * Call original malloc() | |
351 | */ | |
352 | static void* real_malloc(size_t size) | |
353 | { | |
354 | static void* (*fn)(size_t size); | |
355 | static int recursive = 0; | |
356 | ||
357 | if (!fn) | |
358 | { | |
359 | /* checking recursiveness should actually be thread-specific. But as | |
360 | * it is very likely that the first allocation is done before we go | |
361 | * multi-threaded, we keep it simple. */ | |
362 | if (recursive) | |
363 | { | |
364 | return malloc_for_dlsym(size); | |
365 | } | |
366 | recursive++; | |
367 | fn = get_malloc_fn("malloc"); | |
368 | recursive--; | |
369 | } | |
370 | return fn(size); | |
371 | } | |
372 | ||
373 | /** | |
374 | * Call original free() | |
eb4f4551 | 375 | */ |
17211b6b | 376 | static void real_free(void *ptr) |
eb4f4551 | 377 | { |
17211b6b MW |
378 | static void (*fn)(void *ptr); |
379 | ||
380 | if (!fn) | |
eb4f4551 | 381 | { |
17211b6b | 382 | fn = get_malloc_fn("free"); |
eb4f4551 | 383 | } |
17211b6b MW |
384 | return fn(ptr); |
385 | } | |
386 | ||
387 | /** | |
388 | * Call original realloc() | |
389 | */ | |
390 | static void* real_realloc(void *ptr, size_t size) | |
391 | { | |
392 | static void* (*fn)(void *ptr, size_t size); | |
393 | ||
394 | if (!fn) | |
395 | { | |
396 | fn = get_malloc_fn("realloc"); | |
397 | } | |
398 | return fn(ptr, size); | |
eb4f4551 MW |
399 | } |
400 | ||
d8f6f0c0 MW |
401 | /** |
402 | * Hook definition: plain function overloading existing malloc calls | |
403 | */ | |
404 | #define HOOK(ret, name, ...) ret name(__VA_ARGS__) | |
405 | ||
406 | /** | |
407 | * Hook initialization when not using hooks | |
408 | */ | |
409 | static bool register_hooks() | |
410 | { | |
411 | return TRUE; | |
412 | } | |
413 | ||
414 | #endif /* !__APPLE__ */ | |
415 | ||
986d23bd | 416 | /** |
552cc11b | 417 | * Leak report white list |
151168f6 | 418 | * |
552cc11b | 419 | * List of functions using static allocation buffers or should be suppressed |
7daf5226 | 420 | * otherwise on leak report. |
986d23bd | 421 | */ |
552cc11b | 422 | char *whitelist[] = { |
f7237cf3 MW |
423 | /* backtraces, including own */ |
424 | "backtrace_create", | |
7c8b9fcb | 425 | "safe_strerror", |
7939864d | 426 | /* pthread stuff */ |
552cc11b MW |
427 | "pthread_create", |
428 | "pthread_setspecific", | |
9eb85cff | 429 | "__pthread_setspecific", |
7939864d | 430 | /* glibc functions */ |
552cc11b MW |
431 | "inet_ntoa", |
432 | "strerror", | |
0ffedbfb | 433 | "getprotobyname", |
552cc11b MW |
434 | "getprotobynumber", |
435 | "getservbyport", | |
436 | "getservbyname", | |
b97ca14d | 437 | "gethostbyname", |
3f9ec06f | 438 | "gethostbyname2", |
5a22a021 MW |
439 | "gethostbyname_r", |
440 | "gethostbyname2_r", | |
3f9ec06f | 441 | "getnetbyname", |
25b12c69 MW |
442 | "getpwnam_r", |
443 | "getgrnam_r", | |
552cc11b | 444 | "register_printf_function", |
86813bef | 445 | "register_printf_specifier", |
552cc11b MW |
446 | "syslog", |
447 | "vsyslog", | |
044e0dd1 MW |
448 | "__syslog_chk", |
449 | "__vsyslog_chk", | |
552cc11b MW |
450 | "getaddrinfo", |
451 | "setlocale", | |
70789d28 | 452 | "getpass", |
b97ca14d AS |
453 | "getpwent_r", |
454 | "setpwent", | |
455 | "endpwent", | |
0bac49b0 | 456 | "getspnam_r", |
c9418d4f AS |
457 | "getpwuid_r", |
458 | "initgroups", | |
7939864d MW |
459 | /* ignore dlopen, as we do not dlclose to get proper leak reports */ |
460 | "dlopen", | |
a3d92a37 | 461 | "dlerror", |
1caa265c | 462 | "dlclose", |
07bda3fe | 463 | "dlsym", |
7939864d | 464 | /* mysql functions */ |
552cc11b MW |
465 | "mysql_init_character_set", |
466 | "init_client_errs", | |
467 | "my_thread_init", | |
7939864d | 468 | /* fastcgi library */ |
cf4caefa | 469 | "FCGX_Init", |
7939864d MW |
470 | /* libxml */ |
471 | "xmlInitCharEncodingHandlers", | |
472 | "xmlInitParser", | |
473 | "xmlInitParserCtxt", | |
727b0f11 AS |
474 | /* libcurl */ |
475 | "Curl_client_write", | |
7939864d MW |
476 | /* ClearSilver */ |
477 | "nerr_init", | |
513a1a28 MW |
478 | /* libgcrypt */ |
479 | "gcry_control", | |
480 | "gcry_check_version", | |
a41d0932 MW |
481 | "gcry_randomize", |
482 | "gcry_create_nonce", | |
50a9e845 MW |
483 | /* NSPR */ |
484 | "PR_CallOnce", | |
1888dd6b AS |
485 | /* libapr */ |
486 | "apr_pool_create_ex", | |
ec8426a3 MW |
487 | /* glib */ |
488 | "g_type_init_with_debug_flags", | |
489 | "g_type_register_static", | |
490 | "g_type_class_ref", | |
491 | "g_type_create_instance", | |
492 | "g_type_add_interface_static", | |
493 | "g_type_interface_add_prerequisite", | |
494 | "g_socket_connection_factory_lookup_type", | |
495 | /* libgpg */ | |
496 | "gpg_err_init", | |
7cfa84f5 MW |
497 | /* gnutls */ |
498 | "gnutls_global_init", | |
986d23bd MW |
499 | }; |
500 | ||
7e3f6299 MW |
501 | /** |
502 | * Some functions are hard to whitelist, as they don't use a symbol directly. | |
503 | * Use some static initialization to suppress them on leak reports | |
504 | */ | |
505 | static void init_static_allocations() | |
506 | { | |
507 | tzset(); | |
508 | } | |
5113680f | 509 | |
fce3b5c3 MW |
510 | /** |
511 | * Hashtable hash function | |
512 | */ | |
513 | static u_int hash(backtrace_t *key) | |
514 | { | |
515 | enumerator_t *enumerator; | |
516 | void *addr; | |
517 | u_int hash = 0; | |
518 | ||
519 | enumerator = key->create_frame_enumerator(key); | |
520 | while (enumerator->enumerate(enumerator, &addr)) | |
521 | { | |
522 | hash = chunk_hash_inc(chunk_from_thing(addr), hash); | |
523 | } | |
524 | enumerator->destroy(enumerator); | |
525 | ||
526 | return hash; | |
527 | } | |
528 | ||
529 | /** | |
530 | * Hashtable equals function | |
531 | */ | |
532 | static bool equals(backtrace_t *a, backtrace_t *b) | |
533 | { | |
534 | return a->equals(a, b); | |
535 | } | |
536 | ||
eb4f4551 MW |
537 | /** |
538 | * Summarize and print backtraces | |
539 | */ | |
540 | static int print_traces(private_leak_detective_t *this, | |
541 | FILE *out, int thresh, bool detailed, int *whitelisted) | |
fce3b5c3 | 542 | { |
eb4f4551 | 543 | int leaks = 0; |
fce3b5c3 MW |
544 | memory_header_t *hdr; |
545 | enumerator_t *enumerator; | |
546 | hashtable_t *entries; | |
547 | struct { | |
548 | /** associated backtrace */ | |
549 | backtrace_t *backtrace; | |
550 | /** total size of all allocations */ | |
551 | size_t bytes; | |
552 | /** number of allocations */ | |
553 | u_int count; | |
554 | } *entry; | |
17211b6b | 555 | bool before; |
fce3b5c3 | 556 | |
17211b6b | 557 | before = enable_thread(FALSE); |
fce3b5c3 MW |
558 | |
559 | entries = hashtable_create((hashtable_hash_t)hash, | |
560 | (hashtable_equals_t)equals, 1024); | |
17211b6b | 561 | lock->lock(lock); |
fce3b5c3 MW |
562 | for (hdr = first_header.next; hdr != NULL; hdr = hdr->next) |
563 | { | |
eb4f4551 MW |
564 | if (whitelisted && |
565 | hdr->backtrace->contains_function(hdr->backtrace, | |
566 | whitelist, countof(whitelist))) | |
567 | { | |
568 | (*whitelisted)++; | |
569 | continue; | |
570 | } | |
fce3b5c3 MW |
571 | entry = entries->get(entries, hdr->backtrace); |
572 | if (entry) | |
573 | { | |
574 | entry->bytes += hdr->bytes; | |
575 | entry->count++; | |
576 | } | |
577 | else | |
578 | { | |
579 | INIT(entry, | |
580 | .backtrace = hdr->backtrace, | |
581 | .bytes = hdr->bytes, | |
582 | .count = 1, | |
583 | ); | |
584 | entries->put(entries, hdr->backtrace, entry); | |
585 | } | |
eb4f4551 | 586 | leaks++; |
fce3b5c3 | 587 | } |
17211b6b | 588 | lock->unlock(lock); |
fce3b5c3 MW |
589 | enumerator = entries->create_enumerator(entries); |
590 | while (enumerator->enumerate(enumerator, NULL, &entry)) | |
591 | { | |
01e15ab5 | 592 | if (out && (!thresh || entry->bytes >= thresh)) |
fce3b5c3 | 593 | { |
eb4f4551 | 594 | fprintf(out, "%d bytes total, %d allocations, %d bytes average:\n", |
fce3b5c3 | 595 | entry->bytes, entry->count, entry->bytes / entry->count); |
eb4f4551 | 596 | entry->backtrace->log(entry->backtrace, out, detailed); |
fce3b5c3 MW |
597 | } |
598 | free(entry); | |
599 | } | |
600 | enumerator->destroy(enumerator); | |
601 | entries->destroy(entries); | |
602 | ||
17211b6b | 603 | enable_thread(before); |
eb4f4551 MW |
604 | return leaks; |
605 | } | |
606 | ||
607 | METHOD(leak_detective_t, report, void, | |
608 | private_leak_detective_t *this, bool detailed) | |
609 | { | |
610 | if (lib->leak_detective) | |
611 | { | |
01e15ab5 | 612 | int leaks, whitelisted = 0; |
eb4f4551 MW |
613 | |
614 | leaks = print_traces(this, stderr, 0, detailed, &whitelisted); | |
615 | switch (leaks) | |
616 | { | |
617 | case 0: | |
618 | fprintf(stderr, "No leaks detected"); | |
619 | break; | |
620 | case 1: | |
621 | fprintf(stderr, "One leak detected"); | |
622 | break; | |
623 | default: | |
624 | fprintf(stderr, "%d leaks detected", leaks); | |
625 | break; | |
626 | } | |
627 | fprintf(stderr, ", %d suppressed by whitelist\n", whitelisted); | |
628 | } | |
629 | else | |
630 | { | |
631 | fprintf(stderr, "Leak detective disabled\n"); | |
632 | } | |
633 | } | |
634 | ||
01e15ab5 TB |
635 | METHOD(leak_detective_t, leaks, int, |
636 | private_leak_detective_t *this) | |
637 | { | |
638 | if (lib->leak_detective) | |
639 | { | |
640 | int leaks, whitelisted = 0; | |
641 | ||
642 | leaks = print_traces(this, NULL, 0, FALSE, &whitelisted); | |
643 | return leaks; | |
644 | } | |
645 | return 0; | |
646 | } | |
647 | ||
b46776ae MW |
648 | METHOD(leak_detective_t, set_state, bool, |
649 | private_leak_detective_t *this, bool enable) | |
650 | { | |
17211b6b | 651 | if (enable == enabled) |
b46776ae | 652 | { |
17211b6b | 653 | return enabled; |
b46776ae | 654 | } |
b46776ae MW |
655 | if (enable) |
656 | { | |
17211b6b | 657 | enable_leak_detective(); |
b46776ae MW |
658 | } |
659 | else | |
660 | { | |
17211b6b | 661 | disable_leak_detective(); |
b46776ae | 662 | } |
17211b6b | 663 | return !enabled; |
b46776ae MW |
664 | } |
665 | ||
eb4f4551 MW |
666 | METHOD(leak_detective_t, usage, void, |
667 | private_leak_detective_t *this, FILE *out) | |
668 | { | |
eb4f4551 | 669 | bool detailed; |
17211b6b | 670 | int thresh; |
eb4f4551 MW |
671 | |
672 | thresh = lib->settings->get_int(lib->settings, | |
673 | "libstrongswan.leak_detective.usage_threshold", 10240); | |
674 | detailed = lib->settings->get_bool(lib->settings, | |
675 | "libstrongswan.leak_detective.detailed", TRUE); | |
676 | ||
eb4f4551 | 677 | print_traces(this, out, thresh, detailed, NULL); |
fce3b5c3 MW |
678 | } |
679 | ||
5113680f | 680 | /** |
17211b6b | 681 | * Wrapped malloc() function |
5113680f | 682 | */ |
d8f6f0c0 | 683 | HOOK(void*, malloc, size_t bytes) |
5113680f MW |
684 | { |
685 | memory_header_t *hdr; | |
b7ef3f62 | 686 | memory_tail_t *tail; |
17211b6b | 687 | bool before; |
7daf5226 | 688 | |
17211b6b MW |
689 | if (!enabled || thread_disabled->get(thread_disabled)) |
690 | { | |
691 | return real_malloc(bytes); | |
692 | } | |
7daf5226 | 693 | |
17211b6b | 694 | hdr = real_malloc(sizeof(memory_header_t) + bytes + sizeof(memory_tail_t)); |
b7ef3f62 | 695 | tail = ((void*)hdr) + bytes + sizeof(memory_header_t); |
986d23bd | 696 | /* set to something which causes crashes */ |
b7ef3f62 MW |
697 | memset(hdr, MEMORY_ALLOC_PATTERN, |
698 | sizeof(memory_header_t) + bytes + sizeof(memory_tail_t)); | |
7daf5226 | 699 | |
17211b6b MW |
700 | before = enable_thread(FALSE); |
701 | hdr->backtrace = backtrace_create(2); | |
702 | enable_thread(before); | |
703 | ||
5113680f MW |
704 | hdr->magic = MEMORY_HEADER_MAGIC; |
705 | hdr->bytes = bytes; | |
b7ef3f62 | 706 | tail->magic = MEMORY_TAIL_MAGIC; |
7daf5226 | 707 | |
5113680f | 708 | /* insert at the beginning of the list */ |
17211b6b | 709 | lock->lock(lock); |
5113680f MW |
710 | hdr->next = first_header.next; |
711 | if (hdr->next) | |
712 | { | |
713 | hdr->next->previous = hdr; | |
714 | } | |
715 | hdr->previous = &first_header; | |
716 | first_header.next = hdr; | |
17211b6b | 717 | lock->unlock(lock); |
7daf5226 | 718 | |
5113680f MW |
719 | return hdr + 1; |
720 | } | |
721 | ||
722 | /** | |
17211b6b | 723 | * Wrapped calloc() function |
5113680f | 724 | */ |
d8f6f0c0 | 725 | HOOK(void*, calloc, size_t nmemb, size_t size) |
17211b6b MW |
726 | { |
727 | void *ptr; | |
728 | ||
729 | size *= nmemb; | |
730 | ptr = malloc(size); | |
731 | memset(ptr, 0, size); | |
732 | ||
733 | return ptr; | |
734 | } | |
735 | ||
d8f6f0c0 MW |
736 | /** |
737 | * Wrapped valloc(), TODO: currently not supported | |
738 | */ | |
739 | HOOK(void*, valloc, size_t size) | |
740 | { | |
741 | DBG1(DBG_LIB, "valloc() used, but leak-detective hook missing"); | |
742 | return NULL; | |
743 | } | |
744 | ||
17211b6b MW |
745 | /** |
746 | * Wrapped free() function | |
747 | */ | |
d8f6f0c0 | 748 | HOOK(void, free, void *ptr) |
5113680f | 749 | { |
587ebae7 | 750 | memory_header_t *hdr, *current; |
b7ef3f62 | 751 | memory_tail_t *tail; |
323f9f99 | 752 | backtrace_t *backtrace; |
17211b6b | 753 | bool found = FALSE, before; |
7daf5226 | 754 | |
17211b6b MW |
755 | if (!enabled || thread_disabled->get(thread_disabled)) |
756 | { | |
757 | real_free(ptr); | |
758 | return; | |
759 | } | |
5113680f MW |
760 | /* allow freeing of NULL */ |
761 | if (ptr == NULL) | |
762 | { | |
763 | return; | |
764 | } | |
b7ef3f62 MW |
765 | hdr = ptr - sizeof(memory_header_t); |
766 | tail = ptr + hdr->bytes; | |
7daf5226 | 767 | |
17211b6b | 768 | before = enable_thread(FALSE); |
6c45e622 MW |
769 | if (hdr->magic != MEMORY_HEADER_MAGIC || |
770 | tail->magic != MEMORY_TAIL_MAGIC) | |
5113680f | 771 | { |
17211b6b | 772 | lock->lock(lock); |
587ebae7 MW |
773 | for (current = &first_header; current != NULL; current = current->next) |
774 | { | |
775 | if (current == hdr) | |
776 | { | |
777 | found = TRUE; | |
778 | break; | |
779 | } | |
780 | } | |
17211b6b | 781 | lock->unlock(lock); |
587ebae7 MW |
782 | if (found) |
783 | { | |
784 | /* memory was allocated by our hooks but is corrupted */ | |
785 | fprintf(stderr, "freeing corrupted memory (%p): " | |
786 | "header magic 0x%x, tail magic 0x%x:\n", | |
787 | ptr, hdr->magic, tail->magic); | |
788 | } | |
789 | else | |
790 | { | |
791 | /* memory was not allocated by our hooks */ | |
17211b6b | 792 | fprintf(stderr, "freeing invalid memory (%p)\n", ptr); |
587ebae7 | 793 | } |
549eba30 | 794 | backtrace = backtrace_create(2); |
091d1780 | 795 | backtrace->log(backtrace, stderr, TRUE); |
f7237cf3 | 796 | backtrace->destroy(backtrace); |
5113680f | 797 | } |
6c45e622 | 798 | else |
b7ef3f62 | 799 | { |
6c45e622 | 800 | /* remove item from list */ |
17211b6b | 801 | lock->lock(lock); |
6c45e622 MW |
802 | if (hdr->next) |
803 | { | |
804 | hdr->next->previous = hdr->previous; | |
805 | } | |
806 | hdr->previous->next = hdr->next; | |
17211b6b MW |
807 | lock->unlock(lock); |
808 | ||
f7237cf3 | 809 | hdr->backtrace->destroy(hdr->backtrace); |
7daf5226 | 810 | |
6c45e622 | 811 | /* clear MAGIC, set mem to something remarkable */ |
587ebae7 MW |
812 | memset(hdr, MEMORY_FREE_PATTERN, |
813 | sizeof(memory_header_t) + hdr->bytes + sizeof(memory_tail_t)); | |
7daf5226 | 814 | |
17211b6b | 815 | real_free(hdr); |
6c45e622 | 816 | } |
17211b6b | 817 | enable_thread(before); |
5113680f MW |
818 | } |
819 | ||
820 | /** | |
17211b6b | 821 | * Wrapped realloc() function |
5113680f | 822 | */ |
d8f6f0c0 | 823 | HOOK(void*, realloc, void *old, size_t bytes) |
5113680f | 824 | { |
a401efd0 | 825 | memory_header_t *hdr; |
b7ef3f62 | 826 | memory_tail_t *tail; |
f7237cf3 | 827 | backtrace_t *backtrace; |
17211b6b | 828 | bool before; |
7daf5226 | 829 | |
17211b6b MW |
830 | if (!enabled || thread_disabled->get(thread_disabled)) |
831 | { | |
832 | return real_realloc(old, bytes); | |
833 | } | |
5113680f MW |
834 | /* allow reallocation of NULL */ |
835 | if (old == NULL) | |
836 | { | |
17211b6b | 837 | return malloc(bytes); |
5113680f | 838 | } |
7daf5226 | 839 | |
a401efd0 | 840 | hdr = old - sizeof(memory_header_t); |
b7ef3f62 | 841 | tail = old + hdr->bytes; |
7daf5226 | 842 | |
6c45e622 MW |
843 | if (hdr->magic != MEMORY_HEADER_MAGIC || |
844 | tail->magic != MEMORY_TAIL_MAGIC) | |
b7ef3f62 | 845 | { |
6499354e MW |
846 | fprintf(stderr, "reallocating invalid memory (%p):\n" |
847 | "header magic 0x%x:\n", old, hdr->magic); | |
549eba30 | 848 | backtrace = backtrace_create(2); |
091d1780 | 849 | backtrace->log(backtrace, stderr, TRUE); |
f7237cf3 | 850 | backtrace->destroy(backtrace); |
b7ef3f62 | 851 | } |
6499354e MW |
852 | else |
853 | { | |
854 | /* clear tail magic, allocate, set tail magic */ | |
855 | memset(&tail->magic, MEMORY_ALLOC_PATTERN, sizeof(tail->magic)); | |
856 | } | |
17211b6b MW |
857 | hdr = real_realloc(hdr, |
858 | sizeof(memory_header_t) + bytes + sizeof(memory_tail_t)); | |
b7ef3f62 MW |
859 | tail = ((void*)hdr) + bytes + sizeof(memory_header_t); |
860 | tail->magic = MEMORY_TAIL_MAGIC; | |
6c45e622 | 861 | |
a401efd0 MW |
862 | /* update statistics */ |
863 | hdr->bytes = bytes; | |
17211b6b MW |
864 | |
865 | before = enable_thread(FALSE); | |
f7237cf3 | 866 | hdr->backtrace->destroy(hdr->backtrace); |
549eba30 | 867 | hdr->backtrace = backtrace_create(2); |
17211b6b | 868 | enable_thread(before); |
6c45e622 | 869 | |
a401efd0 | 870 | /* update header of linked list neighbours */ |
17211b6b | 871 | lock->lock(lock); |
a401efd0 MW |
872 | if (hdr->next) |
873 | { | |
874 | hdr->next->previous = hdr; | |
875 | } | |
876 | hdr->previous->next = hdr; | |
17211b6b MW |
877 | lock->unlock(lock); |
878 | ||
a401efd0 | 879 | return hdr + 1; |
5113680f MW |
880 | } |
881 | ||
42e0f26e MW |
882 | METHOD(leak_detective_t, destroy, void, |
883 | private_leak_detective_t *this) | |
5113680f | 884 | { |
17211b6b MW |
885 | disable_leak_detective(); |
886 | lock->destroy(lock); | |
887 | thread_disabled->destroy(thread_disabled); | |
552cc11b | 888 | free(this); |
6b3292da MW |
889 | } |
890 | ||
552cc11b MW |
891 | /* |
892 | * see header file | |
73760ca5 | 893 | */ |
552cc11b | 894 | leak_detective_t *leak_detective_create() |
73760ca5 | 895 | { |
42e0f26e MW |
896 | private_leak_detective_t *this; |
897 | ||
898 | INIT(this, | |
899 | .public = { | |
900 | .report = _report, | |
01e15ab5 | 901 | .leaks = _leaks, |
fce3b5c3 | 902 | .usage = _usage, |
b46776ae | 903 | .set_state = _set_state, |
42e0f26e MW |
904 | .destroy = _destroy, |
905 | }, | |
906 | ); | |
7daf5226 | 907 | |
17211b6b MW |
908 | lock = spinlock_create(); |
909 | thread_disabled = thread_value_create(NULL); | |
910 | ||
7e3f6299 MW |
911 | init_static_allocations(); |
912 | ||
552cc11b | 913 | if (getenv("LEAK_DETECTIVE_DISABLE") == NULL) |
73760ca5 | 914 | { |
d8f6f0c0 MW |
915 | if (register_hooks()) |
916 | { | |
917 | enable_leak_detective(); | |
918 | } | |
73760ca5 | 919 | } |
552cc11b | 920 | return &this->public; |
73760ca5 | 921 | } |