1 /*
2  * alloc.c - memory allocation module implementation
3  *
4  * Copyright (C) 2011-2013 Thien-Thi Nguyen
5  * Copyright (C) 2000, 2001, 2003 Stefan Jahn <stefan@lkcc.org>
6  * Copyright (C) 2000 Raimund Jacob <raimi@lkcc.org>
7  * Copyright (C) 1999 Martin Grabmueller <mgrabmue@cs.tu-berlin.de>
8  *
9  * This is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 3, or (at your option)
12  * any later version.
13  *
14  * This software is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this package.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #include "config.h"
24 
25 #include "timidity.h"
26 #include "unused.h"
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 
31 #include "libserveez/alloc.h"
32 #include "libserveez/util.h"
33 
34 #if DEBUG_MEMORY_LEAKS
35 # include "libserveez/hash.h"
36 # include "le-u32-hash.h"
37 #endif /* DEBUG_MEMORY_LEAKS */
38 
39 #if ENABLE_DEBUG
40 /* The overall number of bytes allocated by libserveez.  */
41 static size_t allocated_bytes = 0;
42 /* The number of memory blocks reserved by libserveez.  */
43 static size_t allocated_blocks = 0;
44 #endif /* ENABLE_DEBUG */
45 
46 /* Default memory management functions.  */
47 svz_malloc_func_t svz_malloc_func = malloc;
48 svz_realloc_func_t svz_realloc_func = realloc;
49 svz_free_func_t svz_free_func = free;
50 
51 /**
52  * Set the internal memory management functions to @var{cus_malloc},
53  * @var{cus_realloc} and @var{cus_free}, respectively.
54  * The default internal values are @code{malloc}, @code{realloc}
55  * and @code{free}.
56  */
57 void
svz_set_mm_funcs(svz_malloc_func_t cus_malloc,svz_realloc_func_t cus_realloc,svz_free_func_t cus_free)58 svz_set_mm_funcs (svz_malloc_func_t cus_malloc,
59                   svz_realloc_func_t cus_realloc,
60                   svz_free_func_t cus_free)
61 {
62   svz_malloc_func = cus_malloc;
63   svz_realloc_func = cus_realloc;
64   svz_free_func = cus_free;
65 }
66 
67 #if DEBUG_MEMORY_LEAKS
68 
69 /* heap hash table */
70 static svz_hash_t *heap = NULL;
71 
72 /* return static heap hash code key length */
73 static size_t
heap_hash_keylen(UNUSED const char * id)74 heap_hash_keylen (UNUSED const char *id)
75 {
76   return SIZEOF_VOID_P;
77 }
78 
79 /* compare two heap hash values */
80 static int
heap_hash_equals(const char * id1,const char * id2)81 heap_hash_equals (const char *id1, const char *id2)
82 {
83   return memcmp (id1, id2, SIZEOF_VOID_P);
84 }
85 
86 /* calculate heap hash code */
87 static unsigned long
heap_hash_code(const char * id)88 heap_hash_code (const char *id)
89 {
90   unsigned long code = le_u32_hash (id);
91   code >>= 3;
92   return code;
93 }
94 
95 /* structure for heap management */
96 typedef struct
97 {
98   void *ptr;       /* memory pointer */
99   size_t size;     /* memory block's size */
100   void *caller;    /* the caller */
101 }
102 heap_block_t;
103 
104 /* add another heap block to the heap management */
105 static void
heap_add(heap_block_t * block)106 heap_add (heap_block_t *block)
107 {
108   if (heap == NULL)
109     heap = svz_hash_configure (svz_hash_create (4, NULL),
110                                heap_hash_keylen,
111                                heap_hash_code,
112                                heap_hash_equals);
113   svz_hash_put (heap, (char *) &block->ptr, block);
114 }
115 
116 #ifdef _MSC_VER
117 # include <windows.h>
118 # include <imagehlp.h>
119 # define __builtin_return_address(no) ((void *) (stack.AddrReturn.Offset))
120 # define heap_caller()                                                     \
121     STACKFRAME stack;                                                      \
122     StackWalk (IMAGE_FILE_MACHINE_I386, GetCurrentProcess (),              \
123                GetCurrentThread (), &stack, NULL, NULL, NULL, NULL, NULL)
124 #else
125 # ifndef __GNUC__
126 #  define __builtin_return_address(no) 0
127 # endif
128 # define heap_caller()
129 #endif
130 
131 #else /* !DEBUG_MEMORY_LEAKS */
132 # define heap_caller()
133 #endif /* !DEBUG_MEMORY_LEAKS */
134 
135 /* FIXME: Make ‘static inline’ func w/ attribute ‘SVZ_EXITING’.  */
136 #define oom(who)  do                                            \
137     {                                                           \
138       svz_log (SVZ_LOG_FATAL, who ": virtual memory exhausted\n");  \
139       exit (EXIT_FAILURE);                                      \
140     }                                                           \
141   while (0)
142 
143 /**
144  * Allocate @var{size} bytes of memory and return a pointer to this block.
145  */
146 void *
svz_malloc(size_t size)147 svz_malloc (size_t size)
148 {
149   void *ptr;
150 #if ENABLE_DEBUG
151   size_t *p;
152 #if DEBUG_MEMORY_LEAKS
153   heap_block_t *block;
154 #endif /* DEBUG_MEMORY_LEAKS */
155 #endif /* ENABLE_DEBUG */
156 
157   heap_caller ();
158   assert (size);
159 
160 #if ENABLE_DEBUG
161   if ((ptr = (void *) svz_malloc_func (size + 2 *
162                                        sizeof (size_t))) != NULL)
163     {
164 #if ENABLE_HEAP_COUNT
165       /* save size at the beginning of the block */
166       p = (size_t *) ptr;
167       *p = size;
168       p += 2;
169       ptr = (void *) p;
170 #if DEBUG_MEMORY_LEAKS
171       /* put heap pointer into special heap hash */
172       block = svz_malloc_func (sizeof (heap_block_t));
173       block->ptr = ptr;
174       block->size = size;
175       block->caller = __builtin_return_address (0);
176       heap_add (block);
177 #endif /* DEBUG_MEMORY_LEAKS */
178       allocated_bytes += size;
179 #endif /* ENABLE_HEAP_COUNT */
180       allocated_blocks++;
181       return ptr;
182     }
183 #else /* not ENABLE_DEBUG */
184   if ((ptr = (void *) svz_malloc_func (size)) != NULL)
185     {
186       return ptr;
187     }
188 #endif /* not ENABLE_DEBUG */
189   else
190     oom ("malloc");
191 }
192 
193 /**
194  * Allocate @var{size} bytes of memory and return a pointer to this block.
195  * The memory is cleared (filled with zeros).
196  */
197 void *
svz_calloc(size_t size)198 svz_calloc (size_t size)
199 {
200   void *ptr = svz_malloc (size);
201   memset (ptr, 0, size);
202   return ptr;
203 }
204 
205 /**
206  * Change the size of a block of memory at @var{ptr}, previously
207  * returned by @code{svz_malloc}, to @var{size} bytes.  If @var{ptr}
208  * is @code{NULL}, allocate a new block.
209  */
210 void *
svz_realloc(void * ptr,size_t size)211 svz_realloc (void *ptr, size_t size)
212 {
213 #if ENABLE_DEBUG
214   size_t old_size, *p;
215 #endif /* ENABLE_DEBUG */
216 #if DEBUG_MEMORY_LEAKS
217   heap_block_t *block;
218 #endif /* DEBUG_MEMORY_LEAKS */
219 
220   heap_caller ();
221   assert (size);
222 
223   if (ptr)
224     {
225 #if ENABLE_DEBUG
226 #if ENABLE_HEAP_COUNT
227 #if DEBUG_MEMORY_LEAKS
228       if ((block = svz_hash_delete (heap, (char *) &ptr)) == NULL ||
229           block->ptr != ptr)
230         {
231           fprintf (stdout, "realloc: %p not found in heap (caller: %p)\n",
232                    ptr, __builtin_return_address (0));
233           assert (0);
234         }
235       svz_free_func (block);
236 #endif /* DEBUG_MEMORY_LEAKS */
237 
238       /* get previous blocksize */
239       p = (size_t *) ptr;
240       p -= 2;
241       old_size = *p;
242       ptr = (void *) p;
243 #endif /* ENABLE_HEAP_COUNT */
244 
245       if ((ptr = (void *) svz_realloc_func (ptr, size + 2 *
246                                             sizeof (size_t))) != NULL)
247         {
248 #if ENABLE_HEAP_COUNT
249           /* save block size */
250           p = (size_t *) ptr;
251           *p = size;
252           p += 2;
253           ptr = (void *) p;
254 
255 #if DEBUG_MEMORY_LEAKS
256           block = svz_malloc_func (sizeof (heap_block_t));
257           block->ptr = ptr;
258           block->size = size;
259           block->caller = __builtin_return_address (0);
260           heap_add (block);
261 #endif /* DEBUG_MEMORY_LEAKS */
262 
263           allocated_bytes += size - old_size;
264 #endif /* ENABLE_HEAP_COUNT */
265 
266           return ptr;
267         }
268 #else /* not ENABLE_DEBUG */
269       if ((ptr = (void *) svz_realloc_func (ptr, size)) != NULL)
270         {
271           return ptr;
272         }
273 #endif /* not ENABLE_DEBUG */
274       else
275         oom ("realloc");
276     }
277   else
278     {
279       ptr = svz_malloc (size);
280       return ptr;
281     }
282 }
283 
284 /**
285  * Free a block of memory at @var{ptr}, previously returned by
286  * @code{svz_malloc} or @code{svz_realloc}.  If @var{ptr} is
287  * @code{NULL}, do nothing.
288  */
289 void
svz_free(void * ptr)290 svz_free (void *ptr)
291 {
292 #if ENABLE_DEBUG
293 #if ENABLE_HEAP_COUNT
294   size_t size, *p;
295 #if DEBUG_MEMORY_LEAKS
296   heap_block_t *block;
297 #endif /* DEBUG_MEMORY_LEAKS */
298 #endif /* ENABLE_HEAP_COUNT */
299 #endif /* ENABLE_DEBUG */
300 
301   heap_caller ();
302 
303   if (ptr)
304     {
305 #if ENABLE_DEBUG
306 #if ENABLE_HEAP_COUNT
307 #if DEBUG_MEMORY_LEAKS
308       if ((block = svz_hash_delete (heap, (char *) &ptr)) == NULL ||
309           block->ptr != ptr)
310         {
311           fprintf (stdout, "free: %p not found in heap (caller: %p)\n",
312                    ptr, __builtin_return_address (0));
313           assert (0);
314         }
315       svz_free_func (block);
316 #endif /* DEBUG_MEMORY_LEAKS */
317 
318       /* get blocksize */
319       p = (size_t *) ptr;
320       p -= 2;
321       size = *p;
322       ptr = (void *) p;
323       assert (size);
324       allocated_bytes -= size;
325 #endif /* ENABLE_HEAP_COUNT */
326 
327       allocated_blocks--;
328 #endif /* ENABLE_DEBUG */
329       svz_free_func (ptr);
330     }
331 }
332 
333 #if DEBUG_MEMORY_LEAKS
334 static void
heap_internal(UNUSED void * k,void * v,UNUSED void * closure)335 heap_internal (UNUSED void *k, void *v, UNUSED void *closure)
336 {
337   heap_block_t *block = v;
338   size_t *p = (size_t *) block->ptr;
339 
340   p -= 2;
341   printf ("heap: caller = %p, ptr = %p, size = %zu\n",
342           block->caller, block->ptr, block->size);
343   svz_hexdump (stdout, "unreleased heap",
344                (int) block->ptr, block->ptr, *p, 256);
345   svz_free_func (block);
346 }
347 
348 /*
349  * Print a list of non-released memory blocks.  This is for debugging only
350  * and should never occur in final software releases.  The function goes
351  * through the heap hash and states each blocks address, size and caller.
352  */
353 void
svz_heap(void)354 svz_heap (void)
355 {
356   if (svz_hash_size (heap))
357     {
358       svz_hash_foreach (heap_internal, heap, NULL);
359     }
360   else
361     {
362       fprintf (stdout, "heap: no unreleased heap blocks\n");
363     }
364   svz_hash_destroy (heap);
365   heap = NULL;
366 }
367 #endif /* DEBUG_MEMORY_LEAKS */
368 
369 /**
370  * Duplicate the given string @var{src} if it is not @code{NULL} and has
371  * non-zero length.  Return the new string.
372  */
373 char *
svz_strdup(const char * src)374 svz_strdup (const char *src)
375 {
376   char *dst;
377   int len;
378 
379   if (src == NULL || (len = strlen (src)) == 0)
380     return NULL;
381 
382   dst = svz_malloc (len + 1);
383   memcpy (dst, src, len + 1);
384   return dst;
385 }
386 
387 /*
388  * Resize the memory block pointed to by @var{ptr} to @var{size} bytes.
389  * This routine also allocates memory permanently.
390  */
391 void *
svz_prealloc(void * ptr,size_t size)392 svz_prealloc (void *ptr, size_t size)
393 {
394   void *dst = svz_realloc_func (ptr, size);
395   if (dst == NULL)
396     oom ("realloc");
397   return dst;
398 }
399 
400 /*
401  * Duplicate the given character string @var{src} permanently.
402  */
403 char *
svz_pstrdup(const char * src)404 svz_pstrdup (const char *src)
405 {
406   char *dst;
407   size_t count;
408 
409   assert (src);
410   count = 1 + strlen (src);
411   if (! (dst = svz_malloc_func (count)))
412     oom ("malloc");
413   memcpy (dst, src, count);
414 
415   return dst;
416 }
417 
418 /**
419  * Write values to @code{to[0]} and @code{to[1]} representing the
420  * number of currently allocated bytes and blocks, respectively.
421  * If Serveez was not configured with @samp{--enable-debug},
422  * the values are always 0.
423  */
424 void
svz_get_curalloc(size_t * to)425 svz_get_curalloc (size_t *to)
426 {
427 #ifndef ENABLE_DEBUG
428   to[0] = to[1] = 0;
429 #else
430   to[0] = allocated_bytes;
431   to[1] = allocated_blocks;
432 #endif
433 }
434