/* * @(#)debug_malloc.c 1.15 10/03/23 * * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * -Redistribution of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * -Redistribution in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Oracle or the names of contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed, licensed or intended * for use in the design, construction, operation or maintenance of any * nuclear facility. */ /* ************************************************************************** * * Set of malloc/realloc/calloc/strdup/free replacement macros that * insert some extra words around each allocation for debugging purposes * and also attempt to detect invalid uses of the malloc heap through * various tricks like inserting clobber words at the head and tail of * the user's area, delayed free() calls, and setting the memory to * a fixed pattern on allocation and when freed. The allocations also * can include warrants so that when an area is clobbered, this * package can report where the allocation took place. * The macros included are: * malloc(size) * realloc(ptr,size) * calloc(nelem,elsize) * strdup(s1) * free(ptr) * malloc_police() <--- Not a system function * The above macros match the standard behavior of the system functions. * * They should be used through the include file "debug_malloc.h". * * IMPORTANT: All source files that call any of these macros * should include debug_malloc.h. This package will * not work if the memory isn't allocated and freed * by the macros in debug_malloc.h. The important issue * is that any malloc() from debug_malloc.h must be * freed by the free() in debug_malloc.h. * * The macros in debug_malloc.h will override the normal use of * malloc, realloc, calloc, strdup, and free with the functions below. * * These functions include: * void *debug_malloc(size_t, void*, int); * void *debug_realloc(void*, size_t, void*, int); * void *debug_calloc(size_t, size_t, void*, int); * void debug_free(void *, void*, int); * * In addition the function debug_malloc_police() can be called to * tell you what memory has not been freed. * void debug_malloc_police(void*, int); * The function debug_malloc_police() is available through the macro * malloc_police(). Normally you would want to call this at exit() * time to find out what memory is still allocated. * * The variable malloc_watch determines if the warrants are generated. * warrants are structures that include the filename and line number * of the caller who allocated the memory. This structure is stored * at the tail of the malloc space, which is allocated large enough * to hold some clobber words at the head and tail, the user's request * and the warrant record (if malloc_watch is non-zero). * * The macro LEFT_OVER_CHAR is what the trailing bytes of an allocation * are set to (when the allocation is not a multiple of 8) on allocation. * At free(0 time, these bytes are double checked to make sure they were * not clobbered. To remove this feature #undef LEFT_OVER_CHAR. * * The memory freed will have the FREED_CHAR put into it. To remove this * feature #undef FREED_CHAR. * * The memory allocated (not calloc'd) will have the ALLOC_CHAR put into it * at the time of allocation. To remove this feature #undef ALLOC_CHAR. * * The macro MAX_FREE_DELAY_COUNT controls how many free blocks will * be kept around before being freed. This creates a delayed affect * so that free space that gets clobbered just might get detected. * The free() call will immediately set the user space to the FREED_CHAR, * leaving the clobber words and warrant in place (making sure they * haven't been clobbered). Then the free() pointer is added to a * queue of MAX_FREE_DELAY_COUNT long, and if the queue was full, the * oldest free()'d memory is actually freed, getting it's entire * memory length set to the FREED_CHAR. * * WARNING: This can significantly slow down an application, depending * on how many allocations are made. Also the additional memory * needed for the clobber words and the warrants can be significant * again, depending on how many allocations are made. * In addition, the delayed free calls can create situations * where you might run out of memory prematurely. * * ************************************************************************** */ #ifdef DEBUG #include #include #include #include #include #include "hprof.h" /* *************************************************************************** * Space normally looks like (clobber Word is 64 bits and aligned to 8 bytes): * * ----------------- * malloc/free get->| clobber Word | ---> contains -size requested by user * ----------------- * User gets --->| user space | * | | * | | left_over | ---> left_over bytes will be <= 7 * ----------------- * | clobber Word | ---> contains -size requested by user * ----------------- * | Warrant | ---> Optional (malloc_watch!=0) * | | Contains filename and line number * | | where allocation happened * | | * ----------------- ***************************************************************************/ /* * Flag that tells debug_malloc/debug_free/debug_realloc to police * heap space usage. (This is a dynamic flag that can be turned on/off) */ static int malloc_watch = 1; /* Character to stuff into freed space */ #define FREED_CHAR 'F' /* Character to stuff into allocated space */ #define ALLOC_CHAR 'A' /* Character to stuff into left over trailing bytes */ #define LEFT_OVER_CHAR 'Z' /* Number of 'free' calls that will be delayed until the end */ #define MAX_FREE_DELAY_COUNT 1 #undef MAX_FREE_DELAY_COUNT /* Maximum name of __FILE_ stored in each malloc'd area */ #define WARRANT_NAME_MAX (32-1) /* 1 less than multiple of 8 is best */ /* Macro to convert a user pointer to the malloc pointer */ #define user2malloc_(uptr) (((char*)(void*)uptr)-sizeof(Word)) /* Macro to convert a macro pointer to the user pointer */ #define malloc2user_(mptr) (((char*)(void*)(mptr))+sizeof(Word)) /* Size of the warrant record (this is dynamic) */ #define warrant_space ( malloc_watch?sizeof(Warrant_Record):0 ) /* Macro to round up a number of bytes to a multiple of sizeof(Word) bytes */ #define round_up_(n) \ ((n)==0?0:(sizeof(Word)+(((n)-1)/sizeof(Word))*sizeof(Word))) /* Macro to calculate the needed malloc bytes from the user's request. */ #define rbytes_(nbytes) \ (size_t)( sizeof(Word) + round_up_(nbytes) + sizeof(Word) + warrant_space ) /* Macro to get the -size stored in space through the malloc pointer */ #define nsize1_(mptr) (((Word*)(void*)(mptr))->nsize1) #define nsize2_(mptr) (((Word*)(void*)(mptr))->nsize2) /* Macro to get the -size stored in the tail of the space through */ /* the malloc pointer */ #define tail_nsize1_(mptr) \ nsize1_(((char*)(void*)(mptr))+round_up_(-nsize1_(mptr))+sizeof(Word)) #define tail_nsize2_(mptr) \ nsize2_(((char*)(void*)(mptr))+round_up_(-nsize1_(mptr))+sizeof(Word)) /* Macro to get the -size stored in space through the user pointer */ #define user_nsize1_(uptr) nsize1_(user2malloc_(uptr)) #define user_nsize2_(uptr) nsize2_(user2malloc_(uptr)) /* Macro to get the -size stored in the tail of the space through */ /* the user pointer */ #define user_tail_nsize1_(uptr) tail_nsize1_(user2malloc_(uptr)) #define user_tail_nsize2_(uptr) tail_nsize2_(user2malloc_(uptr)) /* Macro to get the int* of the last 32bit word of user space */ #define last_user_word_(mptr) \ ((int*)(((char*)(void*)(mptr))+round_up_(-nsize1_(mptr)))) /* Macros to get at the warrant contents from the malloc pointer */ #define warrant_(mptr) \ (*((Warrant_Record*)(void*)(((char*)(void*)(mptr))+round_up_(-nsize1_(mptr))+sizeof(Word)*2))) /* This struct is allocated after the tail clobber word if malloc_watch */ /* is true. */ typedef struct { void *link; /* Next mptr in list */ char name[WARRANT_NAME_MAX + 1]; /* Name of allocator */ int line; /* Line number where allocated */ int id; /* Nth allocation */ } Warrant_Record; #define warrant_link_(mptr) warrant_(mptr).link #define warrant_name_(mptr) warrant_(mptr).name #define warrant_line_(mptr) warrant_(mptr).line #define warrant_id_(mptr) warrant_(mptr).id #define MFILE(mptr) (malloc_watch?warrant_name_(mptr):"?") #define MLINE(mptr) (malloc_watch?warrant_line_(mptr):0) #define MID(mptr) (malloc_watch?warrant_id_(mptr):0) /* This should be one machine word and is also the clobber word struct */ typedef struct { int nsize1; int nsize2; } Word; /* Largest basic type , sizeof(double)? */ /* The first malloc pointer for the warrants */ static void *first_warrant_mptr = NULL; /* Counter of allocations */ static int id_counter = 0; static int largest_size = 0; static void * largest_addr = NULL; static void * smallest_addr = NULL; /* Used to isolate what the error is */ static char *debug_check; static void *clobbered_ptr; /* Minumum macro */ #define minimum(a,b) ((a)<(b)?(a):(b)) /* Message routine */ static void error_message(const char * format, ...) { FILE *error_fp = stderr; /* All debug_malloc.c messages */ va_list ap; va_start(ap, format); (void)fprintf(error_fp, "debug_malloc: "); (void)vfprintf(error_fp, format, ap); (void)fprintf(error_fp, "\n"); (void)fflush(error_fp); va_end(ap); } /* This function prints out a memory error for the memory function * 'name' which was called in file 'file' at line number 'line'. The malloc * pointer with the error is in 'mptr'. */ static void memory_error(void *mptr, const char *name, int mid, const char *mfile, int mline, const char *file, int line) { char nice_words[512]; char temp[256]; int len; void *mptr_walk; if (name == NULL) name = "UNKNOWN_NAME"; if (file == NULL) file = "UNKNOWN_FILE"; md_system_error(temp, (int)sizeof(temp)); (void)strcpy(nice_words, temp); if ( debug_check!=NULL ) { (void)md_snprintf(nice_words, sizeof(nice_words), "%s The %s at %p appears to have been hit.", temp, debug_check, clobbered_ptr); } len = -nsize1_(mptr); error_message("Error: " "%s The malloc space #%d is at %p [user size=%d(0x%x)]," " and was allocated from file \"%s\" at line %d." " [The debug function %s() detected this error " "in file \"%s\" at line %d.]", nice_words, mid, mptr, len, len, mfile, mline, name, file, line); /* Print out contents of this allocation */ { int i; void *uptr = malloc2user_(mptr); char *pmess; pmess = temp; for(i=0;i<(int)sizeof(temp);i++) { int ch = ((unsigned char*)uptr)[i]; if ( isprint(ch) ) { *pmess++ = ch; } else { *pmess++ = '\\'; *pmess++ = 'x'; (void)sprintf(pmess,"%02x",ch); pmess+=2; } } *pmess = 0; error_message("Error: %p contains user data: %s", uptr, temp); } /* Try and print out table */ if (!malloc_watch) { return; } mptr_walk = first_warrant_mptr; if (mptr_walk != NULL) { error_message("Active allocations: " "count=%d, largest_size=%d, address range (%p,%p)", id_counter, largest_size, smallest_addr, largest_addr); do { int size1; int size2; char *mfile_walk; if ( mptr_walk > largest_addr || mptr_walk < smallest_addr ) { error_message("Terminating list due to pointer corruption"); break; } size1 = -nsize1_(mptr_walk); size2 = -nsize2_(mptr_walk); mfile_walk = MFILE(mptr_walk); error_message("#%d: addr=%p size1=%d size2=%d file=\"%.*s\" line=%d", MID(mptr_walk), mptr_walk, size1, size2, WARRANT_NAME_MAX, mfile_walk, MLINE(mptr_walk)); if ( size1 != size2 || size1 > largest_size || size1 < 0 ) { error_message("Terminating list due to size corruption"); break; } mptr_walk = warrant_link_(mptr_walk); } while (mptr_walk != NULL); } abort(); } /* This function sets the clobber word and sets up the warrant for the input * malloc pointer "mptr". */ static void setup_space_and_issue_warrant(void *mptr, size_t size, const char *file, int line) { register int nbytes; /*LINTED*/ nbytes = (int)size; if ( nbytes > largest_size || largest_addr == NULL ) largest_size = nbytes; /*LINTED*/ if ( mptr > largest_addr ) largest_addr = mptr; /*LINTED*/ if ( mptr < smallest_addr || smallest_addr == NULL ) smallest_addr = mptr; /* Must be done first: */ nsize1_(mptr) = -nbytes; nsize2_(mptr) = -nbytes; tail_nsize1_(mptr) = -nbytes; tail_nsize2_(mptr) = -nbytes; #ifdef LEFT_OVER_CHAR /* Fill in those few extra bytes just before the tail Word structure */ { register int trailing_extra_bytes; /* LINTED */ trailing_extra_bytes = (int) (round_up_(nbytes) - nbytes); if ( trailing_extra_bytes > 0 ) { register char *p; register int i; p = ((char *) mptr) + sizeof(Word) + nbytes; for (i = 0; i < trailing_extra_bytes; i++) p[i] = LEFT_OVER_CHAR; } } #endif /* Fill out warrant */ if (malloc_watch) { static Warrant_Record zero_warrant; register void *p1, *p2; size_t len; int start_pos = 0; warrant_(mptr) = zero_warrant; p1 = warrant_name_(mptr); len = strlen(file); if ( len > WARRANT_NAME_MAX ) { /*LINTED*/ start_pos = (int)len - WARRANT_NAME_MAX; } p2 = ((char*)file) + start_pos; /*LINTED*/ (void) memcpy(p1, p2, minimum(((int)len), WARRANT_NAME_MAX)); warrant_line_(mptr) = line; warrant_id_(mptr) = ++id_counter; warrant_link_(mptr) = first_warrant_mptr; first_warrant_mptr = mptr; } } /* This function checks the clobber words at the beginning and end of the * allocated space. */ static void memory_check(void *uptr, int mid, const char *mfile, int mline, const char *file, int line) { int neg_nbytes; int nbytes; debug_check = "pointer value itself"; clobbered_ptr = uptr; if (uptr == NULL) memory_error((void *) NULL, "memory_check", mid, mfile, mline, file, line); /* Check both Word structures */ debug_check = "first beginning clobber word"; clobbered_ptr = (char*)&user_nsize1_(uptr); neg_nbytes = user_nsize1_(uptr); if (neg_nbytes >= 0) memory_error(user2malloc_(uptr), "memory_check", mid, mfile, mline, file, line); debug_check = "second beginning clobber word"; clobbered_ptr = (char*)&user_nsize2_(uptr); if (neg_nbytes != user_nsize2_(uptr)) memory_error(user2malloc_(uptr), "memory_check", mid, mfile, mline, file, line); debug_check = "first ending clobber word"; clobbered_ptr = (char*)&user_tail_nsize1_(uptr); if (neg_nbytes != user_tail_nsize1_(uptr)) memory_error(user2malloc_(uptr), "memory_check", mid, mfile, mline, file, line); debug_check = "second ending clobber word"; clobbered_ptr = (char*)&user_tail_nsize2_(uptr); if (neg_nbytes != user_tail_nsize2_(uptr)) memory_error(user2malloc_(uptr), "memory_check", mid, mfile, mline, file, line); /* Get a positive count of bytes */ nbytes = -neg_nbytes; #ifdef LEFT_OVER_CHAR { /* Check those few extra bytes just before the tail Word structure */ register int trailing_extra_bytes; register int i; register char *p; /* LINTED */ trailing_extra_bytes = (int) (round_up_(nbytes) - nbytes); p = ((char *) (uptr)) + nbytes; debug_check = "trailing left over area"; for (i = 0; i < trailing_extra_bytes; i++) { clobbered_ptr = p+1; if (p[i] != LEFT_OVER_CHAR) { memory_error(user2malloc_(uptr), "memory_check", mid, mfile, mline, file, line); } } } #endif /* Make sure debug_check is cleared */ debug_check = NULL; } /* This function looks for the given malloc pointer in the police line up * and removes it from the warrant list. * mptr The pointer to the malloc space being removed */ static int remove_warrant(void *mptr) { void *mptr1, *last_mptr1; /* Free it up from the list */ if (malloc_watch && mptr != NULL) { int found; found = 0; last_mptr1 = NULL; mptr1 = first_warrant_mptr; while (mptr1 != NULL) { if (mptr1 == mptr) { if (last_mptr1 == NULL) first_warrant_mptr = warrant_link_(mptr1); else warrant_link_(last_mptr1) = warrant_link_(mptr1); found = 1; break; } last_mptr1 = mptr1; mptr1 = warrant_link_(mptr1); } return found; } return 1; } static void actual_free(void *uptr, const char *file, int line) { void *mptr; const char *mfile; int mline; int mid; if ( uptr == NULL ) return; mptr = user2malloc_(uptr); memory_check(uptr, (mid=MID(mptr)), (mfile=MFILE(mptr)), (mline=MLINE(mptr)), file, line); if (malloc_watch && remove_warrant(mptr)==0 ) memory_check(uptr, mid, mfile, mline, file, line); #ifdef FREED_CHAR if ( mptr!=NULL ) { size_t nbytes = -nsize1_(mptr); /* LINTED */ (void)memset(mptr, FREED_CHAR, rbytes_(nbytes)); } #endif free(mptr); } #ifdef MAX_FREE_DELAY_COUNT static void *free_delay[MAX_FREE_DELAY_COUNT]; static int free_delay_pos = 0; static void delayed_free(void *uptr, const char* file, int line) { void *mptr; void *olduptr = free_delay[free_delay_pos]; size_t nbytes; if ( uptr==NULL ) return; mptr = user2malloc_(uptr); memory_check(uptr, MID(mptr), MFILE(mptr), MLINE(mptr), file, line); if ( olduptr!=NULL ) { actual_free(olduptr, file, line); } free_delay[free_delay_pos] = uptr; free_delay_pos++; free_delay_pos = free_delay_pos % MAX_FREE_DELAY_COUNT; nbytes = -user_nsize1_(uptr); #ifdef FREED_CHAR (void)memset(uptr, FREED_CHAR, (size_t)nbytes); #endif } static void delayed_free_all(const char *file, int line) { int i; for ( i=0; i< MAX_FREE_DELAY_COUNT; i++) { void *olduptr = free_delay[i]; free_delay[i] = NULL; if ( olduptr!=NULL ) { actual_free(olduptr, file, line); } } } #endif void debug_free(void *uptr, const char *file, int line) { int mid = 0; if (uptr == NULL) memory_error((void *) NULL, "debug_free", mid, file, line, file, line); #ifdef MAX_FREE_DELAY_COUNT delayed_free(uptr, file, line); #else actual_free(uptr, file, line); #endif } /* This function calls malloc(). */ void * debug_malloc(size_t nbytes, const char *file, int line) { void *mptr; void *uptr; int mid = id_counter; /*LINTED*/ if ((int)nbytes <= 0) memory_error((void *) NULL, "debug_malloc", mid, file, line, file, line); /* LINTED */ mptr = malloc(rbytes_(nbytes)); if (mptr == NULL) memory_error((void *) NULL, "debug_malloc", mid, file, line, file, line); setup_space_and_issue_warrant(mptr, nbytes, file, line); uptr = malloc2user_(mptr); #ifdef ALLOC_CHAR (void)memset(uptr, ALLOC_CHAR, (size_t)nbytes); #endif return uptr; } void * debug_realloc(void *uptr, size_t nbytes, const char *file, int line) { void *mptr; void *oldmptr; void *newuptr; size_t oldnbytes; int mid = id_counter; oldmptr = user2malloc_(uptr); oldnbytes = 0; if ((int)nbytes <= 0) memory_error(oldmptr, "debug_realloc", mid, file, line, file, line); if (uptr != NULL) { memory_check(uptr, MID(oldmptr), MFILE(oldmptr), MLINE(oldmptr), file, line); oldnbytes = -user_nsize1_(uptr); if ( malloc_watch && remove_warrant(oldmptr)==0 ) memory_check(uptr, MID(oldmptr), MFILE(oldmptr), MLINE(oldmptr), file, line); } if (uptr == NULL) { /* LINTED */ mptr = malloc(rbytes_(nbytes)); } else { /* LINTED */ mptr = realloc(oldmptr, rbytes_(nbytes)); } if (mptr == NULL) memory_error(oldmptr, "debug_realloc", mid, file, line, file, line); setup_space_and_issue_warrant(mptr, nbytes, file, line); newuptr = malloc2user_(mptr); #ifdef ALLOC_CHAR if (uptr == NULL) (void)memset(newuptr, ALLOC_CHAR, (size_t)nbytes); else if ( nbytes > oldnbytes ) (void)memset(((char*)newuptr)+oldnbytes, ALLOC_CHAR, (size_t)nbytes-oldnbytes); #endif return newuptr; } /* This function calls calloc(). */ void * debug_calloc(size_t nelem, size_t elsize, const char *file, int line) { void *mptr; size_t nbytes; int mid = id_counter; nbytes = nelem*elsize; /*LINTED*/ if ((int)nbytes <= 0) memory_error((void *) NULL, "debug_calloc", mid, file, line, file, line); /* LINTED */ mptr = calloc(rbytes_(nbytes),1); if (mptr == NULL) memory_error((void *) NULL, "debug_calloc", mid, file, line, file, line); setup_space_and_issue_warrant(mptr, nbytes, file, line); return malloc2user_(mptr); } /* This function replaces strdup(). */ char * debug_strdup(const char *s1, const char *file, int line) { void *mptr; void *uptr; size_t nbytes; int mid = id_counter; if (s1 == NULL) memory_error((void *) NULL, "debug_strdup", mid, file, line, file, line); nbytes = strlen(s1)+1; /*LINTED*/ if ((int)nbytes < 0) memory_error((void *) NULL, "debug_strdup", mid, file, line, file, line); /* LINTED */ mptr = malloc(rbytes_(nbytes)); if (mptr == NULL) memory_error((void *) NULL, "debug_strdup", mid, file, line, file, line); setup_space_and_issue_warrant(mptr, nbytes, file, line); uptr = malloc2user_(mptr); (void)strcpy((char*)uptr, s1); return (char*)uptr; } void debug_malloc_verify(const char *file, int line) { void *mptr; #ifdef MAX_FREE_DELAY_COUNT delayed_free_all(file,line); #endif if (!malloc_watch) { return; } mptr = first_warrant_mptr; if (mptr != NULL) { /* Check all this memory first */ do { memory_check(malloc2user_(mptr), MID(mptr), MFILE(mptr), MLINE(mptr), file, line); mptr = warrant_link_(mptr); } while (mptr != NULL); } } /* Report outstanding space warrants to console. */ void debug_malloc_police(const char *file, int line) { void *mptr; #ifdef MAX_FREE_DELAY_COUNT delayed_free_all(file,line); #endif if (!malloc_watch) { return; } mptr = first_warrant_mptr; if (mptr != NULL) { debug_malloc_verify(file, line); /* Now issue warrants */ mptr = first_warrant_mptr; do { error_message("Outstanding space warrant: %p (%d bytes) allocated by %s at line %d, allocation #%d", mptr, -nsize1_(mptr), warrant_name_(mptr), warrant_line_(mptr), warrant_id_(mptr)); mptr = warrant_link_(mptr); } while (mptr != NULL); } } #else void debug_malloc_verify(const char *file, int line) { file = file; line = line; } void debug_malloc_police(const char *file, int line) { file = file; line = line; } #endif