3f564b0e2fa1d298b5c83165194b596fde45adeb
[strongswan.git] / src / libstrongswan / utils / leak_detective.c
1 /**
2 * @file leak_detective.c
3 *
4 * @brief Allocation hooks to find memory leaks.
5 */
6
7 /*
8 * Copyright (C) 2006 Martin Willi
9 * Hochschule fuer Technik Rapperswil
10 *
11 * This program is free software; you can redistribute it and/or modify it
12 * under the terms of the GNU General Public License as published by the
13 * Free Software Foundation; either version 2 of the License, or (at your
14 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
15 *
16 * This program is distributed in the hope that it will be useful, but
17 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
18 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
19 * for more details.
20 */
21
22 #include <stddef.h>
23 #include <string.h>
24 #include <stdio.h>
25 #include <malloc.h>
26 #include <signal.h>
27 #include <sys/socket.h>
28 #include <netinet/in.h>
29 #include <arpa/inet.h>
30 #include <dlfcn.h>
31 #include <unistd.h>
32 #include <syslog.h>
33 #include <pthread.h>
34 #include <netdb.h>
35 #include <printf.h>
36 #ifdef HAVE_BACKTRACE
37 # include <execinfo.h>
38 #endif /* HAVE_BACKTRACE */
39
40 #include "leak_detective.h"
41
42 #include <library.h>
43 #include <debug.h>
44
45 #ifdef LEAK_DETECTIVE
46
47 /**
48 * Magic value which helps to detect memory corruption. Yummy!
49 */
50 #define MEMORY_HEADER_MAGIC 0x7ac0be11
51
52 /**
53 * Pattern which is filled in memory before freeing it
54 */
55 #define MEMORY_FREE_PATTERN 0xFF
56
57 /**
58 * Pattern which is filled in newly allocated memory
59 */
60 #define MEMORY_ALLOC_PATTERN 0xEE
61
62
63 static void install_hooks(void);
64 static void uninstall_hooks(void);
65 static void *malloc_hook(size_t, const void *);
66 static void *realloc_hook(void *, size_t, const void *);
67 static void free_hook(void*, const void *);
68
69 static u_int count_malloc = 0;
70 static u_int count_free = 0;
71 static u_int count_realloc = 0;
72
73 typedef struct memory_header_t memory_header_t;
74
75 /**
76 * Header which is prepended to each allocated memory block
77 */
78 struct memory_header_t {
79 /**
80 * Magci byte which must(!) hold MEMORY_HEADER_MAGIC
81 */
82 u_int32_t magic;
83
84 /**
85 * Number of bytes following after the header
86 */
87 size_t bytes;
88
89 /**
90 * Stack frames at the time of allocation
91 */
92 void *stack_frames[STACK_FRAMES_COUNT];
93
94 /**
95 * Number of stacks frames obtained in stack_frames
96 */
97 int stack_frame_count;
98
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;
108 };
109
110 /**
111 * first mem header is just a dummy to chain
112 * the others on it...
113 */
114 static memory_header_t first_header = {
115 magic: MEMORY_HEADER_MAGIC,
116 bytes: 0,
117 stack_frame_count: 0,
118 previous: NULL,
119 next: NULL
120 };
121
122 /**
123 * standard hooks, used to temparily remove hooking
124 */
125 static void *old_malloc_hook, *old_realloc_hook, *old_free_hook;
126
127 /**
128 * are the hooks currently installed?
129 */
130 static bool installed = FALSE;
131
132 /**
133 * Mutex to exclusivly uninstall hooks, access heap list
134 */
135 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
136
137
138 /**
139 * log stack frames queried by backtrace()
140 * TODO: Dump symbols of static functions. This could be done with
141 * the addr2line utility or the GNU BFD Library...
142 */
143 static void log_stack_frames(void **stack_frames, int stack_frame_count)
144 {
145 #ifdef HAVE_BACKTRACE
146 char **strings;
147 size_t i;
148
149 strings = backtrace_symbols (stack_frames, stack_frame_count);
150
151 DBG1(" dumping %d stack frame addresses", stack_frame_count);
152
153 for (i = 0; i < stack_frame_count; i++)
154 {
155 DBG1(" %s", strings[i]);
156 }
157 free (strings);
158 #endif /* HAVE_BACKTRACE */
159 }
160
161 /**
162 * Whitelist, which contains address ranges in stack frames ignored when leaking.
163 *
164 * This is necessary, as some function use allocation hacks (static buffers)
165 * and so on, which we want to suppress on leak reports.
166 *
167 * The range_size is calculated using the readelf utility, e.g.:
168 * readelf -s /lib/glibc.so.6
169 * The values are for glibc-2.4 and may or may not be correct on other systems.
170 */
171 typedef struct whitelist_t whitelist_t;
172
173 struct whitelist_t {
174 void* range_start;
175 size_t range_size;
176 };
177
178 #ifdef LIBCURL
179 void *Curl_getaddrinfo(void);
180 #endif
181
182 whitelist_t whitelist[] = {
183 {pthread_create, 2542},
184 {pthread_setspecific, 217},
185 {mktime, 60},
186 {tzset, 123},
187 {inet_ntoa, 249},
188 {strerror, 180},
189 {getprotobynumber, 291},
190 {getservbyport, 311},
191 {register_printf_function, 159},
192 {syslog, 45},
193 {dlopen, 109},
194 /* from /usr/lib/libcurl.so.3 */
195 {Curl_getaddrinfo, 480},
196 };
197
198 /**
199 * Check if this stack frame is whitelisted.
200 */
201 static bool is_whitelisted(void **stack_frames, int stack_frame_count)
202 {
203 int i, j;
204
205 for (i=0; i< stack_frame_count; i++)
206 {
207 for (j=0; j<sizeof(whitelist)/sizeof(whitelist_t); j++)
208 {
209 if (stack_frames[i] >= whitelist[j].range_start &&
210 stack_frames[i] <= (whitelist[j].range_start + whitelist[j].range_size))
211 {
212 return TRUE;
213 }
214 }
215 }
216 return FALSE;
217 }
218
219 /**
220 * Report leaks at library destruction
221 */
222 void report_leaks()
223 {
224 memory_header_t *hdr;
225 int leaks = 0;
226
227 for (hdr = first_header.next; hdr != NULL; hdr = hdr->next)
228 {
229 if (!is_whitelisted(hdr->stack_frames, hdr->stack_frame_count))
230 {
231 DBG1("Leak (%d bytes at %p):", hdr->bytes, hdr + 1);
232 log_stack_frames(hdr->stack_frames, hdr->stack_frame_count);
233 leaks++;
234 }
235 }
236
237 switch (leaks)
238 {
239 case 0:
240 DBG1("No leaks detected");
241 break;
242 case 1:
243 DBG1("One leak detected");
244 break;
245 default:
246 DBG1("%d leaks detected", leaks);
247 break;
248 }
249 }
250
251 /**
252 * Installs the malloc hooks, enables leak detection
253 */
254 static void install_hooks()
255 {
256 if (!installed)
257 {
258 old_malloc_hook = __malloc_hook;
259 old_realloc_hook = __realloc_hook;
260 old_free_hook = __free_hook;
261 __malloc_hook = malloc_hook;
262 __realloc_hook = realloc_hook;
263 __free_hook = free_hook;
264 installed = TRUE;
265 }
266 }
267
268 /**
269 * Uninstalls the malloc hooks, disables leak detection
270 */
271 static void uninstall_hooks()
272 {
273 if (installed)
274 {
275 __malloc_hook = old_malloc_hook;
276 __free_hook = old_free_hook;
277 __realloc_hook = old_realloc_hook;
278 installed = FALSE;
279 }
280 }
281
282 /**
283 * Hook function for malloc()
284 */
285 void *malloc_hook(size_t bytes, const void *caller)
286 {
287 memory_header_t *hdr;
288
289 pthread_mutex_lock(&mutex);
290 count_malloc++;
291 uninstall_hooks();
292 hdr = malloc(bytes + sizeof(memory_header_t));
293 /* set to something which causes crashes */
294 memset(hdr, MEMORY_ALLOC_PATTERN, bytes + sizeof(memory_header_t));
295
296 hdr->magic = MEMORY_HEADER_MAGIC;
297 hdr->bytes = bytes;
298 hdr->stack_frame_count = backtrace(hdr->stack_frames, STACK_FRAMES_COUNT);
299 install_hooks();
300
301 /* insert at the beginning of the list */
302 hdr->next = first_header.next;
303 if (hdr->next)
304 {
305 hdr->next->previous = hdr;
306 }
307 hdr->previous = &first_header;
308 first_header.next = hdr;
309 pthread_mutex_unlock(&mutex);
310 return hdr + 1;
311 }
312
313 /**
314 * Hook function for free()
315 */
316 void free_hook(void *ptr, const void *caller)
317 {
318 void *stack_frames[STACK_FRAMES_COUNT];
319 int stack_frame_count;
320 memory_header_t *hdr = ptr - sizeof(memory_header_t);
321
322 /* allow freeing of NULL */
323 if (ptr == NULL)
324 {
325 return;
326 }
327
328 pthread_mutex_lock(&mutex);
329 count_free++;
330 uninstall_hooks();
331 if (hdr->magic != MEMORY_HEADER_MAGIC)
332 {
333 DBG1("freeing of invalid memory (%p, MAGIC 0x%x != 0x%x):",
334 ptr, hdr->magic, MEMORY_HEADER_MAGIC);
335 stack_frame_count = backtrace(stack_frames, STACK_FRAMES_COUNT);
336 log_stack_frames(stack_frames, stack_frame_count);
337 install_hooks();
338 pthread_mutex_unlock(&mutex);
339 return;
340 }
341
342 /* remove item from list */
343 if (hdr->next)
344 {
345 hdr->next->previous = hdr->previous;
346 }
347 hdr->previous->next = hdr->next;
348
349 /* clear MAGIC, set mem to something remarkable */
350 memset(hdr, MEMORY_FREE_PATTERN, hdr->bytes + sizeof(memory_header_t));
351
352 free(hdr);
353 install_hooks();
354 pthread_mutex_unlock(&mutex);
355 }
356
357 /**
358 * Hook function for realloc()
359 */
360 void *realloc_hook(void *old, size_t bytes, const void *caller)
361 {
362 memory_header_t *hdr;
363 void *stack_frames[STACK_FRAMES_COUNT];
364 int stack_frame_count;
365
366 /* allow reallocation of NULL */
367 if (old == NULL)
368 {
369 return malloc_hook(bytes, caller);
370 }
371
372 hdr = old - sizeof(memory_header_t);
373
374 pthread_mutex_lock(&mutex);
375 count_realloc++;
376 uninstall_hooks();
377 if (hdr->magic != MEMORY_HEADER_MAGIC)
378 {
379 DBG1("reallocation of invalid memory (%p):", old);
380 stack_frame_count = backtrace(stack_frames, STACK_FRAMES_COUNT);
381 log_stack_frames(stack_frames, stack_frame_count);
382 install_hooks();
383 pthread_mutex_unlock(&mutex);
384 raise(SIGKILL);
385 return NULL;
386 }
387
388 hdr = realloc(hdr, bytes + sizeof(memory_header_t));
389
390 /* update statistics */
391 hdr->bytes = bytes;
392 hdr->stack_frame_count = backtrace(hdr->stack_frames, STACK_FRAMES_COUNT);
393
394 /* update header of linked list neighbours */
395 if (hdr->next)
396 {
397 hdr->next->previous = hdr;
398 }
399 hdr->previous->next = hdr;
400 install_hooks();
401 pthread_mutex_unlock(&mutex);
402 return hdr + 1;
403 }
404
405 /**
406 * Setup leak detective
407 */
408 void __attribute__ ((constructor)) leak_detective_init()
409 {
410 install_hooks();
411 }
412
413 /**
414 * Clean up leak detective
415 */
416 void __attribute__ ((destructor)) leak_detective_cleanup()
417 {
418 uninstall_hooks();
419 report_leaks();
420 }
421
422 /**
423 * Log memory allocation statistics
424 */
425 void leak_detective_status(FILE *stream)
426 {
427 u_int blocks = 0;
428 size_t bytes = 0;
429 memory_header_t *hdr = &first_header;
430
431 pthread_mutex_lock(&mutex);
432 while ((hdr = hdr->next))
433 {
434 blocks++;
435 bytes += hdr->bytes;
436 }
437 pthread_mutex_unlock(&mutex);
438
439 fprintf(stream, "allocation statistics:\n");
440 fprintf(stream, " call stats: malloc: %d, free: %d, realloc: %d\n",
441 count_malloc, count_free, count_realloc);
442 fprintf(stream, " allocated %d blocks, total size %d bytes (avg. %d bytes)\n",
443 blocks, bytes, bytes/blocks);
444 }
445
446 #else /* !LEAK_DETECTION */
447
448 /**
449 * Dummy when !using LEAK_DETECTIVE
450 */
451 void leak_detective_status(FILE *stream)
452 {
453
454 }
455
456 #endif /* LEAK_DETECTION */