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
62struct 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
82struct debug_memory_footer
83{
84   unsigned magic;
85};
86
87
88static struct list_head list = { &list, &list };
89
90static mtx_t list_mutex = _MTX_INITIALIZER_NP;
91
92static unsigned long last_no = 0;
93
94
95static inline struct debug_memory_header *
96header_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
104static inline void *
105data_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
113static inline struct debug_memory_footer *
114footer_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
123void *
124debug_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
163void
164debug_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      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      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
211void *
212debug_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
221void *
222debug_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      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      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
292unsigned long
293debug_memory_begin(void)
294{
295   return last_no;
296}
297
298void
299debug_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(entry, struct debug_memory_header, 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         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         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 */
357void
358debug_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      assert(0);
369   }
370
371   hdr->tag = tag;
372}
373
374
375/**
376 * Check the given block of memory for validity/corruption.
377 */
378void
379debug_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      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      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 */
409void
410debug_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(entry, struct debug_memory_header, 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         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         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