Commit | Line | Data |
---|---|---|
1c353010 DG |
1 | /* |
2 | * lttng-memleak-finder.c | |
3 | * | |
4 | * LTTng memory leak finder | |
5 | * | |
6 | * Copyright (c) 2013 Mathieu Desnoyers <mathieu.desnoyers@efficios.com> | |
7 | * | |
8 | * This library is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU Lesser General Public | |
10 | * License as published by the Free Software Foundation; either | |
11 | * version 2.1 of the License, or (at your option) any later version. | |
12 | * | |
13 | * This library is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | * Lesser General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU Lesser General Public | |
19 | * License along with this library; if not, write to the Free Software | |
20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
21 | */ | |
22 | ||
23 | #define _GNU_SOURCE | |
24 | #include <dlfcn.h> | |
25 | #include <sys/types.h> | |
26 | #include <stdio.h> | |
27 | #include <pthread.h> | |
28 | #include <string.h> | |
29 | #include <stdint.h> | |
30 | #include <stdlib.h> | |
31 | #include <assert.h> | |
32 | #include "hlist.h" | |
33 | ||
34 | #include "jhash.h" | |
35 | ||
36 | static volatile int print_to_console; | |
37 | ||
38 | static pthread_mutex_t mh_mutex = PTHREAD_MUTEX_INITIALIZER; | |
39 | ||
40 | static void *(*callocp)(size_t, size_t); | |
41 | static void *(*mallocp)(size_t); | |
42 | static void *(*reallocp)(void *, size_t); | |
43 | static void *(*memalignp)(size_t, size_t); | |
44 | static void (*freep)(void *); | |
45 | ||
46 | static volatile int initialized; | |
47 | static __thread int thread_in_hook; | |
48 | ||
49 | #define STATIC_CALLOC_LEN 4096 | |
50 | static char static_calloc_buf[STATIC_CALLOC_LEN]; | |
51 | static size_t static_calloc_len; | |
52 | ||
53 | #define MH_HASH_BITS 20 /* 1 M entries, hardcoded for now */ | |
54 | #define MH_TABLE_SIZE (1 << MH_HASH_BITS) | |
55 | static struct cds_hlist_head mh_table[MH_TABLE_SIZE]; | |
56 | ||
57 | struct mh_entry { | |
58 | struct cds_hlist_node hlist; | |
59 | void *ptr; | |
60 | const void *alloc_caller; | |
61 | char *caller_symbol; | |
62 | size_t alloc_size; | |
63 | }; | |
64 | ||
65 | static struct mh_entry * | |
66 | get_mh(const void *ptr) | |
67 | { | |
68 | struct cds_hlist_head *head; | |
69 | struct cds_hlist_node *node; | |
70 | struct mh_entry *e; | |
71 | uint32_t hash; | |
72 | ||
73 | hash = jhash(&ptr, sizeof(ptr), 0); | |
74 | head = &mh_table[hash & (MH_TABLE_SIZE - 1)]; | |
75 | cds_hlist_for_each_entry(e, node, head, hlist) { | |
76 | if (ptr == e->ptr) | |
77 | return e; | |
78 | } | |
79 | return NULL; | |
80 | } | |
81 | ||
82 | static void | |
83 | add_mh(void *ptr, size_t alloc_size, const void *caller) | |
84 | { | |
85 | struct cds_hlist_head *head; | |
86 | struct cds_hlist_node *node; | |
87 | struct mh_entry *e; | |
88 | uint32_t hash; | |
89 | Dl_info info; | |
90 | ||
91 | if (!ptr) | |
92 | return; | |
93 | hash = jhash(&ptr, sizeof(ptr), 0); | |
94 | head = &mh_table[hash & (MH_TABLE_SIZE - 1)]; | |
95 | cds_hlist_for_each_entry(e, node, head, hlist) { | |
96 | if (ptr == e->ptr) { | |
97 | fprintf(stderr, "[warning] add_mh pointer %p is already there\n", | |
98 | ptr); | |
99 | //assert(0); /* already there */ | |
100 | } | |
101 | } | |
102 | e = malloc(sizeof(*e)); | |
103 | e->ptr = ptr; | |
104 | e->alloc_caller = caller; | |
105 | e->alloc_size = alloc_size; | |
106 | if (dladdr(caller, &info) && info.dli_sname) { | |
107 | e->caller_symbol = strdup(info.dli_sname); | |
108 | } else { | |
109 | e->caller_symbol = NULL; | |
110 | } | |
111 | cds_hlist_add_head(&e->hlist, head); | |
112 | } | |
113 | ||
114 | static void | |
115 | del_mh(void *ptr, const void *caller) | |
116 | { | |
117 | struct mh_entry *e; | |
118 | ||
119 | if (!ptr) | |
120 | return; | |
121 | e = get_mh(ptr); | |
122 | if (!e) { | |
123 | fprintf(stderr, | |
124 | "[warning] trying to free unallocated ptr %p caller %p\n", | |
125 | ptr, caller); | |
126 | return; | |
127 | } | |
128 | cds_hlist_del(&e->hlist); | |
129 | free(e->caller_symbol); | |
130 | free(e); | |
131 | } | |
132 | ||
133 | static void __attribute__((constructor)) | |
134 | do_init(void) | |
135 | { | |
136 | char *env; | |
137 | ||
138 | if (initialized) | |
139 | return; | |
140 | callocp = (void *(*) (size_t, size_t)) dlsym (RTLD_NEXT, "calloc"); | |
141 | mallocp = (void *(*) (size_t)) dlsym (RTLD_NEXT, "malloc"); | |
142 | reallocp = (void *(*) (void *, size_t)) dlsym (RTLD_NEXT, "realloc"); | |
143 | memalignp = (void *(*)(size_t, size_t)) dlsym (RTLD_NEXT, "memalign"); | |
144 | freep = (void (*) (void *)) dlsym (RTLD_NEXT, "free"); | |
145 | ||
146 | env = getenv("LTTNG_MEMLEAK_PRINT"); | |
147 | if (env && strcmp(env, "1") == 0) | |
148 | print_to_console = 1; | |
149 | ||
150 | initialized = 1; | |
151 | } | |
152 | ||
153 | static | |
154 | void *static_calloc(size_t nmemb, size_t size) | |
155 | { | |
156 | size_t prev_len; | |
157 | ||
158 | if (nmemb * size > sizeof(static_calloc_buf) - static_calloc_len) | |
159 | return NULL; | |
160 | prev_len = static_calloc_len; | |
161 | static_calloc_len += nmemb + size; | |
162 | return &static_calloc_buf[prev_len]; | |
163 | } | |
164 | ||
165 | void * | |
166 | calloc(size_t nmemb, size_t size) | |
167 | { | |
168 | void *result; | |
169 | const void *caller = __builtin_return_address(0); | |
170 | ||
171 | if (callocp == NULL) { | |
172 | return static_calloc(nmemb, size); | |
173 | } | |
174 | ||
175 | do_init(); | |
176 | ||
177 | if (thread_in_hook) { | |
178 | return callocp(nmemb, size); | |
179 | } | |
180 | ||
181 | thread_in_hook = 1; | |
182 | ||
183 | pthread_mutex_lock(&mh_mutex); | |
184 | ||
185 | /* Call resursively */ | |
186 | result = callocp(nmemb, size); | |
187 | ||
188 | add_mh(result, nmemb * size, caller); | |
189 | ||
190 | /* printf might call malloc, so protect it too. */ | |
191 | if (print_to_console) | |
192 | fprintf(stderr, "calloc(%zu,%zu) returns %p\n", nmemb, size, result); | |
193 | ||
194 | pthread_mutex_unlock(&mh_mutex); | |
195 | ||
196 | thread_in_hook = 0; | |
197 | ||
198 | return result; | |
199 | } | |
200 | ||
201 | void * | |
202 | malloc(size_t size) | |
203 | { | |
204 | void *result; | |
205 | const void *caller = __builtin_return_address(0); | |
206 | ||
207 | do_init(); | |
208 | ||
209 | if (thread_in_hook) { | |
210 | return mallocp(size); | |
211 | } | |
212 | ||
213 | thread_in_hook = 1; | |
214 | ||
215 | pthread_mutex_lock(&mh_mutex); | |
216 | ||
217 | /* Call resursively */ | |
218 | result = mallocp(size); | |
219 | ||
220 | add_mh(result, size, caller); | |
221 | ||
222 | /* printf might call malloc, so protect it too. */ | |
223 | if (print_to_console) | |
224 | fprintf(stderr, "malloc(%zu) returns %p\n", size, result); | |
225 | ||
226 | pthread_mutex_unlock(&mh_mutex); | |
227 | ||
228 | thread_in_hook = 0; | |
229 | ||
230 | return result; | |
231 | } | |
232 | ||
233 | void * | |
234 | realloc(void *ptr, size_t size) | |
235 | { | |
236 | void *result; | |
237 | const void *caller = __builtin_return_address(0); | |
238 | ||
239 | do_init(); | |
240 | ||
241 | if (thread_in_hook) { | |
242 | return reallocp(ptr, size); | |
243 | } | |
244 | ||
245 | thread_in_hook = 1; | |
246 | ||
247 | pthread_mutex_lock(&mh_mutex); | |
248 | ||
249 | /* Call resursively */ | |
250 | result = reallocp(ptr, size); | |
251 | ||
252 | if (size == 0 && ptr) { | |
253 | /* equivalent to free() */ | |
254 | del_mh(ptr, caller); | |
255 | } else if (result) { | |
256 | del_mh(ptr, caller); | |
257 | add_mh(result, size, caller); | |
258 | } | |
259 | ||
260 | /* printf might call malloc, so protect it too. */ | |
261 | if (print_to_console) | |
262 | fprintf(stderr, "realloc(%p,%zu) returns %p\n", ptr, size, result); | |
263 | ||
264 | pthread_mutex_unlock(&mh_mutex); | |
265 | ||
266 | thread_in_hook = 0; | |
267 | ||
268 | return result; | |
269 | } | |
270 | ||
271 | void * | |
272 | memalign(size_t alignment, size_t size) | |
273 | { | |
274 | void *result; | |
275 | const void *caller = __builtin_return_address(0); | |
276 | ||
277 | do_init(); | |
278 | ||
279 | if (thread_in_hook) { | |
280 | return memalignp(alignment, size); | |
281 | } | |
282 | ||
283 | thread_in_hook = 1; | |
284 | ||
285 | pthread_mutex_lock(&mh_mutex); | |
286 | ||
287 | /* Call resursively */ | |
288 | result = memalignp(alignment, size); | |
289 | ||
290 | add_mh(result, size, caller); | |
291 | ||
292 | /* printf might call malloc, so protect it too. */ | |
293 | if (print_to_console) | |
294 | fprintf(stderr, "memalign(%zu,%zu) returns %p\n", | |
295 | alignment, size, result); | |
296 | ||
297 | pthread_mutex_unlock(&mh_mutex); | |
298 | ||
299 | thread_in_hook = 0; | |
300 | ||
301 | return result; | |
302 | } | |
303 | ||
304 | void | |
305 | free(void *ptr) | |
306 | { | |
307 | const void *caller = __builtin_return_address(0); | |
308 | ||
309 | do_init(); | |
310 | ||
311 | if (thread_in_hook) { | |
312 | freep(ptr); | |
313 | return; | |
314 | } | |
315 | ||
316 | thread_in_hook = 1; | |
317 | pthread_mutex_lock(&mh_mutex); | |
318 | ||
319 | /* Call resursively */ | |
320 | freep(ptr); | |
321 | ||
322 | del_mh(ptr, caller); | |
323 | ||
324 | /* printf might call free, so protect it too. */ | |
325 | if (print_to_console) | |
326 | fprintf(stderr, "freed pointer %p\n", ptr); | |
327 | ||
328 | pthread_mutex_unlock(&mh_mutex); | |
329 | thread_in_hook = 0; | |
330 | } | |
331 | ||
332 | static __attribute__((destructor)) | |
333 | void print_leaks(void) | |
334 | { | |
335 | unsigned long i; | |
336 | ||
337 | for (i = 0; i < MH_TABLE_SIZE; i++) { | |
338 | struct cds_hlist_head *head; | |
339 | struct cds_hlist_node *node; | |
340 | struct mh_entry *e; | |
341 | ||
342 | head = &mh_table[i]; | |
343 | cds_hlist_for_each_entry(e, node, head, hlist) { | |
344 | fprintf(stderr, "[leak] ptr: %p size: %zu caller: %p <%s>\n", | |
345 | e->ptr, e->alloc_size, e->alloc_caller, | |
346 | e->caller_symbol); | |
347 | } | |
348 | } | |
349 | } |