1 | /* Copyright (C) 2000 MySQL AB, 2011 Monty Program Ab |
2 | |
3 | This program is free software; you can redistribute it and/or modify |
4 | it under the terms of the GNU General Public License as published by |
5 | the Free Software Foundation; version 2 of the License. |
6 | |
7 | This program is distributed in the hope that it will be useful, |
8 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | GNU General Public License for more details. |
11 | |
12 | You should have received a copy of the GNU General Public License |
13 | along with this program; if not, write to the Free Software |
14 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ |
15 | |
16 | /******************************************************************** |
17 | memory debugger |
18 | based on safemalloc, memory sub-system, written by Bjorn Benson |
19 | ********************************************************************/ |
20 | |
21 | |
22 | #include "mysys_priv.h" |
23 | #include <my_stacktrace.h> /* my_addr_resolve */ |
24 | |
25 | #if HAVE_EXECINFO_H |
26 | #include <execinfo.h> |
27 | #endif |
28 | |
29 | /* |
30 | this can be set to 1 if we leak memory and know it |
31 | (to disable memory leak tests on exit) |
32 | */ |
33 | int sf_leaking_memory= 0; |
34 | |
35 | #ifdef SAFEMALLOC |
36 | |
37 | /* this mutex protects all sf_* variables, and nothing else*/ |
38 | static pthread_mutex_t sf_mutex; |
39 | static int init_done= 0; |
40 | |
41 | #ifndef SF_REMEMBER_FRAMES |
42 | #define SF_REMEMBER_FRAMES 8 |
43 | #endif |
44 | |
45 | /* ignore the first two frames (sf_malloc itself, and my_malloc) */ |
46 | #define SF_FRAMES_SKIP 2 |
47 | |
48 | /* |
49 | Structure that stores information of an allocated memory block |
50 | The data is at &struct_adr+sizeof(struct irem) |
51 | Note that sizeof(struct st_irem) % sizeof(double) == 0 |
52 | */ |
53 | struct st_irem |
54 | { |
55 | struct st_irem *next; /* Linked list of structures */ |
56 | struct st_irem *prev; /* Other link */ |
57 | size_t datasize; /* Size requested */ |
58 | #if SIZEOF_SIZE_T == 4 |
59 | size_t pad; /* Compensate 32bit datasize */ |
60 | #endif |
61 | #ifdef HAVE_BACKTRACE |
62 | void *frame[SF_REMEMBER_FRAMES]; /* call stack */ |
63 | #endif |
64 | uint32 flags; /* Flags passed to malloc */ |
65 | my_thread_id thread_id; /* Which thread did the allocation */ |
66 | uint32 marker; /* Underrun marker value */ |
67 | }; |
68 | |
69 | static int sf_malloc_count= 0; /* Number of allocated chunks */ |
70 | |
71 | static void *sf_min_adress= (void*) (intptr)~0ULL, |
72 | *sf_max_adress= 0; |
73 | |
74 | static struct st_irem *sf_malloc_root = 0; |
75 | |
76 | #define MAGICSTART 0x14235296 /* A magic value for underrun key */ |
77 | |
78 | #define MAGICEND0 0x68 /* Magic values for overrun keys */ |
79 | #define MAGICEND1 0x34 /* " */ |
80 | #define MAGICEND2 0x7A /* " */ |
81 | #define MAGICEND3 0x15 /* " */ |
82 | |
83 | static int bad_ptr(const char *where, void *ptr); |
84 | static void free_memory(void *ptr); |
85 | static void sf_terminate(); |
86 | |
87 | /* Setup default call to get a thread id for the memory */ |
88 | |
89 | my_thread_id default_sf_malloc_dbug_id(void) |
90 | { |
91 | return my_thread_dbug_id(); |
92 | } |
93 | |
94 | my_thread_id (*sf_malloc_dbug_id)(void)= default_sf_malloc_dbug_id; |
95 | |
96 | |
97 | /** |
98 | allocates memory |
99 | */ |
100 | |
101 | void *sf_malloc(size_t size, myf my_flags) |
102 | { |
103 | struct st_irem *irem; |
104 | uchar *data; |
105 | |
106 | /* |
107 | this style of initialization looks like race conditon prone, |
108 | but it is safe under the assumption that a program does |
109 | at least one malloc() while still being single threaded. |
110 | */ |
111 | if (!init_done) |
112 | { |
113 | pthread_mutex_init(&sf_mutex, NULL); |
114 | atexit(sf_terminate); |
115 | init_done= 1; |
116 | } |
117 | |
118 | irem= (struct st_irem *) malloc (sizeof(struct st_irem) + size + 4); |
119 | |
120 | if (!irem) |
121 | return 0; |
122 | |
123 | /* we guarantee the alignment */ |
124 | compile_time_assert(sizeof(struct st_irem) % sizeof(double) == 0); |
125 | |
126 | /* Fill up the structure */ |
127 | data= (uchar*) (irem + 1); |
128 | irem->datasize= size; |
129 | irem->prev= 0; |
130 | irem->flags= my_flags; |
131 | irem->marker= MAGICSTART; |
132 | irem->thread_id= sf_malloc_dbug_id(); |
133 | data[size + 0]= MAGICEND0; |
134 | data[size + 1]= MAGICEND1; |
135 | data[size + 2]= MAGICEND2; |
136 | data[size + 3]= MAGICEND3; |
137 | |
138 | #ifdef HAVE_BACKTRACE |
139 | { |
140 | void *frame[SF_REMEMBER_FRAMES + SF_FRAMES_SKIP]; |
141 | int frames= backtrace(frame, array_elements(frame)); |
142 | if (frames < SF_FRAMES_SKIP) |
143 | frames= 0; |
144 | else |
145 | { |
146 | frames-= SF_FRAMES_SKIP; |
147 | memcpy(irem->frame, frame + SF_FRAMES_SKIP, sizeof(void*)*frames); |
148 | } |
149 | if (frames < SF_REMEMBER_FRAMES) |
150 | irem->frame[frames]= 0; |
151 | } |
152 | #endif |
153 | |
154 | pthread_mutex_lock(&sf_mutex); |
155 | |
156 | /* Add this structure to the linked list */ |
157 | if ((irem->next= sf_malloc_root)) |
158 | sf_malloc_root->prev= irem; |
159 | sf_malloc_root= irem; |
160 | |
161 | /* Keep the statistics */ |
162 | sf_malloc_count++; |
163 | set_if_smaller(sf_min_adress, (void*)data); |
164 | set_if_bigger(sf_max_adress, (void*)data); |
165 | |
166 | pthread_mutex_unlock(&sf_mutex); |
167 | |
168 | TRASH_ALLOC(data, size); |
169 | return data; |
170 | } |
171 | |
172 | void *sf_realloc(void *ptr, size_t size, myf my_flags) |
173 | { |
174 | char *data; |
175 | |
176 | if (!ptr) |
177 | return sf_malloc(size, my_flags); |
178 | |
179 | if (bad_ptr("Reallocating" , ptr)) |
180 | return 0; |
181 | |
182 | if ((data= sf_malloc(size, my_flags))) |
183 | { |
184 | struct st_irem *irem= (struct st_irem *)ptr - 1; |
185 | set_if_smaller(size, irem->datasize); |
186 | memcpy(data, ptr, size); |
187 | free_memory(ptr); |
188 | } |
189 | return data; |
190 | } |
191 | |
192 | void sf_free(void *ptr) |
193 | { |
194 | if (!ptr || bad_ptr("Freeing" , ptr)) |
195 | return; |
196 | |
197 | free_memory(ptr); |
198 | } |
199 | |
200 | /** |
201 | Return size of memory block and if block is thread specific |
202 | |
203 | sf_malloc_usable_size() |
204 | @param ptr Pointer to malloced block |
205 | @param flags We will store 1 here if block is marked as MY_THREAD_SPECIFIC |
206 | otherwise 0 |
207 | |
208 | @return Size of block |
209 | */ |
210 | |
211 | size_t sf_malloc_usable_size(void *ptr, my_bool *is_thread_specific) |
212 | { |
213 | struct st_irem *irem= (struct st_irem *)ptr - 1; |
214 | DBUG_ENTER("sf_malloc_usable_size" ); |
215 | *is_thread_specific= MY_TEST(irem->flags & MY_THREAD_SPECIFIC); |
216 | DBUG_PRINT("exit" , ("size: %lu flags: %lu" , (ulong) irem->datasize, |
217 | (ulong)irem->flags)); |
218 | DBUG_RETURN(irem->datasize); |
219 | } |
220 | |
221 | #ifdef HAVE_BACKTRACE |
222 | static void print_stack(void **frame) |
223 | { |
224 | const char *err; |
225 | int i; |
226 | |
227 | if ((err= my_addr_resolve_init())) |
228 | { |
229 | fprintf(stderr, "(my_addr_resolve failure: %s)\n" , err); |
230 | return; |
231 | } |
232 | |
233 | for (i=0; i < SF_REMEMBER_FRAMES && frame[i]; i++) |
234 | { |
235 | my_addr_loc loc; |
236 | if (i) |
237 | fprintf(stderr, ", " ); |
238 | |
239 | if (my_addr_resolve(frame[i], &loc)) |
240 | fprintf(stderr, "%p" , frame[i]); |
241 | else |
242 | fprintf(stderr, "%s:%u" , loc.file, loc.line); |
243 | } |
244 | fprintf(stderr, "\n" ); |
245 | } |
246 | #else |
247 | #define print_stack(X) fprintf(stderr, "???\n") |
248 | #endif |
249 | |
250 | static void free_memory(void *ptr) |
251 | { |
252 | struct st_irem *irem= (struct st_irem *)ptr - 1; |
253 | |
254 | if ((irem->flags & MY_THREAD_SPECIFIC) && irem->thread_id && |
255 | irem->thread_id != sf_malloc_dbug_id()) |
256 | { |
257 | fprintf(stderr, "Warning: %4lu bytes freed by T@%lu, allocated by T@%lu at " , |
258 | (ulong) irem->datasize, |
259 | (ulong) sf_malloc_dbug_id(), (ulong) irem->thread_id); |
260 | print_stack(irem->frame); |
261 | } |
262 | |
263 | pthread_mutex_lock(&sf_mutex); |
264 | /* Remove this structure from the linked list */ |
265 | if (irem->prev) |
266 | irem->prev->next= irem->next; |
267 | else |
268 | sf_malloc_root= irem->next; |
269 | |
270 | if (irem->next) |
271 | irem->next->prev= irem->prev; |
272 | |
273 | /* Handle the statistics */ |
274 | sf_malloc_count--; |
275 | pthread_mutex_unlock(&sf_mutex); |
276 | |
277 | /* only trash the data and magic values, but keep the stack trace */ |
278 | TRASH_FREE((uchar*)(irem + 1) - 4, irem->datasize + 8); |
279 | free(irem); |
280 | return; |
281 | } |
282 | |
283 | static void warn(const char *format,...) |
284 | { |
285 | va_list args; |
286 | DBUG_PRINT("error" , ("%s" , format)); |
287 | va_start(args,format); |
288 | fflush(stderr); |
289 | vfprintf(stderr, format, args); |
290 | va_end(args); |
291 | |
292 | #ifdef HAVE_BACKTRACE |
293 | { |
294 | void *frame[SF_REMEMBER_FRAMES + SF_FRAMES_SKIP]; |
295 | int frames= backtrace(frame, array_elements(frame)); |
296 | fprintf(stderr, " at " ); |
297 | if (frames < SF_REMEMBER_FRAMES + SF_FRAMES_SKIP) |
298 | frame[frames]= 0; |
299 | print_stack(frame + SF_FRAMES_SKIP); |
300 | } |
301 | #endif |
302 | } |
303 | |
304 | static int bad_ptr(const char *where, void *ptr) |
305 | { |
306 | struct st_irem *irem= (struct st_irem *)ptr - 1; |
307 | const uchar *magicend; |
308 | |
309 | if (((intptr) ptr) % sizeof(double)) |
310 | { |
311 | warn("Error: %s wrong aligned pointer" , where); |
312 | return 1; |
313 | } |
314 | if (ptr < sf_min_adress || ptr > sf_max_adress) |
315 | { |
316 | warn("Error: %s pointer out of range" , where); |
317 | return 1; |
318 | } |
319 | if (irem->marker != MAGICSTART) |
320 | { |
321 | DBUG_PRINT("error" ,("Unallocated data or underrun buffer %p" , ptr)); |
322 | warn("Error: %s unallocated data or underrun buffer %p" , ptr, where); |
323 | return 1; |
324 | } |
325 | |
326 | magicend= (uchar*)ptr + irem->datasize; |
327 | if (magicend[0] != MAGICEND0 || |
328 | magicend[1] != MAGICEND1 || |
329 | magicend[2] != MAGICEND2 || |
330 | magicend[3] != MAGICEND3) |
331 | { |
332 | DBUG_PRINT("error" ,("Overrun buffer %p" , ptr)); |
333 | warn("Error: %s overrun buffer %p" , where, ptr); |
334 | fprintf(stderr, "Allocated at " ); |
335 | print_stack(irem->frame); |
336 | return 1; |
337 | } |
338 | |
339 | return 0; |
340 | } |
341 | |
342 | /* check all allocated memory list for consistency */ |
343 | int sf_sanity() |
344 | { |
345 | struct st_irem *irem; |
346 | int flag= 0; |
347 | int count= 0; |
348 | |
349 | pthread_mutex_lock(&sf_mutex); |
350 | count= sf_malloc_count; |
351 | for (irem= sf_malloc_root; irem && count > 0; count--, irem= irem->next) |
352 | flag+= bad_ptr("Safemalloc" , irem + 1); |
353 | pthread_mutex_unlock(&sf_mutex); |
354 | if (count || irem) |
355 | { |
356 | warn("Error: Safemalloc link list destroyed" ); |
357 | return 1; |
358 | } |
359 | return 0; |
360 | } |
361 | |
362 | /** |
363 | report on all the memory pieces that have not been free'd |
364 | |
365 | @param id Id of thread to report. 0 if all |
366 | */ |
367 | |
368 | void sf_report_leaked_memory(my_thread_id id) |
369 | { |
370 | size_t total= 0; |
371 | struct st_irem *irem; |
372 | |
373 | sf_sanity(); |
374 | |
375 | /* Report on all the memory that was allocated but not free'd */ |
376 | |
377 | for (irem= sf_malloc_root; irem; irem= irem->next) |
378 | { |
379 | if (!id || (irem->thread_id == id && irem->flags & MY_THREAD_SPECIFIC)) |
380 | { |
381 | my_thread_id tid = irem->thread_id && irem->flags & MY_THREAD_SPECIFIC ? |
382 | irem->thread_id : 0; |
383 | fprintf(stderr, "Warning: %4lu bytes lost at %p, allocated by T@%llu at " , |
384 | (ulong) irem->datasize, (char*) (irem + 1), tid); |
385 | print_stack(irem->frame); |
386 | total+= irem->datasize; |
387 | } |
388 | } |
389 | if (total) |
390 | fprintf(stderr, "Memory lost: %lu bytes in %d chunks\n" , |
391 | (ulong) total, sf_malloc_count); |
392 | return; |
393 | } |
394 | |
395 | static void sf_terminate() |
396 | { |
397 | if (!sf_leaking_memory) |
398 | sf_report_leaked_memory(0); |
399 | |
400 | pthread_mutex_destroy(&sf_mutex); |
401 | } |
402 | |
403 | #endif |
404 | |