1 /**************************************************************************
2  *
3  * Copyright 2008 VMware, Inc.
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 VMWARE 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 <jfonseca@vmware.com>
33  */
34 
35 #include "pipe/p_config.h"
36 
37 #define DEBUG_MEMORY_IMPLEMENTATION
38 
39 #include "os/os_thread.h"
40 
41 #include "util/u_debug.h"
42 #include "util/u_debug_stack.h"
43 #include "util/list.h"
44 #include "util/os_memory.h"
45 #include "util/os_memory_debug.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 static mtx_t list_mutex = _MTX_INITIALIZER_NP;
91 
92 static unsigned long last_no = 0;
93 
94 
95 static inline struct debug_memory_header *
header_from_data(void * data)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 *
data_from_header(struct debug_memory_header * hdr)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 *
footer_from_header(struct debug_memory_header * hdr)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 *
debug_malloc(const char * file,unsigned line,const char * function,size_t size)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    mtx_lock(&list_mutex);
157    list_addtail(&hdr->head, &list);
158    mtx_unlock(&list_mutex);
159 
160    return data_from_header(hdr);
161 }
162 
163 void
debug_free(const char * file,unsigned line,const char * function,void * ptr)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    mtx_lock(&list_mutex);
202    list_del(&hdr->head);
203    mtx_unlock(&list_mutex);
204    hdr->magic = 0;
205    ftr->magic = 0;
206 
207    os_free(hdr);
208 #endif
209 }
210 
211 void *
debug_calloc(const char * file,unsigned line,const char * function,size_t count,size_t size)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 *
debug_realloc(const char * file,unsigned line,const char * function,void * old_ptr,size_t old_size,size_t new_size)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    mtx_lock(&list_mutex);
277    list_replace(&old_hdr->head, &new_hdr->head);
278    mtx_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
debug_memory_begin(void)293 debug_memory_begin(void)
294 {
295    return last_no;
296 }
297 
298 void
debug_memory_end(unsigned long start_no)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
debug_memory_tag(void * ptr,unsigned tag)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
debug_memory_check_block(void * ptr)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
debug_memory_check(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 }
452