]>
Commit | Line | Data |
---|---|---|
5113680f | 1 | /* |
552cc11b | 2 | * Copyright (C) 2006-2008 Martin Willi |
5113680f MW |
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 | */ | |
552cc11b | 15 | |
7a485e90 MW |
16 | #define _GNU_SOURCE |
17 | #include <sched.h> | |
5113680f | 18 | #include <stddef.h> |
5113680f MW |
19 | #include <string.h> |
20 | #include <stdio.h> | |
21 | #include <malloc.h> | |
5113680f MW |
22 | #include <signal.h> |
23 | #include <sys/socket.h> | |
24 | #include <netinet/in.h> | |
25 | #include <arpa/inet.h> | |
1d025fbc | 26 | #include <unistd.h> |
8bc96e08 | 27 | #include <syslog.h> |
8bc96e08 | 28 | #include <pthread.h> |
b1904247 | 29 | #include <netdb.h> |
552cc11b | 30 | #include <locale.h> |
5113680f MW |
31 | |
32 | #include "leak_detective.h" | |
33 | ||
60356f33 | 34 | #include <library.h> |
db7ef624 | 35 | #include <debug.h> |
f7237cf3 | 36 | #include <utils/backtrace.h> |
5113680f | 37 | |
552cc11b MW |
38 | typedef struct private_leak_detective_t private_leak_detective_t; |
39 | ||
40 | /** | |
41 | * private data of leak_detective | |
42 | */ | |
43 | struct private_leak_detective_t { | |
44 | ||
45 | /** | |
46 | * public functions | |
47 | */ | |
48 | leak_detective_t public; | |
49 | }; | |
5113680f MW |
50 | |
51 | /** | |
269f7f44 | 52 | * Magic value which helps to detect memory corruption. Yummy! |
5113680f | 53 | */ |
269f7f44 MW |
54 | #define MEMORY_HEADER_MAGIC 0x7ac0be11 |
55 | ||
b7ef3f62 MW |
56 | /** |
57 | * Magic written to tail of allocation | |
58 | */ | |
59 | #define MEMORY_TAIL_MAGIC 0xcafebabe | |
60 | ||
269f7f44 MW |
61 | /** |
62 | * Pattern which is filled in memory before freeing it | |
63 | */ | |
64 | #define MEMORY_FREE_PATTERN 0xFF | |
65 | ||
66 | /** | |
67 | * Pattern which is filled in newly allocated memory | |
68 | */ | |
69 | #define MEMORY_ALLOC_PATTERN 0xEE | |
70 | ||
5113680f | 71 | |
5113680f MW |
72 | static void install_hooks(void); |
73 | static void uninstall_hooks(void); | |
74 | static void *malloc_hook(size_t, const void *); | |
75 | static void *realloc_hook(void *, size_t, const void *); | |
76 | static void free_hook(void*, const void *); | |
73760ca5 | 77 | |
cce97b64 MW |
78 | void *(*old_malloc_hook)(size_t, const void *); |
79 | void *(*old_realloc_hook)(void *, size_t, const void *); | |
80 | void (*old_free_hook)(void*, const void *); | |
81 | ||
73760ca5 MW |
82 | static u_int count_malloc = 0; |
83 | static u_int count_free = 0; | |
84 | static u_int count_realloc = 0; | |
5113680f MW |
85 | |
86 | typedef struct memory_header_t memory_header_t; | |
b7ef3f62 | 87 | typedef struct memory_tail_t memory_tail_t; |
5113680f MW |
88 | |
89 | /** | |
90 | * Header which is prepended to each allocated memory block | |
91 | */ | |
92 | struct memory_header_t { | |
5113680f MW |
93 | |
94 | /** | |
95 | * Number of bytes following after the header | |
96 | */ | |
b7ef3f62 | 97 | u_int bytes; |
5113680f | 98 | |
5113680f MW |
99 | /** |
100 | * Pointer to previous entry in linked list | |
101 | */ | |
102 | memory_header_t *previous; | |
103 | ||
104 | /** | |
105 | * Pointer to next entry in linked list | |
106 | */ | |
107 | memory_header_t *next; | |
b7ef3f62 | 108 | |
f7237cf3 MW |
109 | /** |
110 | * backtrace taken during (re-)allocation | |
111 | */ | |
112 | backtrace_t *backtrace; | |
113 | ||
b7ef3f62 MW |
114 | /** |
115 | * magic bytes to detect bad free or heap underflow, MEMORY_HEADER_MAGIC | |
116 | */ | |
117 | u_int32_t magic; | |
118 | ||
119 | }__attribute__((__packed__)); | |
120 | ||
121 | /** | |
122 | * tail appended to each allocated memory block | |
123 | */ | |
124 | struct memory_tail_t { | |
125 | ||
126 | /** | |
127 | * Magic bytes to detect heap overflow, MEMORY_TAIL_MAGIC | |
128 | */ | |
129 | u_int32_t magic; | |
130 | ||
131 | }__attribute__((__packed__)); | |
5113680f MW |
132 | |
133 | /** | |
134 | * first mem header is just a dummy to chain | |
135 | * the others on it... | |
136 | */ | |
6b3292da | 137 | static memory_header_t first_header = { |
5113680f MW |
138 | magic: MEMORY_HEADER_MAGIC, |
139 | bytes: 0, | |
f7237cf3 | 140 | backtrace: NULL, |
5113680f MW |
141 | previous: NULL, |
142 | next: NULL | |
143 | }; | |
144 | ||
1d025fbc | 145 | /** |
6b3292da | 146 | * are the hooks currently installed? |
1d025fbc | 147 | */ |
6b3292da | 148 | static bool installed = FALSE; |
5113680f | 149 | |
986d23bd | 150 | /** |
552cc11b | 151 | * Leak report white list |
151168f6 | 152 | * |
552cc11b MW |
153 | * List of functions using static allocation buffers or should be suppressed |
154 | * otherwise on leak report. | |
986d23bd | 155 | */ |
552cc11b | 156 | char *whitelist[] = { |
f7237cf3 MW |
157 | /* backtraces, including own */ |
158 | "backtrace_create", | |
7939864d | 159 | /* pthread stuff */ |
552cc11b MW |
160 | "pthread_create", |
161 | "pthread_setspecific", | |
9eb85cff | 162 | "__pthread_setspecific", |
7939864d | 163 | /* glibc functions */ |
552cc11b | 164 | "mktime", |
e69f33f6 | 165 | "__gmtime_r", |
c198fc55 | 166 | "localtime_r", |
552cc11b MW |
167 | "tzset", |
168 | "inet_ntoa", | |
169 | "strerror", | |
170 | "getprotobynumber", | |
171 | "getservbyport", | |
172 | "getservbyname", | |
5a22a021 MW |
173 | "gethostbyname_r", |
174 | "gethostbyname2_r", | |
25b12c69 MW |
175 | "getpwnam_r", |
176 | "getgrnam_r", | |
552cc11b MW |
177 | "register_printf_function", |
178 | "syslog", | |
179 | "vsyslog", | |
552cc11b MW |
180 | "getaddrinfo", |
181 | "setlocale", | |
7939864d MW |
182 | /* ignore dlopen, as we do not dlclose to get proper leak reports */ |
183 | "dlopen", | |
a3d92a37 | 184 | "dlerror", |
1caa265c | 185 | "dlclose", |
7939864d | 186 | /* mysql functions */ |
552cc11b MW |
187 | "mysql_init_character_set", |
188 | "init_client_errs", | |
189 | "my_thread_init", | |
7939864d | 190 | /* fastcgi library */ |
cf4caefa | 191 | "FCGX_Init", |
7939864d MW |
192 | /* libxml */ |
193 | "xmlInitCharEncodingHandlers", | |
194 | "xmlInitParser", | |
195 | "xmlInitParserCtxt", | |
196 | /* ClearSilver */ | |
197 | "nerr_init", | |
915e04b2 MW |
198 | /* OpenSSL */ |
199 | "RSA_new_method", | |
200 | "DH_new_method", | |
fe5d7c43 | 201 | "ENGINE_load_builtin_engines", |
04425625 | 202 | "OPENSSL_config", |
986d23bd MW |
203 | }; |
204 | ||
205 | /** | |
552cc11b | 206 | * check if a stack frame contains functions listed above |
986d23bd | 207 | */ |
f7237cf3 | 208 | static bool is_whitelisted(backtrace_t *backtrace) |
986d23bd | 209 | { |
f7237cf3 MW |
210 | int i; |
211 | for (i = 0; i < sizeof(whitelist)/sizeof(char*); i++) | |
986d23bd | 212 | { |
f7237cf3 MW |
213 | if (backtrace->contains_function(backtrace, whitelist[i])) |
214 | { | |
215 | return TRUE; | |
986d23bd MW |
216 | } |
217 | } | |
218 | return FALSE; | |
219 | } | |
220 | ||
6b3292da MW |
221 | /** |
222 | * Report leaks at library destruction | |
223 | */ | |
224 | void report_leaks() | |
225 | { | |
226 | memory_header_t *hdr; | |
cf4caefa | 227 | int leaks = 0, whitelisted = 0; |
6b3292da | 228 | |
6b3292da MW |
229 | for (hdr = first_header.next; hdr != NULL; hdr = hdr->next) |
230 | { | |
f7237cf3 | 231 | if (is_whitelisted(hdr->backtrace)) |
cf4caefa MW |
232 | { |
233 | whitelisted++; | |
234 | } | |
235 | else | |
986d23bd | 236 | { |
552cc11b MW |
237 | fprintf(stderr, "Leak (%d bytes at %p):\n", hdr->bytes, hdr + 1); |
238 | /* skip the first frame, contains leak detective logic */ | |
f7237cf3 | 239 | hdr->backtrace->log(hdr->backtrace, stderr); |
986d23bd MW |
240 | leaks++; |
241 | } | |
6b3292da MW |
242 | } |
243 | ||
244 | switch (leaks) | |
245 | { | |
246 | case 0: | |
cf4caefa | 247 | fprintf(stderr, "No leaks detected"); |
6b3292da MW |
248 | break; |
249 | case 1: | |
cf4caefa | 250 | fprintf(stderr, "One leak detected"); |
6b3292da MW |
251 | break; |
252 | default: | |
cf4caefa | 253 | fprintf(stderr, "%d leaks detected", leaks); |
6b3292da MW |
254 | break; |
255 | } | |
cf4caefa | 256 | fprintf(stderr, ", %d suppressed by whitelist\n", whitelisted); |
6b3292da MW |
257 | } |
258 | ||
5113680f MW |
259 | /** |
260 | * Installs the malloc hooks, enables leak detection | |
261 | */ | |
6b3292da | 262 | static void install_hooks() |
5113680f | 263 | { |
8bc96e08 MW |
264 | if (!installed) |
265 | { | |
266 | old_malloc_hook = __malloc_hook; | |
267 | old_realloc_hook = __realloc_hook; | |
268 | old_free_hook = __free_hook; | |
269 | __malloc_hook = malloc_hook; | |
270 | __realloc_hook = realloc_hook; | |
271 | __free_hook = free_hook; | |
272 | installed = TRUE; | |
273 | } | |
5113680f MW |
274 | } |
275 | ||
276 | /** | |
277 | * Uninstalls the malloc hooks, disables leak detection | |
278 | */ | |
6b3292da | 279 | static void uninstall_hooks() |
5113680f | 280 | { |
8bc96e08 MW |
281 | if (installed) |
282 | { | |
283 | __malloc_hook = old_malloc_hook; | |
284 | __free_hook = old_free_hook; | |
285 | __realloc_hook = old_realloc_hook; | |
286 | installed = FALSE; | |
287 | } | |
5113680f MW |
288 | } |
289 | ||
290 | /** | |
291 | * Hook function for malloc() | |
292 | */ | |
6b3292da | 293 | void *malloc_hook(size_t bytes, const void *caller) |
5113680f MW |
294 | { |
295 | memory_header_t *hdr; | |
b7ef3f62 | 296 | memory_tail_t *tail; |
84b18d5f TB |
297 | pthread_t thread_id = pthread_self(); |
298 | int oldpolicy; | |
299 | struct sched_param oldparams, params; | |
300 | ||
301 | pthread_getschedparam(thread_id, &oldpolicy, &oldparams); | |
302 | ||
303 | params.__sched_priority = sched_get_priority_max(SCHED_FIFO); | |
304 | pthread_setschedparam(thread_id, SCHED_FIFO, ¶ms); | |
5113680f | 305 | |
73760ca5 | 306 | count_malloc++; |
5113680f | 307 | uninstall_hooks(); |
b7ef3f62 MW |
308 | hdr = malloc(sizeof(memory_header_t) + bytes + sizeof(memory_tail_t)); |
309 | tail = ((void*)hdr) + bytes + sizeof(memory_header_t); | |
986d23bd | 310 | /* set to something which causes crashes */ |
b7ef3f62 MW |
311 | memset(hdr, MEMORY_ALLOC_PATTERN, |
312 | sizeof(memory_header_t) + bytes + sizeof(memory_tail_t)); | |
5113680f MW |
313 | |
314 | hdr->magic = MEMORY_HEADER_MAGIC; | |
315 | hdr->bytes = bytes; | |
f7237cf3 | 316 | hdr->backtrace = backtrace_create(3); |
b7ef3f62 | 317 | tail->magic = MEMORY_TAIL_MAGIC; |
269f7f44 | 318 | install_hooks(); |
5113680f MW |
319 | |
320 | /* insert at the beginning of the list */ | |
321 | hdr->next = first_header.next; | |
322 | if (hdr->next) | |
323 | { | |
324 | hdr->next->previous = hdr; | |
325 | } | |
326 | hdr->previous = &first_header; | |
327 | first_header.next = hdr; | |
84b18d5f TB |
328 | |
329 | pthread_setschedparam(thread_id, oldpolicy, &oldparams); | |
330 | ||
5113680f MW |
331 | return hdr + 1; |
332 | } | |
333 | ||
334 | /** | |
335 | * Hook function for free() | |
336 | */ | |
6b3292da | 337 | void free_hook(void *ptr, const void *caller) |
5113680f | 338 | { |
b7ef3f62 MW |
339 | memory_header_t *hdr; |
340 | memory_tail_t *tail; | |
f7237cf3 | 341 | backtrace_t *backtrace; |
84b18d5f TB |
342 | pthread_t thread_id = pthread_self(); |
343 | int oldpolicy; | |
344 | struct sched_param oldparams, params; | |
345 | ||
5113680f MW |
346 | /* allow freeing of NULL */ |
347 | if (ptr == NULL) | |
348 | { | |
349 | return; | |
350 | } | |
b7ef3f62 MW |
351 | hdr = ptr - sizeof(memory_header_t); |
352 | tail = ptr + hdr->bytes; | |
5113680f | 353 | |
84b18d5f TB |
354 | pthread_getschedparam(thread_id, &oldpolicy, &oldparams); |
355 | ||
356 | params.__sched_priority = sched_get_priority_max(SCHED_FIFO); | |
357 | pthread_setschedparam(thread_id, SCHED_FIFO, ¶ms); | |
358 | ||
73760ca5 | 359 | count_free++; |
269f7f44 | 360 | uninstall_hooks(); |
6c45e622 MW |
361 | if (hdr->magic != MEMORY_HEADER_MAGIC || |
362 | tail->magic != MEMORY_TAIL_MAGIC) | |
5113680f | 363 | { |
6c45e622 MW |
364 | fprintf(stderr, "freeing invalid memory (%p): " |
365 | "header magic 0x%x, tail magic 0x%x:\n", | |
366 | ptr, hdr->magic, tail->magic); | |
f7237cf3 MW |
367 | backtrace = backtrace_create(3); |
368 | backtrace->log(backtrace, stderr); | |
369 | backtrace->destroy(backtrace); | |
5113680f | 370 | } |
6c45e622 | 371 | else |
b7ef3f62 | 372 | { |
6c45e622 MW |
373 | /* remove item from list */ |
374 | if (hdr->next) | |
375 | { | |
376 | hdr->next->previous = hdr->previous; | |
377 | } | |
378 | hdr->previous->next = hdr->next; | |
f7237cf3 MW |
379 | hdr->backtrace->destroy(hdr->backtrace); |
380 | ||
6c45e622 MW |
381 | /* clear MAGIC, set mem to something remarkable */ |
382 | memset(hdr, MEMORY_FREE_PATTERN, hdr->bytes + sizeof(memory_header_t)); | |
f7237cf3 | 383 | |
6c45e622 MW |
384 | free(hdr); |
385 | } | |
269f7f44 | 386 | |
5113680f | 387 | install_hooks(); |
6c90949f | 388 | pthread_setschedparam(thread_id, oldpolicy, &oldparams); |
5113680f MW |
389 | } |
390 | ||
391 | /** | |
392 | * Hook function for realloc() | |
393 | */ | |
6b3292da | 394 | void *realloc_hook(void *old, size_t bytes, const void *caller) |
5113680f | 395 | { |
a401efd0 | 396 | memory_header_t *hdr; |
b7ef3f62 | 397 | memory_tail_t *tail; |
f7237cf3 | 398 | backtrace_t *backtrace; |
84b18d5f TB |
399 | pthread_t thread_id = pthread_self(); |
400 | int oldpolicy; | |
401 | struct sched_param oldparams, params; | |
402 | ||
5113680f MW |
403 | /* allow reallocation of NULL */ |
404 | if (old == NULL) | |
405 | { | |
406 | return malloc_hook(bytes, caller); | |
407 | } | |
a401efd0 MW |
408 | |
409 | hdr = old - sizeof(memory_header_t); | |
b7ef3f62 | 410 | tail = old + hdr->bytes; |
269f7f44 | 411 | |
84b18d5f TB |
412 | pthread_getschedparam(thread_id, &oldpolicy, &oldparams); |
413 | ||
414 | params.__sched_priority = sched_get_priority_max(SCHED_FIFO); | |
415 | pthread_setschedparam(thread_id, SCHED_FIFO, ¶ms); | |
416 | ||
73760ca5 | 417 | count_realloc++; |
a401efd0 | 418 | uninstall_hooks(); |
6c45e622 MW |
419 | if (hdr->magic != MEMORY_HEADER_MAGIC || |
420 | tail->magic != MEMORY_TAIL_MAGIC) | |
b7ef3f62 | 421 | { |
6c45e622 MW |
422 | fprintf(stderr, "reallocating invalid memory (%p): " |
423 | "header magic 0x%x, tail magic 0x%x:\n", | |
424 | old, hdr->magic, tail->magic); | |
f7237cf3 MW |
425 | backtrace = backtrace_create(3); |
426 | backtrace->log(backtrace, stderr); | |
427 | backtrace->destroy(backtrace); | |
b7ef3f62 MW |
428 | } |
429 | /* clear tail magic, allocate, set tail magic */ | |
430 | memset(&tail->magic, MEMORY_ALLOC_PATTERN, sizeof(tail->magic)); | |
431 | hdr = realloc(hdr, sizeof(memory_header_t) + bytes + sizeof(memory_tail_t)); | |
432 | tail = ((void*)hdr) + bytes + sizeof(memory_header_t); | |
433 | tail->magic = MEMORY_TAIL_MAGIC; | |
6c45e622 | 434 | |
a401efd0 MW |
435 | /* update statistics */ |
436 | hdr->bytes = bytes; | |
f7237cf3 MW |
437 | hdr->backtrace->destroy(hdr->backtrace); |
438 | hdr->backtrace = backtrace_create(3); | |
6c45e622 | 439 | |
a401efd0 MW |
440 | /* update header of linked list neighbours */ |
441 | if (hdr->next) | |
442 | { | |
443 | hdr->next->previous = hdr; | |
444 | } | |
445 | hdr->previous->next = hdr; | |
446 | install_hooks(); | |
84b18d5f | 447 | pthread_setschedparam(thread_id, oldpolicy, &oldparams); |
a401efd0 | 448 | return hdr + 1; |
5113680f MW |
449 | } |
450 | ||
451 | /** | |
552cc11b | 452 | * Implementation of leak_detective_t.destroy |
5113680f | 453 | */ |
552cc11b | 454 | static void destroy(private_leak_detective_t *this) |
5113680f | 455 | { |
552cc11b | 456 | if (installed) |
9cc7a297 MW |
457 | { |
458 | uninstall_hooks(); | |
459 | report_leaks(); | |
460 | } | |
552cc11b | 461 | free(this); |
6b3292da MW |
462 | } |
463 | ||
552cc11b MW |
464 | /* |
465 | * see header file | |
73760ca5 | 466 | */ |
552cc11b | 467 | leak_detective_t *leak_detective_create() |
73760ca5 | 468 | { |
552cc11b | 469 | private_leak_detective_t *this = malloc_thing(private_leak_detective_t); |
73760ca5 | 470 | |
552cc11b | 471 | this->public.destroy = (void(*)(leak_detective_t*))destroy; |
9cc7a297 | 472 | |
552cc11b | 473 | if (getenv("LEAK_DETECTIVE_DISABLE") == NULL) |
73760ca5 | 474 | { |
7a485e90 MW |
475 | cpu_set_t mask; |
476 | ||
477 | CPU_ZERO(&mask); | |
478 | CPU_SET(0, &mask); | |
479 | ||
480 | if (sched_setaffinity(0, sizeof(cpu_set_t), &mask) != 0) | |
481 | { | |
482 | fprintf(stderr, "setting CPU affinity failed: %m"); | |
483 | } | |
484 | ||
e609b1cd | 485 | lib->leak_detective = TRUE; |
552cc11b | 486 | install_hooks(); |
73760ca5 | 487 | } |
552cc11b | 488 | return &this->public; |
73760ca5 MW |
489 | } |
490 |