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