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_gallium.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
63struct 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   boolean freed;  /**< Is this a freed block? */
77#endif
78
79   unsigned magic;
80   unsigned tag;
81};
82
83struct debug_memory_footer
84{
85   unsigned magic;
86};
87
88
89static struct list_head list = { &list, &list };
90
91static mtx_t list_mutex = _MTX_INITIALIZER_NP;
92
93static unsigned long last_no = 0;
94
95
96static inline struct debug_memory_header *
97header_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
105static inline void *
106data_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
114static inline struct debug_memory_footer *
115footer_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
124void *
125debug_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   mtx_lock(&list_mutex);
158   LIST_ADDTAIL(&hdr->head, &list);
159   mtx_unlock(&list_mutex);
160
161   return data_from_header(hdr);
162}
163
164void
165debug_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      debug_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      debug_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   mtx_lock(&list_mutex);
203   LIST_DEL(&hdr->head);
204   mtx_unlock(&list_mutex);
205   hdr->magic = 0;
206   ftr->magic = 0;
207
208   os_free(hdr);
209#endif
210}
211
212void *
213debug_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
222void *
223debug_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      debug_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      debug_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   mtx_lock(&list_mutex);
278   LIST_REPLACE(&old_hdr->head, &new_hdr->head);
279   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
293unsigned long
294debug_memory_begin(void)
295{
296   return last_no;
297}
298
299void
300debug_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(struct debug_memory_header, entry, 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         debug_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         debug_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 */
358void
359debug_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", __FUNCTION__, ptr);
369      debug_assert(0);
370   }
371
372   hdr->tag = tag;
373}
374
375
376/**
377 * Check the given block of memory for validity/corruption.
378 */
379void
380debug_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      debug_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      debug_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 */
410void
411debug_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(struct debug_memory_header, entry, 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         debug_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         debug_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