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*/
33int sf_leaking_memory= 0;
34
35#ifdef SAFEMALLOC
36
37/* this mutex protects all sf_* variables, and nothing else*/
38static pthread_mutex_t sf_mutex;
39static 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*/
53struct 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
69static int sf_malloc_count= 0; /* Number of allocated chunks */
70
71static void *sf_min_adress= (void*) (intptr)~0ULL,
72 *sf_max_adress= 0;
73
74static 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
83static int bad_ptr(const char *where, void *ptr);
84static void free_memory(void *ptr);
85static void sf_terminate();
86
87/* Setup default call to get a thread id for the memory */
88
89my_thread_id default_sf_malloc_dbug_id(void)
90{
91 return my_thread_dbug_id();
92}
93
94my_thread_id (*sf_malloc_dbug_id)(void)= default_sf_malloc_dbug_id;
95
96
97/**
98 allocates memory
99*/
100
101void *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
172void *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
192void 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
211size_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
222static 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
250static 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
283static 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
304static 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 */
343int 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
368void 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
395static 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