xref: /aosp_15_r20/external/mesa3d/src/util/u_debug_memory.c (revision 6104692788411f58d303aa86923a9ff6ecaded22)
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 <[email protected]>
33  */
34 
35 #include "util/detect.h"
36 
37 #define DEBUG_MEMORY_IMPLEMENTATION
38 
39 #include "util/u_thread.h"
40 
41 #include "util/simple_mtx.h"
42 #include "util/u_debug.h"
43 #include "util/u_debug_stack.h"
44 #include "util/list.h"
45 #include "util/os_memory.h"
46 #include "util/os_memory_debug.h"
47 
48 
49 #define DEBUG_MEMORY_MAGIC 0x6e34090aU
50 #define DEBUG_MEMORY_STACK 0 /* XXX: disabled until we have symbol lookup */
51 
52 /**
53  * Set to 1 to enable checking of freed blocks of memory.
54  * Basically, don't really deallocate freed memory; keep it in the list
55  * but mark it as freed and do extra checking in debug_memory_check().
56  * This can detect some cases of use-after-free.  But note that since we
57  * never really free anything this will use a lot of memory.
58  */
59 #define DEBUG_FREED_MEMORY 0
60 #define DEBUG_FREED_BYTE 0x33
61 
62 
63 struct debug_memory_header
64 {
65    struct list_head head;
66 
67    unsigned long no;
68    const char *file;
69    unsigned line;
70    const char *function;
71 #if DEBUG_MEMORY_STACK
72    struct debug_stack_frame backtrace[DEBUG_MEMORY_STACK];
73 #endif
74    size_t size;
75 #if DEBUG_FREED_MEMORY
76    bool freed;  /**< Is this a freed block? */
77 #endif
78 
79    unsigned magic;
80    unsigned tag;
81 };
82 
83 struct debug_memory_footer
84 {
85    unsigned magic;
86 };
87 
88 
89 static struct list_head list = { &list, &list };
90 
91 static simple_mtx_t list_mutex = SIMPLE_MTX_INITIALIZER;
92 
93 static unsigned long last_no = 0;
94 
95 
96 static inline struct debug_memory_header *
header_from_data(void * data)97 header_from_data(void *data)
98 {
99    if (data)
100       return (struct debug_memory_header *)((char *)data - sizeof(struct debug_memory_header));
101    else
102       return NULL;
103 }
104 
105 static inline void *
data_from_header(struct debug_memory_header * hdr)106 data_from_header(struct debug_memory_header *hdr)
107 {
108    if (hdr)
109       return (void *)((char *)hdr + sizeof(struct debug_memory_header));
110    else
111       return NULL;
112 }
113 
114 static inline struct debug_memory_footer *
footer_from_header(struct debug_memory_header * hdr)115 footer_from_header(struct debug_memory_header *hdr)
116 {
117    if (hdr)
118       return (struct debug_memory_footer *)((char *)hdr + sizeof(struct debug_memory_header) + hdr->size);
119    else
120       return NULL;
121 }
122 
123 
124 void *
debug_malloc(const char * file,unsigned line,const char * function,size_t size)125 debug_malloc(const char *file, unsigned line, const char *function,
126              size_t size)
127 {
128    struct debug_memory_header *hdr;
129    struct debug_memory_footer *ftr;
130 
131    hdr = os_malloc(sizeof(*hdr) + size + sizeof(*ftr));
132    if (!hdr) {
133       debug_printf("%s:%u:%s: out of memory when trying to allocate %lu bytes\n",
134                    file, line, function,
135                    (long unsigned)size);
136       return NULL;
137    }
138 
139    hdr->no = last_no++;
140    hdr->file = file;
141    hdr->line = line;
142    hdr->function = function;
143    hdr->size = size;
144    hdr->magic = DEBUG_MEMORY_MAGIC;
145    hdr->tag = 0;
146 #if DEBUG_FREED_MEMORY
147    hdr->freed = false;
148 #endif
149 
150 #if DEBUG_MEMORY_STACK
151    debug_backtrace_capture(hdr->backtrace, 0, DEBUG_MEMORY_STACK);
152 #endif
153 
154    ftr = footer_from_header(hdr);
155    ftr->magic = DEBUG_MEMORY_MAGIC;
156 
157    simple_mtx_lock(&list_mutex);
158    list_addtail(&hdr->head, &list);
159    simple_mtx_unlock(&list_mutex);
160 
161    return data_from_header(hdr);
162 }
163 
164 void
debug_free(const char * file,unsigned line,const char * function,void * ptr)165 debug_free(const char *file, unsigned line, const char *function,
166            void *ptr)
167 {
168    struct debug_memory_header *hdr;
169    struct debug_memory_footer *ftr;
170 
171    if (!ptr)
172       return;
173 
174    hdr = header_from_data(ptr);
175    if (hdr->magic != DEBUG_MEMORY_MAGIC) {
176       debug_printf("%s:%u:%s: freeing bad or corrupted memory %p\n",
177                    file, line, function,
178                    ptr);
179       assert(0);
180       return;
181    }
182 
183    ftr = footer_from_header(hdr);
184    if (ftr->magic != DEBUG_MEMORY_MAGIC) {
185       debug_printf("%s:%u:%s: buffer overflow %p\n",
186                    hdr->file, hdr->line, hdr->function,
187                    ptr);
188       assert(0);
189    }
190 
191 #if DEBUG_FREED_MEMORY
192    /* Check for double-free */
193    assert(!hdr->freed);
194    /* Mark the block as freed but don't really free it */
195    hdr->freed = true;
196    /* Save file/line where freed */
197    hdr->file = file;
198    hdr->line = line;
199    /* set freed memory to special value */
200    memset(ptr, DEBUG_FREED_BYTE, hdr->size);
201 #else
202    simple_mtx_lock(&list_mutex);
203    list_del(&hdr->head);
204    simple_mtx_unlock(&list_mutex);
205    hdr->magic = 0;
206    ftr->magic = 0;
207 
208    os_free(hdr);
209 #endif
210 }
211 
212 void *
debug_calloc(const char * file,unsigned line,const char * function,size_t count,size_t size)213 debug_calloc(const char *file, unsigned line, const char *function,
214              size_t count, size_t size )
215 {
216    void *ptr = debug_malloc( file, line, function, count * size );
217    if (ptr)
218       memset( ptr, 0, count * size );
219    return ptr;
220 }
221 
222 void *
debug_realloc(const char * file,unsigned line,const char * function,void * old_ptr,size_t old_size,size_t new_size)223 debug_realloc(const char *file, unsigned line, const char *function,
224               void *old_ptr, size_t old_size, size_t new_size )
225 {
226    struct debug_memory_header *old_hdr, *new_hdr;
227    struct debug_memory_footer *old_ftr, *new_ftr;
228    void *new_ptr;
229 
230    if (!old_ptr)
231       return debug_malloc( file, line, function, new_size );
232 
233    if (!new_size) {
234       debug_free( file, line, function, old_ptr );
235       return NULL;
236    }
237 
238    old_hdr = header_from_data(old_ptr);
239    if (old_hdr->magic != DEBUG_MEMORY_MAGIC) {
240       debug_printf("%s:%u:%s: reallocating bad or corrupted memory %p\n",
241                    file, line, function,
242                    old_ptr);
243       assert(0);
244       return NULL;
245    }
246 
247    old_ftr = footer_from_header(old_hdr);
248    if (old_ftr->magic != DEBUG_MEMORY_MAGIC) {
249       debug_printf("%s:%u:%s: buffer overflow %p\n",
250                    old_hdr->file, old_hdr->line, old_hdr->function,
251                    old_ptr);
252       assert(0);
253    }
254 
255    /* alloc new */
256    new_hdr = os_malloc(sizeof(*new_hdr) + new_size + sizeof(*new_ftr));
257    if (!new_hdr) {
258       debug_printf("%s:%u:%s: out of memory when trying to allocate %lu bytes\n",
259                    file, line, function,
260                    (long unsigned)new_size);
261       return NULL;
262    }
263    new_hdr->no = old_hdr->no;
264    new_hdr->file = old_hdr->file;
265    new_hdr->line = old_hdr->line;
266    new_hdr->function = old_hdr->function;
267    new_hdr->size = new_size;
268    new_hdr->magic = DEBUG_MEMORY_MAGIC;
269    new_hdr->tag = 0;
270 #if DEBUG_FREED_MEMORY
271    new_hdr->freed = false;
272 #endif
273 
274    new_ftr = footer_from_header(new_hdr);
275    new_ftr->magic = DEBUG_MEMORY_MAGIC;
276 
277    simple_mtx_lock(&list_mutex);
278    list_replace(&old_hdr->head, &new_hdr->head);
279    simple_mtx_unlock(&list_mutex);
280 
281    /* copy data */
282    new_ptr = data_from_header(new_hdr);
283    memcpy( new_ptr, old_ptr, old_size < new_size ? old_size : new_size );
284 
285    /* free old */
286    old_hdr->magic = 0;
287    old_ftr->magic = 0;
288    os_free(old_hdr);
289 
290    return new_ptr;
291 }
292 
293 unsigned long
debug_memory_begin(void)294 debug_memory_begin(void)
295 {
296    return last_no;
297 }
298 
299 void
debug_memory_end(unsigned long start_no)300 debug_memory_end(unsigned long start_no)
301 {
302    size_t total_size = 0;
303    struct list_head *entry;
304 
305    if (start_no == last_no)
306       return;
307 
308    entry = list.prev;
309    for (; entry != &list; entry = entry->prev) {
310       struct debug_memory_header *hdr;
311       void *ptr;
312       struct debug_memory_footer *ftr;
313 
314       hdr = list_entry(entry, struct debug_memory_header, head);
315       ptr = data_from_header(hdr);
316       ftr = footer_from_header(hdr);
317 
318       if (hdr->magic != DEBUG_MEMORY_MAGIC) {
319          debug_printf("%s:%u:%s: bad or corrupted memory %p\n",
320                       hdr->file, hdr->line, hdr->function,
321                       ptr);
322          assert(0);
323       }
324 
325       if ((start_no <= hdr->no && hdr->no < last_no) ||
326           (last_no < start_no && (hdr->no < last_no || start_no <= hdr->no))) {
327          debug_printf("%s:%u:%s: %lu bytes at %p not freed\n",
328                       hdr->file, hdr->line, hdr->function,
329                       (unsigned long) hdr->size, ptr);
330 #if DEBUG_MEMORY_STACK
331          debug_backtrace_dump(hdr->backtrace, DEBUG_MEMORY_STACK);
332 #endif
333          total_size += hdr->size;
334       }
335 
336       if (ftr->magic != DEBUG_MEMORY_MAGIC) {
337          debug_printf("%s:%u:%s: buffer overflow %p\n",
338                       hdr->file, hdr->line, hdr->function,
339                       ptr);
340          assert(0);
341       }
342    }
343 
344    if (total_size) {
345       debug_printf("Total of %lu KB of system memory apparently leaked\n",
346                    (unsigned long) (total_size + 1023)/1024);
347    }
348    else {
349       debug_printf("No memory leaks detected.\n");
350    }
351 }
352 
353 
354 /**
355  * Put a tag (arbitrary integer) on a memory block.
356  * Can be useful for debugging.
357  */
358 void
debug_memory_tag(void * ptr,unsigned tag)359 debug_memory_tag(void *ptr, unsigned tag)
360 {
361    struct debug_memory_header *hdr;
362 
363    if (!ptr)
364       return;
365 
366    hdr = header_from_data(ptr);
367    if (hdr->magic != DEBUG_MEMORY_MAGIC) {
368       debug_printf("%s corrupted memory at %p\n", __func__, ptr);
369       assert(0);
370    }
371 
372    hdr->tag = tag;
373 }
374 
375 
376 /**
377  * Check the given block of memory for validity/corruption.
378  */
379 void
debug_memory_check_block(void * ptr)380 debug_memory_check_block(void *ptr)
381 {
382    struct debug_memory_header *hdr;
383    struct debug_memory_footer *ftr;
384 
385    if (!ptr)
386       return;
387 
388    hdr = header_from_data(ptr);
389    ftr = footer_from_header(hdr);
390 
391    if (hdr->magic != DEBUG_MEMORY_MAGIC) {
392       debug_printf("%s:%u:%s: bad or corrupted memory %p\n",
393                    hdr->file, hdr->line, hdr->function, ptr);
394       assert(0);
395    }
396 
397    if (ftr->magic != DEBUG_MEMORY_MAGIC) {
398       debug_printf("%s:%u:%s: buffer overflow %p\n",
399                    hdr->file, hdr->line, hdr->function, ptr);
400       assert(0);
401    }
402 }
403 
404 
405 
406 /**
407  * We can periodically call this from elsewhere to do a basic sanity
408  * check of the heap memory we've allocated.
409  */
410 void
debug_memory_check(void)411 debug_memory_check(void)
412 {
413    struct list_head *entry;
414 
415    entry = list.prev;
416    for (; entry != &list; entry = entry->prev) {
417       struct debug_memory_header *hdr;
418       struct debug_memory_footer *ftr;
419       const char *ptr;
420 
421       hdr = list_entry(entry, struct debug_memory_header, head);
422       ftr = footer_from_header(hdr);
423       ptr = (const char *) data_from_header(hdr);
424 
425       if (hdr->magic != DEBUG_MEMORY_MAGIC) {
426          debug_printf("%s:%u:%s: bad or corrupted memory %p\n",
427                       hdr->file, hdr->line, hdr->function, ptr);
428          assert(0);
429       }
430 
431       if (ftr->magic != DEBUG_MEMORY_MAGIC) {
432          debug_printf("%s:%u:%s: buffer overflow %p\n",
433                       hdr->file, hdr->line, hdr->function, ptr);
434          assert(0);
435       }
436 
437 #if DEBUG_FREED_MEMORY
438       /* If this block is marked as freed, check that it hasn't been touched */
439       if (hdr->freed) {
440          int i;
441          for (i = 0; i < hdr->size; i++) {
442             if (ptr[i] != DEBUG_FREED_BYTE) {
443                debug_printf("Memory error: byte %d of block at %p of size %d is 0x%x\n",
444                             i, ptr, hdr->size, ptr[i]);
445                debug_printf("Block was freed at %s:%d\n", hdr->file, hdr->line);
446             }
447             assert(ptr[i] == DEBUG_FREED_BYTE);
448          }
449       }
450 #endif
451    }
452 }
453