/* * 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: mem.c,v 1.3 2006/10/05 04:27:37 ca Exp $") /* ** Debugging memory allocation package with memory context. ** ** NOTES: ** 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. ** ** This is a split of heap.c, it would be nice to merge these ** together again. */ #include "sm/assert.h" #include "sm/error.h" #include "sm/debug.h" #include "sm/string.h" #include "sm/mem.h" #include "heapdbg.h" #include "sm/memops.h" #if SM_HEAP_CHECK # include "sm/io.h" #endif #if MTA_USE_PTHREADS # include "sm/pthread.h" #endif struct sm_mctx_S { sm_magic_T sm_magic; size_t sm_mctx_cur; /* current memory usage */ size_t sm_mctx_max; /* maximum memory usage */ size_t sm_mctx_lim; /* upper limit for memory usage */ /* not yet checked */ uint sm_mctx_flags; sm_mctx_oom_F *sm_mctx_oom; /* callback if out of memory */ void *sm_mctx_cbctx; /* application context to pass to cb */ #if MTA_USE_PTHREADS pthread_mutex_t sm_mctx_mutex; #endif }; #define SM_MCTX_SET_FLAG(sm_mctx, fl) (sm_mctx)->sm_mctx_flags |= (fl) #define SM_MCTX_CLR_FLAG(sm_mctx, fl) (sm_mctx)->sm_mctx_flags &= ~(fl) #define SM_MCTX_IS_FLAG(sm_mctx, fl) (((sm_mctx)->sm_mctx_flags & (fl)) != 0) /* undef all macro versions of the "functions" so they can be specified here */ #if !SM_HEAP_CHECK # undef sm_ctxmalloc # undef sm_ctxzalloc # undef sm_ctxrealloc #endif /* !SM_HEAP_CHECK */ #undef sm_ctxalloc_tagged #undef sm_ctxfree #undef sm_ctxfree_tagged #if SM_HEAP_CHECK # undef sm_ctxheap_register # undef sm_ctxheap_checkptr # undef sm_ctxheap_report sm_heap_item_T *SmMemTable[SM_HEAP_TABLE_SIZE]; #endif /* SM_HEAP_CHECK */ #define MALLOC_SIZE(size) ((size) == 0 ? 1 : (size)) /* ** SM_MCTX_NEW -- create new mctx ** ** Parameters: ** limit -- limit for memory usage (0: none). ** oomcb -- callback for out of memory ** cbctx -- context for callback ** psm_mctx -- (pointer to) memory context (output) ** ** Returns: ** usual error code. */ sm_ret_T sm_mctx_new(size_t limit, sm_mctx_oom_F oomcb, void *cbctx, sm_mctx_P *psm_mctx) { sm_mctx_P sm_mctx; #if MTA_USE_PTHREADS int r; #endif SM_REQUIRE(psm_mctx != NULL); sm_mctx = (sm_mctx_P) malloc(sizeof(*sm_mctx)); if (sm_mctx == NULL) return sm_err_temp(ENOMEM); #if MTA_USE_PTHREADS r = pthread_mutex_init(&(sm_mctx->sm_mctx_mutex), SM_PTHREAD_MUTEXATTR); if (r != 0) { free(sm_mctx); return sm_err_temp(r); } #endif sm_mctx->sm_mctx_cur = 0; sm_mctx->sm_mctx_max = 0; sm_mctx->sm_mctx_lim = limit; sm_mctx->sm_mctx_oom = oomcb; sm_mctx->sm_mctx_cbctx = cbctx; sm_mctx->sm_magic = SM_MCTX_MAGIC; *psm_mctx = sm_mctx; return SM_SUCCESS; } /* ** SM_MCTX_FLAGS -- set flags for mctx ** ** Parameters: ** sm_mctx -- memory context ** flags -- flags to set ** ** Returns: ** usual error code. */ sm_ret_T sm_mctx_flags(sm_mctx_P sm_mctx, uint flags) { #if MTA_USE_PTHREADS int r; #endif SM_IS_MCTX(sm_mctx); #if MTA_USE_PTHREADS r = pthread_mutex_lock(&(sm_mctx->sm_mctx_mutex)); SM_ASSERT(r == 0); #endif sm_mctx->sm_mctx_flags = flags; #if MTA_USE_PTHREADS r = pthread_mutex_unlock(&(sm_mctx->sm_mctx_mutex)); SM_ASSERT(r == 0); #endif return SM_SUCCESS; } /* ** SM_MCTX_DESTROY -- destroy new mctx ** ** Parameters: ** sm_mctx -- memory context to destroy ** ** Returns: ** usual error code. */ sm_ret_T sm_mctx_destroy(sm_mctx_P sm_mctx) { if (sm_mctx == NULL) return SM_SUCCESS; SM_IS_MCTX(sm_mctx); #if MTA_USE_PTHREADS (void) pthread_mutex_destroy(&(sm_mctx->sm_mctx_mutex)); #endif sm_mctx->sm_magic = SM_MAGIC_NULL; free(sm_mctx); return SM_SUCCESS; } /* ** SM_CTXMALLOC -- wrapper around malloc() ** ** Parameters: ** sm_mctx -- memory context ** flags -- some flags ** size -- size of requested memory. ** tag -- tag for debugging. ** num -- additional value for debugging. ** group -- heap group for debugging. ** ** Returns: ** Pointer to memory region, NULL on error. */ void * #if !SM_HEAP_CHECK sm_ctxalloc(sm_mctx_P sm_mctx, uint flags, size_t size) #else sm_ctxalloc_tagged(sm_mctx_P sm_mctx, uint flags, size_t size, char *tag, int num, int group) #endif { #if MTA_USE_PTHREADS int r; #endif void *ptr; ptr = malloc(MALLOC_SIZE(size)); if (sm_mctx == NULL) return ptr; if (ptr == NULL && sm_mctx->sm_mctx_oom != NULL) { sm_ret_T ret; ret = sm_mctx->sm_mctx_oom(size, sm_mctx->sm_mctx_cur, sm_mctx->sm_mctx_lim, sm_mctx->sm_mctx_cbctx); if (ret == SM_SUCCESS) { ptr = malloc(MALLOC_SIZE(size)); if (ptr == NULL) return NULL; } } if (SM_IS_FLAG(flags, SM_FL_ZALLOC) && ptr != NULL) sm_memzero(ptr, size); #if SM_HEAP_CHECK if (SM_MCTX_IS_FLAG(sm_mctx, SM_MCTX_FL_CHK)) { if (ptr != NULL && !sm_ctxheap_register(sm_mctx, ptr, size, tag, num, group)) { free(ptr); ptr = NULL; size = 0; } } #endif if (size > 0) { #if MTA_USE_PTHREADS r = pthread_mutex_lock(&(sm_mctx->sm_mctx_mutex)); SM_ASSERT(r == 0); #endif sm_mctx->sm_mctx_cur += size; if (sm_mctx->sm_mctx_max < sm_mctx->sm_mctx_cur) sm_mctx->sm_mctx_max = sm_mctx->sm_mctx_cur; #if MTA_USE_PTHREADS r = pthread_mutex_unlock(&(sm_mctx->sm_mctx_mutex)); SM_ASSERT(r == 0); #endif } return ptr; } /* ** SM_CTXREALLOCSIZE -- wrapper for realloc() ** ** Parameters: ** sm_mctx -- memory context ** ptr -- pointer to old memory area. ** oldsize -- size of memory currently used. ** newsize -- size of requested memory. ** ** Returns: ** Pointer to new memory area, NULL on failure. */ void * sm_ctxreallocsize(sm_mctx_P sm_mctx, void *ptr, size_t oldsize, size_t newsize) { #if MTA_USE_PTHREADS int r; #endif void *newptr; newptr = realloc(ptr, MALLOC_SIZE(newsize)); if (sm_mctx == NULL) return newptr; if (newptr == NULL && sm_mctx->sm_mctx_oom != NULL) { sm_ret_T ret; ret = sm_mctx->sm_mctx_oom(newsize, sm_mctx->sm_mctx_cur, sm_mctx->sm_mctx_lim, sm_mctx->sm_mctx_cbctx); if (ret == SM_SUCCESS) { newptr = realloc(ptr, MALLOC_SIZE(newsize)); if (newptr == NULL) return NULL; } } #if MTA_USE_PTHREADS r = pthread_mutex_lock(&(sm_mctx->sm_mctx_mutex)); SM_ASSERT(r == 0); #endif if (sm_mctx->sm_mctx_cur <= oldsize) sm_mctx->sm_mctx_cur = 0; else sm_mctx->sm_mctx_cur -= oldsize; sm_mctx->sm_mctx_cur += newsize; if (sm_mctx->sm_mctx_max < sm_mctx->sm_mctx_cur) sm_mctx->sm_mctx_max = sm_mctx->sm_mctx_cur; #if MTA_USE_PTHREADS r = pthread_mutex_unlock(&(sm_mctx->sm_mctx_mutex)); SM_ASSERT(r == 0); #endif return ptr; } /* ** SM_CTXREALLOC -- wrapper for realloc() ** ** Parameters: ** sm_mctx -- memory context ** ptr -- pointer to old memory area. ** size -- size of requested memory. ** ** Returns: ** Pointer to new memory area, NULL on failure. */ void * sm_ctxrealloc(sm_mctx_P sm_mctx, void *ptr, size_t size) { void *newptr; #if SM_HEAP_CHECK sm_heap_item_T *hi, **hp; # if MTA_USE_PTHREADS int r; # endif #endif newptr = realloc(ptr, MALLOC_SIZE(size)); if (sm_mctx == NULL) return newptr; if (newptr == NULL && sm_mctx->sm_mctx_oom != NULL) { sm_ret_T ret; ret = sm_mctx->sm_mctx_oom(size, sm_mctx->sm_mctx_cur, sm_mctx->sm_mctx_lim, sm_mctx->sm_mctx_cbctx); if (ret == SM_SUCCESS) { newptr = realloc(ptr, MALLOC_SIZE(size)); if (newptr == NULL) return NULL; } } /* keeping track of size requires old size, see sm_ctxreallocsize */ #if SM_HEAP_CHECK if (SM_MCTX_IS_FLAG(sm_mctx, SM_MCTX_FL_CHK)) { # if MTA_USE_PTHREADS r = pthread_mutex_lock(&(sm_mctx->sm_mctx_mutex)); if (r != 0) return NULL; # endif for (hp = &SmMemTable[ptrhash(ptr)]; *hp != NULL; hp = &(**hp).hi_next) { if ((**hp).hi_ptr == ptr) { hi = *hp; newptr = realloc(ptr, size); if (newptr == NULL) { # if MTA_USE_PTHREADS r = pthread_mutex_unlock(&(sm_mctx->sm_mctx_mutex)); SM_ASSERT(r == 0); # endif return NULL; } if (sm_mctx->sm_mctx_cur <= hi->hi_size) sm_mctx->sm_mctx_cur = 0; else sm_mctx->sm_mctx_cur -= hi->hi_size; sm_mctx->sm_mctx_cur += size; if (sm_mctx->sm_mctx_max < sm_mctx->sm_mctx_cur) sm_mctx->sm_mctx_max = sm_mctx->sm_mctx_cur; *hp = hi->hi_next; hi->hi_ptr = newptr; hi->hi_size = size; hp = &SmMemTable[ptrhash(newptr)]; hi->hi_next = *hp; *hp = hi; # if MTA_USE_PTHREADS r = pthread_mutex_unlock(&(sm_mctx->sm_mctx_mutex)); SM_ASSERT(r == 0); # endif return newptr; } } sm_abort("sm_realloc: bad argument (%p)", ptr); /* NOTREACHED */ return NULL; /* keep Irix compiler happy */ } #endif return newptr; } /* ** SM_CTXFREE -- wrapper around free() ** ** Parameters: ** sm_mctx -- memory context ** ptr -- pointer to memory region. ** tag -- tag for debugging. ** num -- additional value for debugging. ** ** Returns: ** none. */ void #if !SM_HEAP_CHECK sm_ctxfree(sm_mctx_P sm_mctx, void *ptr) #else sm_ctxfree_tagged(sm_mctx_P sm_mctx, void *ptr, char *tag, int num) #endif { #if SM_HEAP_CHECK # if MTA_USE_PTHREADS int r; # endif sm_heap_item_T **hp; #endif if (ptr == NULL) return; #if !SM_HEAP_CHECK free(ptr); return; #else if (sm_mctx == NULL || !SM_MCTX_IS_FLAG(sm_mctx, SM_MCTX_FL_CHK)) { free(ptr); return; } # if MTA_USE_PTHREADS r = pthread_mutex_lock(&(sm_mctx->sm_mctx_mutex)); SM_ASSERT(r == 0); if (r != 0) return; # endif /* MTA_USE_PTHREADS */ for (hp = &SmMemTable[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); if (sm_mctx->sm_mctx_cur <= hi->hi_size) sm_mctx->sm_mctx_cur = 0; else sm_mctx->sm_mctx_cur -= hi->hi_size; free(ptr); free(hi); # if MTA_USE_PTHREADS r = pthread_mutex_unlock(&(sm_mctx->sm_mctx_mutex)); SM_ASSERT(r == 0); # endif return; } } sm_abort("sm_free: bad argument (%p) (%s:%d)", ptr, tag, num); #endif } /* ** SM_CTXFREE_SIZE -- wrapper around free() ** ** Parameters: ** sm_mctx -- memory context ** ptr -- pointer to memory region. ** size -- size of released memory. ** tag -- tag for debugging. ** num -- additional value for debugging. ** ** Returns: ** none. */ void #if !SM_HEAP_CHECK sm_ctxfree_size(sm_mctx_P sm_mctx, void *ptr, size_t size) #else sm_ctxfree_size_tagged(sm_mctx_P sm_mctx, void *ptr, size_t size, char *tag, int num) #endif { #if SM_HEAP_CHECK sm_heap_item_T **hp; #endif #if MTA_USE_PTHREADS int r; #endif if (ptr == NULL) return; #if SM_ZERO_FREE /* or other pattern? */ sm_memzero(ptr, size); #endif if (sm_mctx != NULL) { SM_IS_MCTX(sm_mctx); #if MTA_USE_PTHREADS r = pthread_mutex_lock(&(sm_mctx->sm_mctx_mutex)); SM_ASSERT(r == 0); #endif if (sm_mctx->sm_mctx_cur <= size) sm_mctx->sm_mctx_cur = 0; else sm_mctx->sm_mctx_cur -= size; #if MTA_USE_PTHREADS r = pthread_mutex_unlock(&(sm_mctx->sm_mctx_mutex)); SM_ASSERT(r == 0); #endif } #if !SM_HEAP_CHECK free(ptr); return; #else if (sm_mctx == NULL || !SM_MCTX_IS_FLAG(sm_mctx, SM_MCTX_FL_CHK)) { sm_memzero(ptr, size); free(ptr); return; } # if MTA_USE_PTHREADS r = pthread_mutex_lock(&(sm_mctx->sm_mctx_mutex)); SM_ASSERT(r == 0); if (r != 0) return; # endif /* MTA_USE_PTHREADS */ for (hp = &SmMemTable[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); SM_ASSERT(size == hi->hi_size); sm_memzero(ptr, size); free(ptr); free(hi); # if MTA_USE_PTHREADS r = pthread_mutex_unlock(&(sm_mctx->sm_mctx_mutex)); SM_ASSERT(r == 0); # endif return; } } sm_abort("sm_free: bad argument (%p) (%s:%d)", ptr, tag, num); #endif } #if SM_HEAP_CHECK /* ** SM_CTXHEAP_REGISTER -- register a pointer into the heap for debugging. ** ** Parameters: ** sm_mctx -- memory context ** 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_ctxheap_register(sm_mctx_P sm_mctx, 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; SM_IS_MCTX(sm_mctx); if (!SM_MCTX_IS_FLAG(sm_mctx, SM_MCTX_FL_CHK)) return true; SM_REQUIRE(ptr != NULL); i = ptrhash(ptr); # if MTA_USE_PTHREADS r = pthread_mutex_lock(&(sm_mctx->sm_mctx_mutex)); if (r != 0) return false; # endif # if SM_CHECK_REQUIRE /* We require that ptr is not already in SmMemTable */ for (hi = SmMemTable[i]; hi != NULL; hi = hi->hi_next) { if (hi->hi_ptr == ptr) sm_abort("sm_ctxheap_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(&(sm_mctx->sm_mctx_mutex)); 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 = SmMemTable[i]; SmMemTable[i] = hi; # if MTA_USE_PTHREADS r = pthread_mutex_unlock(&(sm_mctx->sm_mctx_mutex)); SM_ASSERT(r == 0); # endif return true; } /* ** SM_CTXHEAP_CHECKPTR_TAGGED -- check whether ptr is a valid argument to sm_free ** ** Parameters: ** sm_mctx -- memory context ** ptr -- pointer to memory region. ** tag -- tag for debugging. ** num -- additional value for debugging. ** ** Returns: ** none. ** ** Side Effects: ** aborts if check fails. */ void sm_ctxheap_checkptr_tagged(sm_mctx_P sm_mctx, void *ptr, char *tag, int num) { # if MTA_USE_PTHREADS int r; # endif sm_heap_item_T *hp; SM_IS_MCTX(sm_mctx); if (!SM_MCTX_IS_FLAG(sm_mctx, SM_MCTX_FL_CHK)) return; if (ptr == NULL) return; # if MTA_USE_PTHREADS r = pthread_mutex_lock(&(sm_mctx->sm_mctx_mutex)); if (r != 0) return; # endif /* MTA_USE_PTHREADS */ for (hp = SmMemTable[ptrhash(ptr)]; hp != NULL; hp = hp->hi_next) { if (hp->hi_ptr == ptr) { # if MTA_USE_PTHREADS r = pthread_mutex_unlock(&(sm_mctx->sm_mctx_mutex)); SM_ASSERT(r == 0); # endif return; } } sm_abort("sm_heap_checkptr(%p): bad ptr (%s:%d)", ptr, tag, num); } /* ** SM_CTXHEAP_REPORT -- output "map" of used heap. ** ** Parameters: ** sm_mctx -- memory context ** stream -- the file pointer to write to. ** verbosity -- how much info? ** ** Returns: ** none. */ void sm_ctxheap_report(sm_mctx_P sm_mctx, sm_file_T *stream, int verbosity) { uint i; # if MTA_USE_PTHREADS int r; # endif ulong group0total, group1total, otherstotal, grandtotal, lin, l; SM_IS_MCTX(sm_mctx); if (!SM_MCTX_IS_FLAG(sm_mctx, SM_MCTX_FL_CHK) || 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(&(sm_mctx->sm_mctx_mutex)); if (r != 0) return; # endif /* MTA_USE_PTHREADS */ for (i = 0; i < sizeof(SmMemTable) / sizeof(SmMemTable[0]); ++i) { sm_heap_item_T *hi = SmMemTable[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) sm_mctx->sm_mctx_max, grandtotal, group0total, group1total, otherstotal, lin); if (grandtotal != sm_mctx->sm_mctx_cur) { sm_io_fprintf(stream, "BUG => SmMemTotal: got %lu, expected %lu\n", (ulong) sm_mctx->sm_mctx_cur, grandtotal); } sm_io_flush(stream); # if MTA_USE_PTHREADS r = pthread_mutex_unlock(&(sm_mctx->sm_mctx_mutex)); SM_ASSERT(r == 0); # endif } #endif /* !SM_HEAP_CHECK */