util: add more memory debugging features
[mesa.git] / src / gallium / auxiliary / util / u_debug_memory.c
1 /**************************************************************************
2 *
3 * Copyright 2008 Tungsten Graphics, Inc., Cedar Park, Texas.
4 * All Rights Reserved.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sub license, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
13 *
14 * The above copyright notice and this permission notice (including the
15 * next paragraph) shall be included in all copies or substantial portions
16 * of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
21 * IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS BE LIABLE FOR
22 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 *
26 **************************************************************************/
27
28 /**
29 * @file
30 * Memory debugging.
31 *
32 * @author José Fonseca <jrfonseca@tungstengraphics.com>
33 */
34
35 #include "pipe/p_config.h"
36
37 #define DEBUG_MEMORY_IMPLEMENTATION
38
39 #include "os/os_memory.h"
40 #include "os/os_memory_debug.h"
41 #include "os/os_thread.h"
42
43 #include "util/u_debug.h"
44 #include "util/u_debug_stack.h"
45 #include "util/u_double_list.h"
46
47
48 #define DEBUG_MEMORY_MAGIC 0x6e34090aU
49 #define DEBUG_MEMORY_STACK 0 /* XXX: disabled until we have symbol lookup */
50
51 /**
52 * Set to 1 to enable checking of freed blocks of memory.
53 * Basically, don't really deallocate freed memory; keep it in the list
54 * but mark it as freed and do extra checking in debug_memory_check().
55 * This can detect some cases of use-after-free. But note that since we
56 * never really free anything this will use a lot of memory.
57 */
58 #define DEBUG_FREED_MEMORY 0
59 #define DEBUG_FREED_BYTE 0x33
60
61
62 struct debug_memory_header
63 {
64 struct list_head head;
65
66 unsigned long no;
67 const char *file;
68 unsigned line;
69 const char *function;
70 #if DEBUG_MEMORY_STACK
71 struct debug_stack_frame backtrace[DEBUG_MEMORY_STACK];
72 #endif
73 size_t size;
74 #if DEBUG_FREED_MEMORY
75 boolean freed; /**< Is this a freed block? */
76 #endif
77
78 unsigned magic;
79 };
80
81 struct debug_memory_footer
82 {
83 unsigned magic;
84 };
85
86
87 static struct list_head list = { &list, &list };
88
89 pipe_static_mutex(list_mutex);
90
91 static unsigned long last_no = 0;
92
93
94 static INLINE struct debug_memory_header *
95 header_from_data(void *data)
96 {
97 if(data)
98 return (struct debug_memory_header *)((char *)data - sizeof(struct debug_memory_header));
99 else
100 return NULL;
101 }
102
103 static INLINE void *
104 data_from_header(struct debug_memory_header *hdr)
105 {
106 if(hdr)
107 return (void *)((char *)hdr + sizeof(struct debug_memory_header));
108 else
109 return NULL;
110 }
111
112 static INLINE struct debug_memory_footer *
113 footer_from_header(struct debug_memory_header *hdr)
114 {
115 if(hdr)
116 return (struct debug_memory_footer *)((char *)hdr + sizeof(struct debug_memory_header) + hdr->size);
117 else
118 return NULL;
119 }
120
121
122 void *
123 debug_malloc(const char *file, unsigned line, const char *function,
124 size_t size)
125 {
126 struct debug_memory_header *hdr;
127 struct debug_memory_footer *ftr;
128
129 hdr = os_malloc(sizeof(*hdr) + size + sizeof(*ftr));
130 if(!hdr) {
131 debug_printf("%s:%u:%s: out of memory when trying to allocate %lu bytes\n",
132 file, line, function,
133 (long unsigned)size);
134 return NULL;
135 }
136
137 hdr->no = last_no++;
138 hdr->file = file;
139 hdr->line = line;
140 hdr->function = function;
141 hdr->size = size;
142 hdr->magic = DEBUG_MEMORY_MAGIC;
143 #if DEBUG_FREED_MEMORY
144 hdr->freed = FALSE;
145 #endif
146
147 #if DEBUG_MEMORY_STACK
148 debug_backtrace_capture(hdr->backtrace, 0, DEBUG_MEMORY_STACK);
149 #endif
150
151 ftr = footer_from_header(hdr);
152 ftr->magic = DEBUG_MEMORY_MAGIC;
153
154 pipe_mutex_lock(list_mutex);
155 LIST_ADDTAIL(&hdr->head, &list);
156 pipe_mutex_unlock(list_mutex);
157
158 return data_from_header(hdr);
159 }
160
161 void
162 debug_free(const char *file, unsigned line, const char *function,
163 void *ptr)
164 {
165 struct debug_memory_header *hdr;
166 struct debug_memory_footer *ftr;
167
168 if(!ptr)
169 return;
170
171 hdr = header_from_data(ptr);
172 if(hdr->magic != DEBUG_MEMORY_MAGIC) {
173 debug_printf("%s:%u:%s: freeing bad or corrupted memory %p\n",
174 file, line, function,
175 ptr);
176 debug_assert(0);
177 return;
178 }
179
180 ftr = footer_from_header(hdr);
181 if(ftr->magic != DEBUG_MEMORY_MAGIC) {
182 debug_printf("%s:%u:%s: buffer overflow %p\n",
183 hdr->file, hdr->line, hdr->function,
184 ptr);
185 debug_assert(0);
186 }
187
188 #if DEBUG_FREED_MEMORY
189 /* Check for double-free */
190 assert(!hdr->freed);
191 /* Mark the block as freed but don't really free it */
192 hdr->freed = TRUE;
193 /* Save file/line where freed */
194 hdr->file = file;
195 hdr->line = line;
196 /* set freed memory to special value */
197 memset(ptr, DEBUG_FREED_BYTE, hdr->size);
198 #else
199 pipe_mutex_lock(list_mutex);
200 LIST_DEL(&hdr->head);
201 pipe_mutex_unlock(list_mutex);
202 hdr->magic = 0;
203 ftr->magic = 0;
204
205 os_free(hdr);
206 #endif
207 }
208
209 void *
210 debug_calloc(const char *file, unsigned line, const char *function,
211 size_t count, size_t size )
212 {
213 void *ptr = debug_malloc( file, line, function, count * size );
214 if( ptr )
215 memset( ptr, 0, count * size );
216 return ptr;
217 }
218
219 void *
220 debug_realloc(const char *file, unsigned line, const char *function,
221 void *old_ptr, size_t old_size, size_t new_size )
222 {
223 struct debug_memory_header *old_hdr, *new_hdr;
224 struct debug_memory_footer *old_ftr, *new_ftr;
225 void *new_ptr;
226
227 if(!old_ptr)
228 return debug_malloc( file, line, function, new_size );
229
230 if(!new_size) {
231 debug_free( file, line, function, old_ptr );
232 return NULL;
233 }
234
235 old_hdr = header_from_data(old_ptr);
236 if(old_hdr->magic != DEBUG_MEMORY_MAGIC) {
237 debug_printf("%s:%u:%s: reallocating bad or corrupted memory %p\n",
238 file, line, function,
239 old_ptr);
240 debug_assert(0);
241 return NULL;
242 }
243
244 old_ftr = footer_from_header(old_hdr);
245 if(old_ftr->magic != DEBUG_MEMORY_MAGIC) {
246 debug_printf("%s:%u:%s: buffer overflow %p\n",
247 old_hdr->file, old_hdr->line, old_hdr->function,
248 old_ptr);
249 debug_assert(0);
250 }
251
252 /* alloc new */
253 new_hdr = os_malloc(sizeof(*new_hdr) + new_size + sizeof(*new_ftr));
254 if(!new_hdr) {
255 debug_printf("%s:%u:%s: out of memory when trying to allocate %lu bytes\n",
256 file, line, function,
257 (long unsigned)new_size);
258 return NULL;
259 }
260 new_hdr->no = old_hdr->no;
261 new_hdr->file = old_hdr->file;
262 new_hdr->line = old_hdr->line;
263 new_hdr->function = old_hdr->function;
264 new_hdr->size = new_size;
265 new_hdr->magic = DEBUG_MEMORY_MAGIC;
266 #if DEBUG_FREED_MEMORY
267 new_hdr->freed = FALSE;
268 #endif
269
270 new_ftr = footer_from_header(new_hdr);
271 new_ftr->magic = DEBUG_MEMORY_MAGIC;
272
273 pipe_mutex_lock(list_mutex);
274 LIST_REPLACE(&old_hdr->head, &new_hdr->head);
275 pipe_mutex_unlock(list_mutex);
276
277 /* copy data */
278 new_ptr = data_from_header(new_hdr);
279 memcpy( new_ptr, old_ptr, old_size < new_size ? old_size : new_size );
280
281 /* free old */
282 old_hdr->magic = 0;
283 old_ftr->magic = 0;
284 os_free(old_hdr);
285
286 return new_ptr;
287 }
288
289 unsigned long
290 debug_memory_begin(void)
291 {
292 return last_no;
293 }
294
295 void
296 debug_memory_end(unsigned long start_no)
297 {
298 size_t total_size = 0;
299 struct list_head *entry;
300
301 if(start_no == last_no)
302 return;
303
304 entry = list.prev;
305 for (; entry != &list; entry = entry->prev) {
306 struct debug_memory_header *hdr;
307 void *ptr;
308 struct debug_memory_footer *ftr;
309
310 hdr = LIST_ENTRY(struct debug_memory_header, entry, head);
311 ptr = data_from_header(hdr);
312 ftr = footer_from_header(hdr);
313
314 if(hdr->magic != DEBUG_MEMORY_MAGIC) {
315 debug_printf("%s:%u:%s: bad or corrupted memory %p\n",
316 hdr->file, hdr->line, hdr->function,
317 ptr);
318 debug_assert(0);
319 }
320
321 if((start_no <= hdr->no && hdr->no < last_no) ||
322 (last_no < start_no && (hdr->no < last_no || start_no <= hdr->no))) {
323 debug_printf("%s:%u:%s: %lu bytes at %p not freed\n",
324 hdr->file, hdr->line, hdr->function,
325 (unsigned long) hdr->size, ptr);
326 #if DEBUG_MEMORY_STACK
327 debug_backtrace_dump(hdr->backtrace, DEBUG_MEMORY_STACK);
328 #endif
329 total_size += hdr->size;
330 }
331
332 if(ftr->magic != DEBUG_MEMORY_MAGIC) {
333 debug_printf("%s:%u:%s: buffer overflow %p\n",
334 hdr->file, hdr->line, hdr->function,
335 ptr);
336 debug_assert(0);
337 }
338 }
339
340 if(total_size) {
341 debug_printf("Total of %lu KB of system memory apparently leaked\n",
342 (unsigned long) (total_size + 1023)/1024);
343 }
344 else {
345 debug_printf("No memory leaks detected.\n");
346 }
347 }
348
349
350 /**
351 * We can periodically call this from elsewhere to do a basic sanity
352 * check of the heap memory we've allocated.
353 */
354 void
355 debug_memory_check(void)
356 {
357 struct list_head *entry;
358
359 entry = list.prev;
360 for (; entry != &list; entry = entry->prev) {
361 struct debug_memory_header *hdr;
362 struct debug_memory_footer *ftr;
363 const char *ptr;
364
365 hdr = LIST_ENTRY(struct debug_memory_header, entry, head);
366 ftr = footer_from_header(hdr);
367 ptr = (const char *) data_from_header(hdr);
368
369 if (hdr->magic != DEBUG_MEMORY_MAGIC) {
370 debug_printf("%s:%u:%s: bad or corrupted memory %p\n",
371 hdr->file, hdr->line, hdr->function, ptr);
372 debug_assert(0);
373 }
374
375 if (ftr->magic != DEBUG_MEMORY_MAGIC) {
376 debug_printf("%s:%u:%s: buffer overflow %p\n",
377 hdr->file, hdr->line, hdr->function, ptr);
378 debug_assert(0);
379 }
380
381 #if DEBUG_FREED_MEMORY
382 /* If this block is marked as freed, check that it hasn't been touched */
383 if (hdr->freed) {
384 int i;
385 for (i = 0; i < hdr->size; i++) {
386 if (ptr[i] != DEBUG_FREED_BYTE) {
387 debug_printf("Memory error: byte %d of block at %p of size %d is 0x%x\n",
388 i, ptr, hdr->size, ptr[i]);
389 debug_printf("Block was freed at %s:%d\n", hdr->file, hdr->line);
390 }
391 assert(ptr[i] == DEBUG_FREED_BYTE);
392 }
393 }
394 #endif
395 }
396 }