Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
4358 | Serge | 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 |
||
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 | unsigned tag; |
||
80 | }; |
||
81 | |||
82 | struct debug_memory_footer |
||
83 | { |
||
84 | unsigned magic; |
||
85 | }; |
||
86 | |||
87 | |||
88 | static struct list_head list = { &list, &list }; |
||
89 | |||
90 | pipe_static_mutex(list_mutex); |
||
91 | |||
92 | static unsigned long last_no = 0; |
||
93 | |||
94 | |||
95 | static INLINE struct debug_memory_header * |
||
96 | header_from_data(void *data) |
||
97 | { |
||
98 | if(data) |
||
99 | return (struct debug_memory_header *)((char *)data - sizeof(struct debug_memory_header)); |
||
100 | else |
||
101 | return NULL; |
||
102 | } |
||
103 | |||
104 | static INLINE void * |
||
105 | data_from_header(struct debug_memory_header *hdr) |
||
106 | { |
||
107 | if(hdr) |
||
108 | return (void *)((char *)hdr + sizeof(struct debug_memory_header)); |
||
109 | else |
||
110 | return NULL; |
||
111 | } |
||
112 | |||
113 | static INLINE struct debug_memory_footer * |
||
114 | footer_from_header(struct debug_memory_header *hdr) |
||
115 | { |
||
116 | if(hdr) |
||
117 | return (struct debug_memory_footer *)((char *)hdr + sizeof(struct debug_memory_header) + hdr->size); |
||
118 | else |
||
119 | return NULL; |
||
120 | } |
||
121 | |||
122 | |||
123 | void * |
||
124 | debug_malloc(const char *file, unsigned line, const char *function, |
||
125 | size_t size) |
||
126 | { |
||
127 | struct debug_memory_header *hdr; |
||
128 | struct debug_memory_footer *ftr; |
||
129 | |||
130 | hdr = os_malloc(sizeof(*hdr) + size + sizeof(*ftr)); |
||
131 | if(!hdr) { |
||
132 | debug_printf("%s:%u:%s: out of memory when trying to allocate %lu bytes\n", |
||
133 | file, line, function, |
||
134 | (long unsigned)size); |
||
135 | return NULL; |
||
136 | } |
||
137 | |||
138 | hdr->no = last_no++; |
||
139 | hdr->file = file; |
||
140 | hdr->line = line; |
||
141 | hdr->function = function; |
||
142 | hdr->size = size; |
||
143 | hdr->magic = DEBUG_MEMORY_MAGIC; |
||
144 | hdr->tag = 0; |
||
145 | #if DEBUG_FREED_MEMORY |
||
146 | hdr->freed = FALSE; |
||
147 | #endif |
||
148 | |||
149 | #if DEBUG_MEMORY_STACK |
||
150 | debug_backtrace_capture(hdr->backtrace, 0, DEBUG_MEMORY_STACK); |
||
151 | #endif |
||
152 | |||
153 | ftr = footer_from_header(hdr); |
||
154 | ftr->magic = DEBUG_MEMORY_MAGIC; |
||
155 | |||
156 | pipe_mutex_lock(list_mutex); |
||
157 | LIST_ADDTAIL(&hdr->head, &list); |
||
158 | pipe_mutex_unlock(list_mutex); |
||
159 | |||
160 | return data_from_header(hdr); |
||
161 | } |
||
162 | |||
163 | void |
||
164 | debug_free(const char *file, unsigned line, const char *function, |
||
165 | void *ptr) |
||
166 | { |
||
167 | struct debug_memory_header *hdr; |
||
168 | struct debug_memory_footer *ftr; |
||
169 | |||
170 | if(!ptr) |
||
171 | return; |
||
172 | |||
173 | hdr = header_from_data(ptr); |
||
174 | if(hdr->magic != DEBUG_MEMORY_MAGIC) { |
||
175 | debug_printf("%s:%u:%s: freeing bad or corrupted memory %p\n", |
||
176 | file, line, function, |
||
177 | ptr); |
||
178 | debug_assert(0); |
||
179 | return; |
||
180 | } |
||
181 | |||
182 | ftr = footer_from_header(hdr); |
||
183 | if(ftr->magic != DEBUG_MEMORY_MAGIC) { |
||
184 | debug_printf("%s:%u:%s: buffer overflow %p\n", |
||
185 | hdr->file, hdr->line, hdr->function, |
||
186 | ptr); |
||
187 | debug_assert(0); |
||
188 | } |
||
189 | |||
190 | #if DEBUG_FREED_MEMORY |
||
191 | /* Check for double-free */ |
||
192 | assert(!hdr->freed); |
||
193 | /* Mark the block as freed but don't really free it */ |
||
194 | hdr->freed = TRUE; |
||
195 | /* Save file/line where freed */ |
||
196 | hdr->file = file; |
||
197 | hdr->line = line; |
||
198 | /* set freed memory to special value */ |
||
199 | memset(ptr, DEBUG_FREED_BYTE, hdr->size); |
||
200 | #else |
||
201 | pipe_mutex_lock(list_mutex); |
||
202 | LIST_DEL(&hdr->head); |
||
203 | pipe_mutex_unlock(list_mutex); |
||
204 | hdr->magic = 0; |
||
205 | ftr->magic = 0; |
||
206 | |||
207 | os_free(hdr); |
||
208 | #endif |
||
209 | } |
||
210 | |||
211 | void * |
||
212 | debug_calloc(const char *file, unsigned line, const char *function, |
||
213 | size_t count, size_t size ) |
||
214 | { |
||
215 | void *ptr = debug_malloc( file, line, function, count * size ); |
||
216 | if( ptr ) |
||
217 | memset( ptr, 0, count * size ); |
||
218 | return ptr; |
||
219 | } |
||
220 | |||
221 | void * |
||
222 | debug_realloc(const char *file, unsigned line, const char *function, |
||
223 | void *old_ptr, size_t old_size, size_t new_size ) |
||
224 | { |
||
225 | struct debug_memory_header *old_hdr, *new_hdr; |
||
226 | struct debug_memory_footer *old_ftr, *new_ftr; |
||
227 | void *new_ptr; |
||
228 | |||
229 | if(!old_ptr) |
||
230 | return debug_malloc( file, line, function, new_size ); |
||
231 | |||
232 | if(!new_size) { |
||
233 | debug_free( file, line, function, old_ptr ); |
||
234 | return NULL; |
||
235 | } |
||
236 | |||
237 | old_hdr = header_from_data(old_ptr); |
||
238 | if(old_hdr->magic != DEBUG_MEMORY_MAGIC) { |
||
239 | debug_printf("%s:%u:%s: reallocating bad or corrupted memory %p\n", |
||
240 | file, line, function, |
||
241 | old_ptr); |
||
242 | debug_assert(0); |
||
243 | return NULL; |
||
244 | } |
||
245 | |||
246 | old_ftr = footer_from_header(old_hdr); |
||
247 | if(old_ftr->magic != DEBUG_MEMORY_MAGIC) { |
||
248 | debug_printf("%s:%u:%s: buffer overflow %p\n", |
||
249 | old_hdr->file, old_hdr->line, old_hdr->function, |
||
250 | old_ptr); |
||
251 | debug_assert(0); |
||
252 | } |
||
253 | |||
254 | /* alloc new */ |
||
255 | new_hdr = os_malloc(sizeof(*new_hdr) + new_size + sizeof(*new_ftr)); |
||
256 | if(!new_hdr) { |
||
257 | debug_printf("%s:%u:%s: out of memory when trying to allocate %lu bytes\n", |
||
258 | file, line, function, |
||
259 | (long unsigned)new_size); |
||
260 | return NULL; |
||
261 | } |
||
262 | new_hdr->no = old_hdr->no; |
||
263 | new_hdr->file = old_hdr->file; |
||
264 | new_hdr->line = old_hdr->line; |
||
265 | new_hdr->function = old_hdr->function; |
||
266 | new_hdr->size = new_size; |
||
267 | new_hdr->magic = DEBUG_MEMORY_MAGIC; |
||
268 | new_hdr->tag = 0; |
||
269 | #if DEBUG_FREED_MEMORY |
||
270 | new_hdr->freed = FALSE; |
||
271 | #endif |
||
272 | |||
273 | new_ftr = footer_from_header(new_hdr); |
||
274 | new_ftr->magic = DEBUG_MEMORY_MAGIC; |
||
275 | |||
276 | pipe_mutex_lock(list_mutex); |
||
277 | LIST_REPLACE(&old_hdr->head, &new_hdr->head); |
||
278 | pipe_mutex_unlock(list_mutex); |
||
279 | |||
280 | /* copy data */ |
||
281 | new_ptr = data_from_header(new_hdr); |
||
282 | memcpy( new_ptr, old_ptr, old_size < new_size ? old_size : new_size ); |
||
283 | |||
284 | /* free old */ |
||
285 | old_hdr->magic = 0; |
||
286 | old_ftr->magic = 0; |
||
287 | os_free(old_hdr); |
||
288 | |||
289 | return new_ptr; |
||
290 | } |
||
291 | |||
292 | unsigned long |
||
293 | debug_memory_begin(void) |
||
294 | { |
||
295 | return last_no; |
||
296 | } |
||
297 | |||
298 | void |
||
299 | debug_memory_end(unsigned long start_no) |
||
300 | { |
||
301 | size_t total_size = 0; |
||
302 | struct list_head *entry; |
||
303 | |||
304 | if(start_no == last_no) |
||
305 | return; |
||
306 | |||
307 | entry = list.prev; |
||
308 | for (; entry != &list; entry = entry->prev) { |
||
309 | struct debug_memory_header *hdr; |
||
310 | void *ptr; |
||
311 | struct debug_memory_footer *ftr; |
||
312 | |||
313 | hdr = LIST_ENTRY(struct debug_memory_header, entry, head); |
||
314 | ptr = data_from_header(hdr); |
||
315 | ftr = footer_from_header(hdr); |
||
316 | |||
317 | if(hdr->magic != DEBUG_MEMORY_MAGIC) { |
||
318 | debug_printf("%s:%u:%s: bad or corrupted memory %p\n", |
||
319 | hdr->file, hdr->line, hdr->function, |
||
320 | ptr); |
||
321 | debug_assert(0); |
||
322 | } |
||
323 | |||
324 | if((start_no <= hdr->no && hdr->no < last_no) || |
||
325 | (last_no < start_no && (hdr->no < last_no || start_no <= hdr->no))) { |
||
326 | debug_printf("%s:%u:%s: %lu bytes at %p not freed\n", |
||
327 | hdr->file, hdr->line, hdr->function, |
||
328 | (unsigned long) hdr->size, ptr); |
||
329 | #if DEBUG_MEMORY_STACK |
||
330 | debug_backtrace_dump(hdr->backtrace, DEBUG_MEMORY_STACK); |
||
331 | #endif |
||
332 | total_size += hdr->size; |
||
333 | } |
||
334 | |||
335 | if(ftr->magic != DEBUG_MEMORY_MAGIC) { |
||
336 | debug_printf("%s:%u:%s: buffer overflow %p\n", |
||
337 | hdr->file, hdr->line, hdr->function, |
||
338 | ptr); |
||
339 | debug_assert(0); |
||
340 | } |
||
341 | } |
||
342 | |||
343 | if(total_size) { |
||
344 | debug_printf("Total of %lu KB of system memory apparently leaked\n", |
||
345 | (unsigned long) (total_size + 1023)/1024); |
||
346 | } |
||
347 | else { |
||
348 | debug_printf("No memory leaks detected.\n"); |
||
349 | } |
||
350 | } |
||
351 | |||
352 | |||
353 | /** |
||
354 | * Put a tag (arbitrary integer) on a memory block. |
||
355 | * Can be useful for debugging. |
||
356 | */ |
||
357 | void |
||
358 | debug_memory_tag(void *ptr, unsigned tag) |
||
359 | { |
||
360 | struct debug_memory_header *hdr; |
||
361 | |||
362 | if (!ptr) |
||
363 | return; |
||
364 | |||
365 | hdr = header_from_data(ptr); |
||
366 | if (hdr->magic != DEBUG_MEMORY_MAGIC) { |
||
367 | debug_printf("%s corrupted memory at %p\n", __FUNCTION__, ptr); |
||
368 | debug_assert(0); |
||
369 | } |
||
370 | |||
371 | hdr->tag = tag; |
||
372 | } |
||
373 | |||
374 | |||
375 | /** |
||
376 | * Check the given block of memory for validity/corruption. |
||
377 | */ |
||
378 | void |
||
379 | debug_memory_check_block(void *ptr) |
||
380 | { |
||
381 | struct debug_memory_header *hdr; |
||
382 | struct debug_memory_footer *ftr; |
||
383 | |||
384 | if (!ptr) |
||
385 | return; |
||
386 | |||
387 | hdr = header_from_data(ptr); |
||
388 | ftr = footer_from_header(hdr); |
||
389 | |||
390 | if (hdr->magic != DEBUG_MEMORY_MAGIC) { |
||
391 | debug_printf("%s:%u:%s: bad or corrupted memory %p\n", |
||
392 | hdr->file, hdr->line, hdr->function, ptr); |
||
393 | debug_assert(0); |
||
394 | } |
||
395 | |||
396 | if (ftr->magic != DEBUG_MEMORY_MAGIC) { |
||
397 | debug_printf("%s:%u:%s: buffer overflow %p\n", |
||
398 | hdr->file, hdr->line, hdr->function, ptr); |
||
399 | debug_assert(0); |
||
400 | } |
||
401 | } |
||
402 | |||
403 | |||
404 | |||
405 | /** |
||
406 | * We can periodically call this from elsewhere to do a basic sanity |
||
407 | * check of the heap memory we've allocated. |
||
408 | */ |
||
409 | void |
||
410 | debug_memory_check(void) |
||
411 | { |
||
412 | struct list_head *entry; |
||
413 | |||
414 | entry = list.prev; |
||
415 | for (; entry != &list; entry = entry->prev) { |
||
416 | struct debug_memory_header *hdr; |
||
417 | struct debug_memory_footer *ftr; |
||
418 | const char *ptr; |
||
419 | |||
420 | hdr = LIST_ENTRY(struct debug_memory_header, entry, head); |
||
421 | ftr = footer_from_header(hdr); |
||
422 | ptr = (const char *) data_from_header(hdr); |
||
423 | |||
424 | if (hdr->magic != DEBUG_MEMORY_MAGIC) { |
||
425 | debug_printf("%s:%u:%s: bad or corrupted memory %p\n", |
||
426 | hdr->file, hdr->line, hdr->function, ptr); |
||
427 | debug_assert(0); |
||
428 | } |
||
429 | |||
430 | if (ftr->magic != DEBUG_MEMORY_MAGIC) { |
||
431 | debug_printf("%s:%u:%s: buffer overflow %p\n", |
||
432 | hdr->file, hdr->line, hdr->function, ptr); |
||
433 | debug_assert(0); |
||
434 | } |
||
435 | |||
436 | #if DEBUG_FREED_MEMORY |
||
437 | /* If this block is marked as freed, check that it hasn't been touched */ |
||
438 | if (hdr->freed) { |
||
439 | int i; |
||
440 | for (i = 0; i < hdr->size; i++) { |
||
441 | if (ptr[i] != DEBUG_FREED_BYTE) { |
||
442 | debug_printf("Memory error: byte %d of block at %p of size %d is 0x%x\n", |
||
443 | i, ptr, hdr->size, ptr[i]); |
||
444 | debug_printf("Block was freed at %s:%d\n", hdr->file, hdr->line); |
||
445 | } |
||
446 | assert(ptr[i] == DEBUG_FREED_BYTE); |
||
447 | } |
||
448 | } |
||
449 | #endif |
||
450 | } |
||
451 | }>=>>>>=>>> |