/* * Copyright (c) 2000-2006 Sendmail, Inc. and its suppliers. * All rights reserved. * * By using this file, you agree to the terms and conditions set * forth in the LICENSE file which can be found at the top level of * the sendmail distribution. */ #include "sm/generic.h" SM_RCSID("@(#)$Id: heap.c,v 1.25 2006/10/05 04:27:37 ca Exp $") /* ** Debugging memory allocation package ** ** If SM_HEAP_CHECK is set, then the variable SmHeapCheck ** can be used to turn on/off heap checks at runtime. ** NOTE: Do NOT turn on heap checks AFTER any allocation has been performed ** because then the pointer isn't registered and hence a free() operation ** will cause a failure. */ #include "sm/assert.h" #include "sm/debug.h" #include "sm/string.h" #include "sm/heap.h" #include "heapdbg.h" #include "sm/memops.h" #if SM_HEAP_CHECK # include "sm/io.h" # if MTA_USE_PTHREADS # include "sm/pthread.h" # endif #endif /* undef all macro versions of the "functions" so they can be specified here */ #if !SM_HEAP_CHECK # undef sm_malloc # undef sm_zalloc # undef sm_realloc #endif /* !SM_HEAP_CHECK */ #undef sm_malloc_tagged #undef sm_zalloc_tagged #undef sm_free #undef sm_free_tagged #if SM_HEAP_CHECK # undef sm_heap_register # undef sm_heap_checkptr # undef sm_heap_report #endif /* SM_HEAP_CHECK */ #if SM_HEAP_CHECK #if 0 SM_DEBUG_T SmHeapCheck = SM_DEBUG_INITIALIZER("sm_check_heap", "@(#)$Debug: sm_check_heap - check sm_malloc, sm_realloc, sm_free calls $"); #else /* 0 */ SM_DEBUG_T SmHeapCheck = 1; #endif /* 0 */ # define HEAP_CHECK SmHeapCheck #endif /* SM_HEAP_CHECK */ #define MALLOC_SIZE(size) ((size) == 0 ? 1 : (size)) #if !SM_HEAP_CHECK # if !HAVE_MALLOC /* ** SM_MALLOC -- wrapper around malloc() ** ** Parameters: ** size -- size of requested memory. ** ** Returns: ** Pointer to memory region, NULL on error. */ void * sm_malloc(size_t size) { return malloc(MALLOC_SIZE(size)); } /* ** SM_REALLOC -- wrapper for realloc() ** ** Parameters: ** ptr -- pointer to old memory area. ** size -- size of requested memory. ** ** Returns: ** Pointer to new memory area, NULL on failure. */ void * sm_realloc(void *ptr, size_t size) { return realloc(ptr, MALLOC_SIZE(size)); } # endif /* !HAVE_MALLOC */ /* ** SM_ZALLOC -- malloc() and initialize memory to 0 ** ** Parameters: ** size -- size of requested memory. ** ** Returns: ** Pointer to memory region, NULL on error. */ void * sm_zalloc(size_t size) { void *ptr; ptr = malloc(MALLOC_SIZE(size)); if (ptr != NULL) sm_memzero(ptr, size); return ptr; } #if !SM_FREE_NULL /* ** SM_FREE -- wrapper around free() ** ** Parameters: ** ptr -- pointer to memory region. ** ** Returns: ** none. */ void sm_free(void *ptr) { if (ptr == NULL) return; free(ptr); return; } #endif /* !SM_FREE_NULL */ /* ** SM_FREE_SIZE -- wrapper around free() ** ** Parameters: ** ptr -- pointer to memory region. ** size -- size of released memory. ** ** Returns: ** none. */ void sm_free_size(void *ptr, size_t size) { if (ptr == NULL) return; # if SM_ZERO_FREE /* or other pattern? */ sm_memzero(ptr, size); # endif free(ptr); return; } #else /* !SM_HEAP_CHECK */ # if MTA_USE_PTHREADS pthread_mutex_t SmHeapMutex = PTHREAD_MUTEX_INITIALIZER; # endif /* ** Each allocated block is assigned a "group number". ** By default, all blocks are assigned to group #1. ** By convention, group #0 is for memory that is never freed. ** You can use group numbers any way you want, in order to help make ** sense of sm_heap_report output. */ int SmHeapGroup = 1; int SmHeapMaxGroup = 1; /* ** Total number of bytes allocated. ** This is only maintained if the sm_check_heap debug category is active. */ size_t SmHeapTotal = 0; /* ** High water mark: the most that SmHeapTotal has ever been. */ size_t SmHeapMaxTotal = 0; /* ** Maximum number of bytes that may be allocated at any one time. ** 0 means no limit. ** This is only honoured if sm_check_heap is active. */ # if 0 SM_DEBUG_T SmHeapLimit = SM_DEBUG_INITIALIZER("sm_heap_limit", "@(#)$Debug: sm_heap_limit - max # of bytes permitted in heap $"); # endif static sm_heap_item_T *SmHeapTable[SM_HEAP_TABLE_SIZE]; /* ** SM_MALLOC_TAGGED -- wrapper around malloc(), debugging version. ** ** Parameters: ** size -- size of requested memory. ** tag -- tag for debugging. ** num -- additional value for debugging. ** group -- heap group for debugging. ** ** Returns: ** Pointer to memory region. */ void * sm_malloc_tagged(size_t size, char *tag, int num, int group) { void *ptr; if (!HEAP_CHECK) { ptr = malloc(MALLOC_SIZE(size)); return ptr; } #if 0 if (sm_xtrap_check()) return NULL; if (sm_debug_active(&SmHeapLimit, 1) && sm_debug_level(&SmHeapLimit) < SmHeapTotal + size) return NULL; #endif ptr = malloc(MALLOC_SIZE(size)); if (ptr != NULL && !sm_heap_register(ptr, size, tag, num, group)) { free(ptr); ptr = NULL; } SmHeapTotal += size; if (SmHeapTotal > SmHeapMaxTotal) SmHeapMaxTotal = SmHeapTotal; return ptr; } /* ** SM_ZALLOC_TAGGED -- wrapper around malloc(), debugging version. ** ** Parameters: ** size -- size of requested memory. ** tag -- tag for debugging. ** num -- additional value for debugging. ** group -- heap group for debugging. ** ** Returns: ** Pointer to memory region. */ void * sm_zalloc_tagged(size_t size, char *tag, int num, int group) { void *ptr; if (!HEAP_CHECK) { ptr = malloc(MALLOC_SIZE(size)); if (ptr != NULL) sm_memzero(ptr, size); return ptr; } #if 0 if (sm_xtrap_check()) return NULL; if (sm_debug_active(&SmHeapLimit, 1) && sm_debug_level(&SmHeapLimit) < SmHeapTotal + size) return NULL; #endif ptr = malloc(MALLOC_SIZE(size)); if (ptr != NULL && !sm_heap_register(ptr, size, tag, num, group)) { free(ptr); ptr = NULL; } SmHeapTotal += size; if (SmHeapTotal > SmHeapMaxTotal) SmHeapMaxTotal = SmHeapTotal; if (ptr != NULL) sm_memzero(ptr, size); return ptr; } /* ** SM_HEAP_REGISTER -- register a pointer into the heap for debugging. ** ** Parameters: ** ptr -- pointer to register. ** size -- size of requested memory. ** tag -- tag for debugging. ** num -- additional value for debugging. ** group -- heap group for debugging. ** ** Returns: ** true iff successfully registered (not yet in table). */ bool sm_heap_register(void *ptr, size_t size, char *tag, int num, int group) { int i; # if MTA_USE_PTHREADS int r; # endif sm_heap_item_T *hi; if (!HEAP_CHECK) return true; SM_REQUIRE(ptr != NULL); i = ptrhash(ptr); # if MTA_USE_PTHREADS r = pthread_mutex_lock(&SmHeapMutex); SM_LOCK_OK(r); if (r != 0) return false; # endif # if SM_CHECK_REQUIRE /* We require that ptr is not already in SmHeapTable */ for (hi = SmHeapTable[i]; hi != NULL; hi = hi->hi_next) { if (hi->hi_ptr == ptr) sm_abort("sm_heap_register: ptr %p is already registered (%s:%d)", ptr, hi->hi_tag, hi->hi_num); } # endif /* SM_CHECK_REQUIRE */ hi = (sm_heap_item_T *) malloc(sizeof(sm_heap_item_T)); if (hi == NULL) { # if MTA_USE_PTHREADS r = pthread_mutex_unlock(&SmHeapMutex); SM_ASSERT(r == 0); # endif return false; } hi->hi_ptr = ptr; hi->hi_size = size; hi->hi_tag = tag; hi->hi_num = num; hi->hi_group = group; hi->hi_next = SmHeapTable[i]; SmHeapTable[i] = hi; # if MTA_USE_PTHREADS r = pthread_mutex_unlock(&SmHeapMutex); SM_ASSERT(r == 0); # endif return true; } /* ** SM_REALLOC -- wrapper for realloc(), debugging version. ** ** Parameters: ** ptr -- pointer to old memory area. ** size -- size of requested memory. ** ** Returns: ** Pointer to new memory area, NULL on failure. */ void * sm_realloc(void *ptr, size_t size) { # if MTA_USE_PTHREADS int r; # endif void *newptr; sm_heap_item_T *hi, **hp; if (!HEAP_CHECK) { newptr = realloc(ptr, size); return newptr; } if (ptr == NULL) return sm_malloc_tagged(size, "realloc", 0, SmHeapGroup); # if MTA_USE_PTHREADS r = pthread_mutex_lock(&SmHeapMutex); SM_LOCK_OK(r); if (r != 0) return NULL; # endif for (hp = &SmHeapTable[ptrhash(ptr)]; *hp != NULL; hp = &(**hp).hi_next) { if ((**hp).hi_ptr == ptr) { #if 0 if (sm_xtrap_check()) goto errunl; #endif hi = *hp; #if 0 if (sm_debug_active(&SmHeapLimit, 1) && sm_debug_level(&SmHeapLimit) < SmHeapTotal - hi->hi_size + size) { goto errunl; } #endif /* 0 */ newptr = realloc(ptr, size); if (newptr == NULL) { goto errunl; } SmHeapTotal = SmHeapTotal - hi->hi_size + size; if (SmHeapTotal > SmHeapMaxTotal) SmHeapMaxTotal = SmHeapTotal; *hp = hi->hi_next; hi->hi_ptr = newptr; hi->hi_size = size; hp = &SmHeapTable[ptrhash(newptr)]; hi->hi_next = *hp; *hp = hi; # if MTA_USE_PTHREADS r = pthread_mutex_unlock(&SmHeapMutex); SM_ASSERT(r == 0); # endif return newptr; } } sm_abort("sm_realloc: bad argument (%p)", ptr); /* NOTREACHED */ errunl: # if MTA_USE_PTHREADS r = pthread_mutex_unlock(&SmHeapMutex); SM_ASSERT(r == 0); # endif return NULL; /* keep Irix compiler happy */ } /* ** SM_FREE_TAGGED -- wrapper around free(), debugging version. ** ** Parameters: ** ptr -- pointer to memory region. ** tag -- tag for debugging. ** num -- additional value for debugging. ** ** Returns: ** none. */ void sm_free_tagged(void *ptr, char *tag, int num) { # if MTA_USE_PTHREADS int r; # endif sm_heap_item_T **hp; if (ptr == NULL) return; if (!HEAP_CHECK) { free(ptr); return; } # if MTA_USE_PTHREADS r = pthread_mutex_lock(&SmHeapMutex); SM_ASSERT(r == 0); if (r != 0) return; # endif /* MTA_USE_PTHREADS */ for (hp = &SmHeapTable[ptrhash(ptr)]; *hp != NULL; hp = &(**hp).hi_next) { if ((**hp).hi_ptr == ptr) { sm_heap_item_T *hi = *hp; *hp = hi->hi_next; /* ** Fill the block with zeros before freeing. ** This is intended to catch problems with ** dangling pointers. The block is filled with ** zeros, not with some non-zero value, because ** it is common practice in some C code to store ** a zero in a structure member before freeing the ** structure, as a defense against dangling pointers. */ (void) memset(ptr, 0, hi->hi_size); SmHeapTotal -= hi->hi_size; free(ptr); free(hi); # if MTA_USE_PTHREADS r = pthread_mutex_unlock(&SmHeapMutex); SM_ASSERT(r == 0); # endif return; } } sm_abort("sm_free: bad argument (%p) (%s:%d)", ptr, tag, num); } /* ** SM_FREE_SIZE_TAGGED -- wrapper around free(), debugging version. ** ** Parameters: ** ptr -- pointer to memory region. ** size -- size of released memory. ** tag -- tag for debugging. ** num -- additional value for debugging. ** ** Returns: ** none. */ void sm_free_size_tagged(void *ptr, size_t size, char *tag, int num) { # if MTA_USE_PTHREADS int r; # endif sm_heap_item_T **hp; if (ptr == NULL) return; if (!HEAP_CHECK) { sm_memzero(ptr, size); free(ptr); return; } # if MTA_USE_PTHREADS r = pthread_mutex_lock(&SmHeapMutex); SM_ASSERT(r == 0); if (r != 0) return; # endif /* MTA_USE_PTHREADS */ for (hp = &SmHeapTable[ptrhash(ptr)]; *hp != NULL; hp = &(**hp).hi_next) { if ((**hp).hi_ptr == ptr) { sm_heap_item_T *hi = *hp; *hp = hi->hi_next; /* ** Fill the block with zeros before freeing. ** This is intended to catch problems with ** dangling pointers. The block is filled with ** zeros, not with some non-zero value, because ** it is common practice in some C code to store ** a zero in a structure member before freeing the ** structure, as a defense against dangling pointers. */ (void) memset(ptr, 0, hi->hi_size); SmHeapTotal -= hi->hi_size; sm_memzero(ptr, size); free(ptr); free(hi); # if MTA_USE_PTHREADS r = pthread_mutex_unlock(&SmHeapMutex); SM_ASSERT(r == 0); # endif return; } } sm_abort("sm_free: bad argument (%p) (%s:%d)", ptr, tag, num); } /* ** SM_HEAP_CHECKPTR_TAGGED -- check whether ptr is a valid argument to sm_free ** ** Parameters: ** ptr -- pointer to memory region. ** tag -- tag for debugging. ** num -- additional value for debugging. ** ** Returns: ** none. ** ** Side Effects: ** aborts if check fails. */ void sm_heap_checkptr_tagged(void *ptr, char *tag, int num) { # if MTA_USE_PTHREADS int r; # endif sm_heap_item_T *hp; if (!HEAP_CHECK) return; if (ptr == NULL) return; # if MTA_USE_PTHREADS r = pthread_mutex_lock(&SmHeapMutex); SM_LOCK_OK(r); if (r != 0) return; # endif /* MTA_USE_PTHREADS */ for (hp = SmHeapTable[ptrhash(ptr)]; hp != NULL; hp = hp->hi_next) { if (hp->hi_ptr == ptr) { # if MTA_USE_PTHREADS r = pthread_mutex_unlock(&SmHeapMutex); SM_ASSERT(r == 0); # endif return; } } sm_abort("sm_heap_checkptr(%p): bad ptr (%s:%d)", ptr, tag, num); } /* ** SM_HEAP_REPORT -- output "map" of used heap. ** ** Parameters: ** stream -- the file pointer to write to. ** verbosity -- how much info? ** ** Returns: ** none. */ void sm_heap_report(sm_file_T *stream, int verbosity) { uint i; # if MTA_USE_PTHREADS int r; # endif ulong group0total, group1total, otherstotal, grandtotal, lin, l; if (!HEAP_CHECK || verbosity <= 0) return; group0total = group1total = otherstotal = grandtotal = lin = 0; # if MTA_USE_PTHREADS /* ** NOTE: This can deadlock iff the caller doesn't have stream open ** because SM I/O will allocated a buffer for stream otherwise ** on the first call to sm_io_fprintf(). ** Possible workaround: call sm_io_fprintf() before acquiring the lock. */ r = pthread_mutex_lock(&SmHeapMutex); SM_LOCK_OK(r); if (r != 0) return; # endif /* MTA_USE_PTHREADS */ for (i = 0; i < sizeof(SmHeapTable) / sizeof(SmHeapTable[0]); ++i) { sm_heap_item_T *hi = SmHeapTable[i]; l = 0; while (hi != NULL) { if (verbosity > 2 || (verbosity > 1 && hi->hi_group != 0)) { sm_io_fprintf(stream, "%4d %*lx %7lu bytes", hi->hi_group, (int) sizeof(void *) * 2, (long)hi->hi_ptr, (ulong)hi->hi_size); if (hi->hi_tag != NULL) { sm_io_fprintf(stream, " %s", hi->hi_tag); if (hi->hi_num) { sm_io_fprintf(stream, ":%d", hi->hi_num); } } sm_io_fprintf(stream, "\n"); } switch (hi->hi_group) { case 0: group0total += hi->hi_size; break; case 1: group1total += hi->hi_size; break; default: otherstotal += hi->hi_size; break; } grandtotal += hi->hi_size; hi = hi->hi_next; ++l; } if (lin < l) lin = l; } sm_io_fprintf(stream, "heap max=%lu, total=%lu, " "group 0=%lu, group 1=%lu, others=%lu\nlinear_length=%lu\n", (ulong) SmHeapMaxTotal, grandtotal, group0total, group1total, otherstotal, lin); if (grandtotal != SmHeapTotal) { sm_io_fprintf(stream, "BUG => SmHeapTotal: got %lu, expected %lu\n", (ulong) SmHeapTotal, grandtotal); } sm_io_flush(stream); # if MTA_USE_PTHREADS r = pthread_mutex_unlock(&SmHeapMutex); SM_ASSERT(r == 0); # endif } #endif /* !SM_HEAP_CHECK */