]>
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. | |
552cc11b MW |
14 | * |
15 | * $Id$ | |
5113680f MW |
16 | */ |
17 | ||
552cc11b MW |
18 | #ifdef HAVE_DLADDR |
19 | # define _GNU_SOURCE | |
20 | # include <dlfcn.h> | |
21 | #endif /* HAVE_DLADDR */ | |
22 | ||
5113680f | 23 | #include <stddef.h> |
5113680f MW |
24 | #include <string.h> |
25 | #include <stdio.h> | |
26 | #include <malloc.h> | |
5113680f MW |
27 | #include <signal.h> |
28 | #include <sys/socket.h> | |
29 | #include <netinet/in.h> | |
30 | #include <arpa/inet.h> | |
31 | #include <dlfcn.h> | |
1d025fbc | 32 | #include <unistd.h> |
8bc96e08 | 33 | #include <syslog.h> |
8bc96e08 | 34 | #include <pthread.h> |
b1904247 | 35 | #include <netdb.h> |
151168f6 | 36 | #include <printf.h> |
552cc11b | 37 | #include <locale.h> |
5347a84f MW |
38 | #ifdef HAVE_BACKTRACE |
39 | # include <execinfo.h> | |
40 | #endif /* HAVE_BACKTRACE */ | |
5113680f MW |
41 | |
42 | #include "leak_detective.h" | |
43 | ||
60356f33 | 44 | #include <library.h> |
db7ef624 | 45 | #include <debug.h> |
5113680f | 46 | |
552cc11b MW |
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 | }; | |
5113680f MW |
59 | |
60 | /** | |
269f7f44 | 61 | * Magic value which helps to detect memory corruption. Yummy! |
5113680f | 62 | */ |
269f7f44 MW |
63 | #define MEMORY_HEADER_MAGIC 0x7ac0be11 |
64 | ||
65 | /** | |
66 | * Pattern which is filled in memory before freeing it | |
67 | */ | |
68 | #define MEMORY_FREE_PATTERN 0xFF | |
69 | ||
70 | /** | |
71 | * Pattern which is filled in newly allocated memory | |
72 | */ | |
73 | #define MEMORY_ALLOC_PATTERN 0xEE | |
74 | ||
5113680f | 75 | |
5113680f MW |
76 | static void install_hooks(void); |
77 | static void uninstall_hooks(void); | |
78 | static void *malloc_hook(size_t, const void *); | |
79 | static void *realloc_hook(void *, size_t, const void *); | |
80 | static void free_hook(void*, const void *); | |
73760ca5 MW |
81 | |
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; | |
87 | ||
88 | /** | |
89 | * Header which is prepended to each allocated memory block | |
90 | */ | |
91 | struct memory_header_t { | |
92 | /** | |
93 | * Magci byte which must(!) hold MEMORY_HEADER_MAGIC | |
94 | */ | |
95 | u_int32_t magic; | |
96 | ||
97 | /** | |
98 | * Number of bytes following after the header | |
99 | */ | |
100 | size_t bytes; | |
101 | ||
102 | /** | |
103 | * Stack frames at the time of allocation | |
104 | */ | |
105 | void *stack_frames[STACK_FRAMES_COUNT]; | |
106 | ||
107 | /** | |
108 | * Number of stacks frames obtained in stack_frames | |
109 | */ | |
110 | int stack_frame_count; | |
111 | ||
112 | /** | |
113 | * Pointer to previous entry in linked list | |
114 | */ | |
115 | memory_header_t *previous; | |
116 | ||
117 | /** | |
118 | * Pointer to next entry in linked list | |
119 | */ | |
120 | memory_header_t *next; | |
121 | }; | |
122 | ||
123 | /** | |
124 | * first mem header is just a dummy to chain | |
125 | * the others on it... | |
126 | */ | |
6b3292da | 127 | static memory_header_t first_header = { |
5113680f MW |
128 | magic: MEMORY_HEADER_MAGIC, |
129 | bytes: 0, | |
130 | stack_frame_count: 0, | |
131 | previous: NULL, | |
132 | next: NULL | |
133 | }; | |
134 | ||
135 | /** | |
136 | * standard hooks, used to temparily remove hooking | |
137 | */ | |
6b3292da | 138 | static void *old_malloc_hook, *old_realloc_hook, *old_free_hook; |
5113680f | 139 | |
1d025fbc | 140 | /** |
6b3292da | 141 | * are the hooks currently installed? |
1d025fbc | 142 | */ |
6b3292da | 143 | static bool installed = FALSE; |
5113680f | 144 | |
8bc96e08 | 145 | /** |
6b3292da | 146 | * Mutex to exclusivly uninstall hooks, access heap list |
8bc96e08 | 147 | */ |
fcfeb322 | 148 | static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; |
6b3292da | 149 | |
5113680f MW |
150 | |
151 | /** | |
152 | * log stack frames queried by backtrace() | |
151168f6 MW |
153 | * TODO: Dump symbols of static functions. This could be done with |
154 | * the addr2line utility or the GNU BFD Library... | |
5113680f | 155 | */ |
6b3292da | 156 | static void log_stack_frames(void **stack_frames, int stack_frame_count) |
5113680f | 157 | { |
e696757c | 158 | #ifdef HAVE_BACKTRACE |
5113680f MW |
159 | char **strings; |
160 | size_t i; | |
161 | ||
552cc11b | 162 | strings = backtrace_symbols(stack_frames, stack_frame_count); |
5113680f | 163 | |
552cc11b | 164 | fprintf(stderr, " dumping %d stack frame addresses\n", stack_frame_count); |
5113680f MW |
165 | |
166 | for (i = 0; i < stack_frame_count; i++) | |
167 | { | |
552cc11b MW |
168 | #ifdef HAVE_DLADDR |
169 | Dl_info info; | |
170 | ||
171 | /* TODO: this is quite hackish, but it works. A more proper solution | |
172 | * would execve addr2strongline and pipe the output to DBG1() */ | |
173 | if (dladdr(stack_frames[i], &info)) | |
174 | { | |
175 | char cmd[1024]; | |
176 | void *ptr = stack_frames[i]; | |
177 | ||
178 | if (strstr(info.dli_fname, ".so")) | |
179 | { | |
180 | ptr = (void*)(stack_frames[i] - info.dli_fbase); | |
181 | } | |
182 | snprintf(cmd, sizeof(cmd), "addr2line -e %s %p", info.dli_fname, ptr); | |
183 | if (info.dli_sname) | |
184 | { | |
185 | fprintf(stderr, " \e[33m%s\e[0m @ %p (\e[31m%s+0x%x\e[0m) [%p]\n", | |
186 | info.dli_fname, info.dli_fbase, info.dli_sname, | |
187 | stack_frames[i] - info.dli_saddr, stack_frames[i]); | |
188 | } | |
189 | else | |
190 | { | |
191 | fprintf(stderr, " \e[33m%s\e[0m @ %p [%p]\n", info.dli_fname, | |
192 | info.dli_fbase, stack_frames[i]); | |
193 | } | |
194 | fprintf(stderr, " -> \e[32m"); | |
195 | system(cmd); | |
196 | fprintf(stderr, "\e[0m"); | |
197 | } | |
198 | else | |
199 | #endif /* HAVE_DLADDR */ | |
200 | { | |
201 | fprintf(stderr, " %s\n", strings[i]); | |
202 | } | |
5113680f MW |
203 | } |
204 | free (strings); | |
e696757c | 205 | #endif /* HAVE_BACKTRACE */ |
5113680f MW |
206 | } |
207 | ||
986d23bd | 208 | /** |
552cc11b | 209 | * Leak report white list |
151168f6 | 210 | * |
552cc11b MW |
211 | * List of functions using static allocation buffers or should be suppressed |
212 | * otherwise on leak report. | |
986d23bd | 213 | */ |
552cc11b MW |
214 | char *whitelist[] = { |
215 | "pthread_create", | |
216 | "pthread_setspecific", | |
217 | "mktime", | |
218 | "tzset", | |
219 | "inet_ntoa", | |
220 | "strerror", | |
221 | "getprotobynumber", | |
222 | "getservbyport", | |
223 | "getservbyname", | |
224 | "register_printf_function", | |
225 | "syslog", | |
226 | "vsyslog", | |
227 | "dlopen", | |
228 | "getaddrinfo", | |
229 | "setlocale", | |
230 | "mysql_init_character_set", | |
231 | "init_client_errs", | |
232 | "my_thread_init", | |
cf4caefa | 233 | "FCGX_Init", |
986d23bd MW |
234 | }; |
235 | ||
236 | /** | |
552cc11b | 237 | * check if a stack frame contains functions listed above |
986d23bd MW |
238 | */ |
239 | static bool is_whitelisted(void **stack_frames, int stack_frame_count) | |
240 | { | |
241 | int i, j; | |
242 | ||
552cc11b | 243 | #ifdef HAVE_DLADDR |
986d23bd MW |
244 | for (i=0; i< stack_frame_count; i++) |
245 | { | |
552cc11b MW |
246 | Dl_info info; |
247 | ||
248 | if (dladdr(stack_frames[i], &info) && info.dli_sname) | |
249 | { | |
250 | for (j = 0; j < sizeof(whitelist)/sizeof(char*); j++) | |
986d23bd | 251 | { |
552cc11b MW |
252 | if (streq(info.dli_sname, whitelist[j])) |
253 | { | |
254 | return TRUE; | |
255 | } | |
986d23bd MW |
256 | } |
257 | } | |
258 | } | |
552cc11b | 259 | #endif /* HAVE_DLADDR */ |
986d23bd MW |
260 | return FALSE; |
261 | } | |
262 | ||
6b3292da MW |
263 | /** |
264 | * Report leaks at library destruction | |
265 | */ | |
266 | void report_leaks() | |
267 | { | |
268 | memory_header_t *hdr; | |
cf4caefa | 269 | int leaks = 0, whitelisted = 0; |
6b3292da | 270 | |
6b3292da MW |
271 | for (hdr = first_header.next; hdr != NULL; hdr = hdr->next) |
272 | { | |
cf4caefa MW |
273 | if (is_whitelisted(hdr->stack_frames, hdr->stack_frame_count)) |
274 | { | |
275 | whitelisted++; | |
276 | } | |
277 | else | |
986d23bd | 278 | { |
552cc11b MW |
279 | fprintf(stderr, "Leak (%d bytes at %p):\n", hdr->bytes, hdr + 1); |
280 | /* skip the first frame, contains leak detective logic */ | |
281 | log_stack_frames(hdr->stack_frames + 1, hdr->stack_frame_count - 1); | |
986d23bd MW |
282 | leaks++; |
283 | } | |
6b3292da MW |
284 | } |
285 | ||
286 | switch (leaks) | |
287 | { | |
288 | case 0: | |
cf4caefa | 289 | fprintf(stderr, "No leaks detected"); |
6b3292da MW |
290 | break; |
291 | case 1: | |
cf4caefa | 292 | fprintf(stderr, "One leak detected"); |
6b3292da MW |
293 | break; |
294 | default: | |
cf4caefa | 295 | fprintf(stderr, "%d leaks detected", leaks); |
6b3292da MW |
296 | break; |
297 | } | |
cf4caefa | 298 | fprintf(stderr, ", %d suppressed by whitelist\n", whitelisted); |
6b3292da MW |
299 | } |
300 | ||
5113680f MW |
301 | /** |
302 | * Installs the malloc hooks, enables leak detection | |
303 | */ | |
6b3292da | 304 | static void install_hooks() |
5113680f | 305 | { |
8bc96e08 MW |
306 | if (!installed) |
307 | { | |
308 | old_malloc_hook = __malloc_hook; | |
309 | old_realloc_hook = __realloc_hook; | |
310 | old_free_hook = __free_hook; | |
311 | __malloc_hook = malloc_hook; | |
312 | __realloc_hook = realloc_hook; | |
313 | __free_hook = free_hook; | |
314 | installed = TRUE; | |
315 | } | |
5113680f MW |
316 | } |
317 | ||
318 | /** | |
319 | * Uninstalls the malloc hooks, disables leak detection | |
320 | */ | |
6b3292da | 321 | static void uninstall_hooks() |
5113680f | 322 | { |
8bc96e08 MW |
323 | if (installed) |
324 | { | |
325 | __malloc_hook = old_malloc_hook; | |
326 | __free_hook = old_free_hook; | |
327 | __realloc_hook = old_realloc_hook; | |
328 | installed = FALSE; | |
329 | } | |
5113680f MW |
330 | } |
331 | ||
332 | /** | |
333 | * Hook function for malloc() | |
334 | */ | |
6b3292da | 335 | void *malloc_hook(size_t bytes, const void *caller) |
5113680f MW |
336 | { |
337 | memory_header_t *hdr; | |
338 | ||
339 | pthread_mutex_lock(&mutex); | |
73760ca5 | 340 | count_malloc++; |
5113680f MW |
341 | uninstall_hooks(); |
342 | hdr = malloc(bytes + sizeof(memory_header_t)); | |
986d23bd | 343 | /* set to something which causes crashes */ |
269f7f44 | 344 | memset(hdr, MEMORY_ALLOC_PATTERN, bytes + sizeof(memory_header_t)); |
5113680f MW |
345 | |
346 | hdr->magic = MEMORY_HEADER_MAGIC; | |
347 | hdr->bytes = bytes; | |
348 | hdr->stack_frame_count = backtrace(hdr->stack_frames, STACK_FRAMES_COUNT); | |
269f7f44 | 349 | install_hooks(); |
5113680f MW |
350 | |
351 | /* insert at the beginning of the list */ | |
352 | hdr->next = first_header.next; | |
353 | if (hdr->next) | |
354 | { | |
355 | hdr->next->previous = hdr; | |
356 | } | |
357 | hdr->previous = &first_header; | |
358 | first_header.next = hdr; | |
5113680f MW |
359 | pthread_mutex_unlock(&mutex); |
360 | return hdr + 1; | |
361 | } | |
362 | ||
363 | /** | |
364 | * Hook function for free() | |
365 | */ | |
6b3292da | 366 | void free_hook(void *ptr, const void *caller) |
5113680f MW |
367 | { |
368 | void *stack_frames[STACK_FRAMES_COUNT]; | |
369 | int stack_frame_count; | |
370 | memory_header_t *hdr = ptr - sizeof(memory_header_t); | |
371 | ||
372 | /* allow freeing of NULL */ | |
373 | if (ptr == NULL) | |
374 | { | |
375 | return; | |
376 | } | |
377 | ||
378 | pthread_mutex_lock(&mutex); | |
73760ca5 | 379 | count_free++; |
269f7f44 | 380 | uninstall_hooks(); |
5113680f MW |
381 | if (hdr->magic != MEMORY_HEADER_MAGIC) |
382 | { | |
552cc11b MW |
383 | fprintf(stderr, "freeing of invalid memory (%p, MAGIC 0x%x != 0x%x):\n", |
384 | ptr, hdr->magic, MEMORY_HEADER_MAGIC); | |
5113680f MW |
385 | stack_frame_count = backtrace(stack_frames, STACK_FRAMES_COUNT); |
386 | log_stack_frames(stack_frames, stack_frame_count); | |
269f7f44 MW |
387 | install_hooks(); |
388 | pthread_mutex_unlock(&mutex); | |
5113680f MW |
389 | return; |
390 | } | |
5113680f MW |
391 | |
392 | /* remove item from list */ | |
393 | if (hdr->next) | |
394 | { | |
395 | hdr->next->previous = hdr->previous; | |
396 | } | |
397 | hdr->previous->next = hdr->next; | |
398 | ||
269f7f44 MW |
399 | /* clear MAGIC, set mem to something remarkable */ |
400 | memset(hdr, MEMORY_FREE_PATTERN, hdr->bytes + sizeof(memory_header_t)); | |
401 | ||
5113680f MW |
402 | free(hdr); |
403 | install_hooks(); | |
404 | pthread_mutex_unlock(&mutex); | |
405 | } | |
406 | ||
407 | /** | |
408 | * Hook function for realloc() | |
409 | */ | |
6b3292da | 410 | void *realloc_hook(void *old, size_t bytes, const void *caller) |
5113680f | 411 | { |
a401efd0 | 412 | memory_header_t *hdr; |
5113680f MW |
413 | void *stack_frames[STACK_FRAMES_COUNT]; |
414 | int stack_frame_count; | |
415 | ||
416 | /* allow reallocation of NULL */ | |
417 | if (old == NULL) | |
418 | { | |
419 | return malloc_hook(bytes, caller); | |
420 | } | |
a401efd0 MW |
421 | |
422 | hdr = old - sizeof(memory_header_t); | |
269f7f44 | 423 | |
a401efd0 | 424 | pthread_mutex_lock(&mutex); |
73760ca5 | 425 | count_realloc++; |
a401efd0 | 426 | uninstall_hooks(); |
5113680f MW |
427 | if (hdr->magic != MEMORY_HEADER_MAGIC) |
428 | { | |
552cc11b | 429 | fprintf(stderr, "reallocation of invalid memory (%p):\n", old); |
5113680f MW |
430 | stack_frame_count = backtrace(stack_frames, STACK_FRAMES_COUNT); |
431 | log_stack_frames(stack_frames, stack_frame_count); | |
269f7f44 MW |
432 | install_hooks(); |
433 | pthread_mutex_unlock(&mutex); | |
986d23bd | 434 | raise(SIGKILL); |
5113680f MW |
435 | return NULL; |
436 | } | |
437 | ||
a401efd0 MW |
438 | hdr = realloc(hdr, bytes + sizeof(memory_header_t)); |
439 | ||
440 | /* update statistics */ | |
441 | hdr->bytes = bytes; | |
442 | hdr->stack_frame_count = backtrace(hdr->stack_frames, STACK_FRAMES_COUNT); | |
5113680f | 443 | |
a401efd0 MW |
444 | /* update header of linked list neighbours */ |
445 | if (hdr->next) | |
446 | { | |
447 | hdr->next->previous = hdr; | |
448 | } | |
449 | hdr->previous->next = hdr; | |
450 | install_hooks(); | |
451 | pthread_mutex_unlock(&mutex); | |
452 | return hdr + 1; | |
5113680f MW |
453 | } |
454 | ||
455 | /** | |
552cc11b | 456 | * Implementation of leak_detective_t.destroy |
5113680f | 457 | */ |
552cc11b | 458 | static void destroy(private_leak_detective_t *this) |
5113680f | 459 | { |
552cc11b | 460 | if (installed) |
9cc7a297 MW |
461 | { |
462 | uninstall_hooks(); | |
463 | report_leaks(); | |
464 | } | |
552cc11b | 465 | free(this); |
6b3292da MW |
466 | } |
467 | ||
552cc11b MW |
468 | /* |
469 | * see header file | |
73760ca5 | 470 | */ |
552cc11b | 471 | leak_detective_t *leak_detective_create() |
73760ca5 | 472 | { |
552cc11b | 473 | private_leak_detective_t *this = malloc_thing(private_leak_detective_t); |
73760ca5 | 474 | |
552cc11b | 475 | this->public.destroy = (void(*)(leak_detective_t*))destroy; |
9cc7a297 | 476 | |
552cc11b | 477 | if (getenv("LEAK_DETECTIVE_DISABLE") == NULL) |
73760ca5 | 478 | { |
552cc11b | 479 | install_hooks(); |
73760ca5 | 480 | } |
552cc11b | 481 | return &this->public; |
73760ca5 MW |
482 | } |
483 |