/* * xmlmemory.c: libxml memory allocator wrapper. * * daniel@veillard.com */ #define IN_LIBXML #include "libxml.h" #include #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_TIME_H #include #endif #ifdef HAVE_STDLIB_H #include #else #ifdef HAVE_MALLOC_H #include #endif #endif #ifdef HAVE_CTYPE_H #include #endif /** * MEM_LIST: * * keep track of all allocated blocks for error reporting * Always build the memory list ! */ #ifdef DEBUG_MEMORY_LOCATION #ifndef MEM_LIST #define MEM_LIST /* keep a list of all the allocated memory blocks */ #endif #endif #include #include #include #include static int xmlMemInitialized = 0; static unsigned long debugMemSize = 0; static unsigned long debugMaxMemSize = 0; static xmlMutexPtr xmlMemMutex = NULL; void xmlMallocBreakpoint(void); /************************************************************************ * * * Macros, variables and associated types * * * ************************************************************************/ #ifdef xmlMalloc #undef xmlMalloc #endif #ifdef xmlRealloc #undef xmlRealloc #endif #ifdef xmlMemStrdup #undef xmlMemStrdup #endif /* * Each of the blocks allocated begin with a header containing informations */ #define MEMTAG 0x5aa5 #define MALLOC_TYPE 1 #define REALLOC_TYPE 2 #define STRDUP_TYPE 3 #define MALLOC_ATOMIC_TYPE 4 #define REALLOC_ATOMIC_TYPE 5 typedef struct memnod { unsigned int mh_tag; unsigned int mh_type; unsigned long mh_number; size_t mh_size; #ifdef MEM_LIST struct memnod *mh_next; struct memnod *mh_prev; #endif const char *mh_file; unsigned int mh_line; } MEMHDR; #ifdef SUN4 #define ALIGN_SIZE 16 #else #define ALIGN_SIZE sizeof(double) #endif #define HDR_SIZE sizeof(MEMHDR) #define RESERVE_SIZE (((HDR_SIZE + (ALIGN_SIZE-1)) \ / ALIGN_SIZE ) * ALIGN_SIZE) #define CLIENT_2_HDR(a) ((MEMHDR *) (((char *) (a)) - RESERVE_SIZE)) #define HDR_2_CLIENT(a) ((void *) (((char *) (a)) + RESERVE_SIZE)) static unsigned int block=0; static unsigned int xmlMemStopAtBlock = 0; static void *xmlMemTraceBlockAt = NULL; #ifdef MEM_LIST static MEMHDR *memlist = NULL; #endif static void debugmem_tag_error(void *addr); #ifdef MEM_LIST static void debugmem_list_add(MEMHDR *); static void debugmem_list_delete(MEMHDR *); #endif #define Mem_Tag_Err(a) debugmem_tag_error(a); #ifndef TEST_POINT #define TEST_POINT #endif /** * xmlMallocBreakpoint: * * Breakpoint to use in conjunction with xmlMemStopAtBlock. When the block * number reaches the specified value this function is called. One need to add a breakpoint * to it to get the context in which the given block is allocated. */ void xmlMallocBreakpoint(void) { xmlGenericError(xmlGenericErrorContext, "xmlMallocBreakpoint reached on block %d\n", xmlMemStopAtBlock); } /** * xmlMallocLoc: * @size: an int specifying the size in byte to allocate. * @file: the file name or NULL * @line: the line number * * a malloc() equivalent, with logging of the allocation info. * * Returns a pointer to the allocated area or NULL in case of lack of memory. */ void * xmlMallocLoc(size_t size, const char * file, int line) { MEMHDR *p; void *ret; if (!xmlMemInitialized) xmlInitMemory(); #ifdef DEBUG_MEMORY xmlGenericError(xmlGenericErrorContext, "Malloc(%d)\n",size); #endif TEST_POINT p = (MEMHDR *) malloc(RESERVE_SIZE+size); if (!p) { xmlGenericError(xmlGenericErrorContext, "xmlMallocLoc : Out of free space\n"); xmlMemoryDump(); return(NULL); } p->mh_tag = MEMTAG; p->mh_size = size; p->mh_type = MALLOC_TYPE; p->mh_file = file; p->mh_line = line; xmlMutexLock(xmlMemMutex); p->mh_number = ++block; debugMemSize += size; if (debugMemSize > debugMaxMemSize) debugMaxMemSize = debugMemSize; #ifdef MEM_LIST debugmem_list_add(p); #endif xmlMutexUnlock(xmlMemMutex); #ifdef DEBUG_MEMORY xmlGenericError(xmlGenericErrorContext, "Malloc(%d) Ok\n",size); #endif if (xmlMemStopAtBlock == p->mh_number) xmlMallocBreakpoint(); ret = HDR_2_CLIENT(p); if (xmlMemTraceBlockAt == ret) { xmlGenericError(xmlGenericErrorContext, "%p : Malloc(%d) Ok\n", xmlMemTraceBlockAt, size); xmlMallocBreakpoint(); } TEST_POINT return(ret); } /** * xmlMallocAtomicLoc: * @size: an int specifying the size in byte to allocate. * @file: the file name or NULL * @line: the line number * * a malloc() equivalent, with logging of the allocation info. * * Returns a pointer to the allocated area or NULL in case of lack of memory. */ void * xmlMallocAtomicLoc(size_t size, const char * file, int line) { MEMHDR *p; void *ret; if (!xmlMemInitialized) xmlInitMemory(); #ifdef DEBUG_MEMORY xmlGenericError(xmlGenericErrorContext, "Malloc(%d)\n",size); #endif TEST_POINT p = (MEMHDR *) malloc(RESERVE_SIZE+size); if (!p) { xmlGenericError(xmlGenericErrorContext, "xmlMallocLoc : Out of free space\n"); xmlMemoryDump(); return(NULL); } p->mh_tag = MEMTAG; p->mh_size = size; p->mh_type = MALLOC_ATOMIC_TYPE; p->mh_file = file; p->mh_line = line; xmlMutexLock(xmlMemMutex); p->mh_number = ++block; debugMemSize += size; if (debugMemSize > debugMaxMemSize) debugMaxMemSize = debugMemSize; #ifdef MEM_LIST debugmem_list_add(p); #endif xmlMutexUnlock(xmlMemMutex); #ifdef DEBUG_MEMORY xmlGenericError(xmlGenericErrorContext, "Malloc(%d) Ok\n",size); #endif if (xmlMemStopAtBlock == p->mh_number) xmlMallocBreakpoint(); ret = HDR_2_CLIENT(p); if (xmlMemTraceBlockAt == ret) { xmlGenericError(xmlGenericErrorContext, "%p : Malloc(%d) Ok\n", xmlMemTraceBlockAt, size); xmlMallocBreakpoint(); } TEST_POINT return(ret); } /** * xmlMemMalloc: * @size: an int specifying the size in byte to allocate. * * a malloc() equivalent, with logging of the allocation info. * * Returns a pointer to the allocated area or NULL in case of lack of memory. */ void * xmlMemMalloc(size_t size) { return(xmlMallocLoc(size, "none", 0)); } /** * xmlReallocLoc: * @ptr: the initial memory block pointer * @size: an int specifying the size in byte to allocate. * @file: the file name or NULL * @line: the line number * * a realloc() equivalent, with logging of the allocation info. * * Returns a pointer to the allocated area or NULL in case of lack of memory. */ void * xmlReallocLoc(void *ptr,size_t size, const char * file, int line) { MEMHDR *p; unsigned long number; if (ptr == NULL) return(xmlMallocLoc(size, file, line)); if (!xmlMemInitialized) xmlInitMemory(); TEST_POINT p = CLIENT_2_HDR(ptr); number = p->mh_number; if (p->mh_tag != MEMTAG) { Mem_Tag_Err(p); goto error; } p->mh_tag = ~MEMTAG; xmlMutexLock(xmlMemMutex); debugMemSize -= p->mh_size; #ifdef MEM_LIST debugmem_list_delete(p); #endif xmlMutexUnlock(xmlMemMutex); p = (MEMHDR *) realloc(p,RESERVE_SIZE+size); if (!p) { goto error; } if (xmlMemTraceBlockAt == ptr) { xmlGenericError(xmlGenericErrorContext, "%p : Realloced(%d -> %d) Ok\n", xmlMemTraceBlockAt, p->mh_size, size); xmlMallocBreakpoint(); } p->mh_tag = MEMTAG; p->mh_number = number; p->mh_type = REALLOC_TYPE; p->mh_size = size; p->mh_file = file; p->mh_line = line; xmlMutexLock(xmlMemMutex); debugMemSize += size; if (debugMemSize > debugMaxMemSize) debugMaxMemSize = debugMemSize; #ifdef MEM_LIST debugmem_list_add(p); #endif xmlMutexUnlock(xmlMemMutex); TEST_POINT return(HDR_2_CLIENT(p)); error: return(NULL); } /** * xmlMemRealloc: * @ptr: the initial memory block pointer * @size: an int specifying the size in byte to allocate. * * a realloc() equivalent, with logging of the allocation info. * * Returns a pointer to the allocated area or NULL in case of lack of memory. */ void * xmlMemRealloc(void *ptr,size_t size) { return(xmlReallocLoc(ptr, size, "none", 0)); } /** * xmlMemFree: * @ptr: the memory block pointer * * a free() equivalent, with error checking. */ void xmlMemFree(void *ptr) { MEMHDR *p; char *target; if (ptr == (void *) -1) { xmlGenericError(xmlGenericErrorContext, "trying to free pointer from freed area\n"); goto error; } if (xmlMemTraceBlockAt == ptr) { xmlGenericError(xmlGenericErrorContext, "%p : Freed()\n", xmlMemTraceBlockAt); xmlMallocBreakpoint(); } TEST_POINT target = (char *) ptr; p = CLIENT_2_HDR(ptr); if (p->mh_tag != MEMTAG) { Mem_Tag_Err(p); goto error; } p->mh_tag = ~MEMTAG; memset(target, -1, p->mh_size); xmlMutexLock(xmlMemMutex); debugMemSize -= p->mh_size; #ifdef MEM_LIST debugmem_list_delete(p); #endif xmlMutexUnlock(xmlMemMutex); free(p); TEST_POINT return; error: xmlGenericError(xmlGenericErrorContext, "xmlMemFree(%lX) error\n", (unsigned long) ptr); xmlMallocBreakpoint(); return; } /** * xmlMemStrdupLoc: * @str: the initial string pointer * @file: the file name or NULL * @line: the line number * * a strdup() equivalent, with logging of the allocation info. * * Returns a pointer to the new string or NULL if allocation error occurred. */ char * xmlMemStrdupLoc(const char *str, const char *file, int line) { char *s; size_t size = strlen(str) + 1; MEMHDR *p; if (!xmlMemInitialized) xmlInitMemory(); TEST_POINT p = (MEMHDR *) malloc(RESERVE_SIZE+size); if (!p) { goto error; } p->mh_tag = MEMTAG; p->mh_size = size; p->mh_type = STRDUP_TYPE; p->mh_file = file; p->mh_line = line; xmlMutexLock(xmlMemMutex); p->mh_number = ++block; debugMemSize += size; if (debugMemSize > debugMaxMemSize) debugMaxMemSize = debugMemSize; #ifdef MEM_LIST debugmem_list_add(p); #endif xmlMutexUnlock(xmlMemMutex); s = (char *) HDR_2_CLIENT(p); if (xmlMemStopAtBlock == p->mh_number) xmlMallocBreakpoint(); if (s != NULL) strcpy(s,str); else goto error; TEST_POINT if (xmlMemTraceBlockAt == s) { xmlGenericError(xmlGenericErrorContext, "%p : Strdup() Ok\n", xmlMemTraceBlockAt); xmlMallocBreakpoint(); } return(s); error: return(NULL); } /** * xmlMemoryStrdup: * @str: the initial string pointer * * a strdup() equivalent, with logging of the allocation info. * * Returns a pointer to the new string or NULL if allocation error occurred. */ char * xmlMemoryStrdup(const char *str) { return(xmlMemStrdupLoc(str, "none", 0)); } /** * xmlMemUsed: * * Provides the amount of memory currently allocated * * Returns an int representing the amount of memory allocated. */ int xmlMemUsed(void) { return(debugMemSize); } #ifdef MEM_LIST /** * xmlMemContentShow: * @fp: a FILE descriptor used as the output file * @p: a memory block header * * tries to show some content from the memory block */ static void xmlMemContentShow(FILE *fp, MEMHDR *p) { int i,j,len = p->mh_size; const char *buf = (const char *) HDR_2_CLIENT(p); if (p == NULL) { fprintf(fp, " NULL"); return; } for (i = 0;i < len;i++) { if (buf[i] == 0) break; if (!isprint((unsigned char) buf[i])) break; } if ((i < 4) && ((buf[i] != 0) || (i == 0))) { if (len >= 4) { MEMHDR *q; void *cur; for (j = 0;j < len -3;j += 4) { cur = *((void **) &buf[j]); q = CLIENT_2_HDR(cur); p = memlist; while (p != NULL) { if (p == q) break; p = p->mh_next; } if ((p != NULL) && (p == q)) { fprintf(fp, " pointer to #%lu at index %d", p->mh_number, j); return; } } } } else if ((i == 0) && (buf[i] == 0)) { fprintf(fp," null"); } else { if (buf[i] == 0) fprintf(fp," \"%.25s\"", buf); else { fprintf(fp," ["); for (j = 0;j < i;j++) fprintf(fp,"%c", buf[j]); fprintf(fp,"]"); } } } #endif /** * xmlMemDisplay: * @fp: a FILE descriptor used as the output file, if NULL, the result is * written to the file .memorylist * * show in-extenso the memory blocks allocated */ void xmlMemDisplay(FILE *fp) { #ifdef MEM_LIST MEMHDR *p; unsigned idx; int nb = 0; #if defined(HAVE_LOCALTIME) && defined(HAVE_STRFTIME) time_t currentTime; char buf[500]; struct tm * tstruct; currentTime = time(NULL); tstruct = localtime(¤tTime); strftime(buf, sizeof(buf) - 1, "%I:%M:%S %p", tstruct); fprintf(fp," %s\n\n", buf); #endif fprintf(fp," MEMORY ALLOCATED : %lu, MAX was %lu\n", debugMemSize, debugMaxMemSize); fprintf(fp,"BLOCK NUMBER SIZE TYPE\n"); idx = 0; xmlMutexLock(xmlMemMutex); p = memlist; while (p) { fprintf(fp,"%-5u %6lu %6lu ",idx++,p->mh_number, (unsigned long)p->mh_size); switch (p->mh_type) { case STRDUP_TYPE:fprintf(fp,"strdup() in ");break; case MALLOC_TYPE:fprintf(fp,"malloc() in ");break; case REALLOC_TYPE:fprintf(fp,"realloc() in ");break; case MALLOC_ATOMIC_TYPE:fprintf(fp,"atomicmalloc() in ");break; case REALLOC_ATOMIC_TYPE:fprintf(fp,"atomicrealloc() in ");break; default:fprintf(fp," ??? in ");break; } if (p->mh_file != NULL) fprintf(fp,"%s(%d)", p->mh_file, p->mh_line); if (p->mh_tag != MEMTAG) fprintf(fp," INVALID"); nb++; if (nb < 100) xmlMemContentShow(fp, p); else fprintf(fp," skip"); fprintf(fp,"\n"); p = p->mh_next; } xmlMutexUnlock(xmlMemMutex); #else fprintf(fp,"Memory list not compiled (MEM_LIST not defined !)\n"); #endif } #ifdef MEM_LIST static void debugmem_list_add(MEMHDR *p) { p->mh_next = memlist; p->mh_prev = NULL; if (memlist) memlist->mh_prev = p; memlist = p; #ifdef MEM_LIST_DEBUG if (stderr) Mem_Display(stderr); #endif } static void debugmem_list_delete(MEMHDR *p) { if (p->mh_next) p->mh_next->mh_prev = p->mh_prev; if (p->mh_prev) p->mh_prev->mh_next = p->mh_next; else memlist = p->mh_next; #ifdef MEM_LIST_DEBUG if (stderr) Mem_Display(stderr); #endif } #endif /* * debugmem_tag_error: * * internal error function. */ static void debugmem_tag_error(void *p) { xmlGenericError(xmlGenericErrorContext, "Memory tag error occurs :%p \n\t bye\n", p); #ifdef MEM_LIST if (stderr) xmlMemDisplay(stderr); #endif } #ifdef MEM_LIST static FILE *xmlMemoryDumpFile = NULL; #endif /** * xmlMemShow: * @fp: a FILE descriptor used as the output file * @nr: number of entries to dump * * show a show display of the memory allocated, and dump * the @nr last allocated areas which were not freed */ void xmlMemShow(FILE *fp, int nr ATTRIBUTE_UNUSED) { #ifdef MEM_LIST MEMHDR *p; #endif if (fp != NULL) fprintf(fp," MEMORY ALLOCATED : %lu, MAX was %lu\n", debugMemSize, debugMaxMemSize); #ifdef MEM_LIST xmlMutexLock(xmlMemMutex); if (nr > 0) { fprintf(fp,"NUMBER SIZE TYPE WHERE\n"); p = memlist; while ((p) && nr > 0) { fprintf(fp,"%6lu %6lu ",p->mh_number,(unsigned long)p->mh_size); switch (p->mh_type) { case STRDUP_TYPE:fprintf(fp,"strdup() in ");break; case MALLOC_TYPE:fprintf(fp,"malloc() in ");break; case MALLOC_ATOMIC_TYPE:fprintf(fp,"atomicmalloc() in ");break; case REALLOC_TYPE:fprintf(fp,"realloc() in ");break; case REALLOC_ATOMIC_TYPE:fprintf(fp,"atomicrealloc() in ");break; default:fprintf(fp," ??? in ");break; } if (p->mh_file != NULL) fprintf(fp,"%s(%d)", p->mh_file, p->mh_line); if (p->mh_tag != MEMTAG) fprintf(fp," INVALID"); xmlMemContentShow(fp, p); fprintf(fp,"\n"); nr--; p = p->mh_next; } } xmlMutexUnlock(xmlMemMutex); #endif /* MEM_LIST */ } /** * xmlMemoryDump: * * Dump in-extenso the memory blocks allocated to the file .memorylist */ void xmlMemoryDump(void) { #ifdef MEM_LIST FILE *dump; if (debugMaxMemSize == 0) return; dump = fopen(".memdump", "w"); if (dump == NULL) xmlMemoryDumpFile = stderr; else xmlMemoryDumpFile = dump; xmlMemDisplay(xmlMemoryDumpFile); if (dump != NULL) fclose(dump); #endif /* MEM_LIST */ } /**************************************************************** * * * Initialization Routines * * * ****************************************************************/ /** * xmlInitMemory: * * Initialize the memory layer. * * Returns 0 on success */ int xmlInitMemory(void) { #ifdef HAVE_STDLIB_H char *breakpoint; #endif /* This is really not good code (see Bug 130419). Suggestions for improvement will be welcome! */ if (xmlMemInitialized) return(-1); xmlMemInitialized = 1; xmlMemMutex = xmlNewMutex(); #ifdef HAVE_STDLIB_H breakpoint = getenv("XML_MEM_BREAKPOINT"); if (breakpoint != NULL) { sscanf(breakpoint, "%ud", &xmlMemStopAtBlock); } #endif #ifdef HAVE_STDLIB_H breakpoint = getenv("XML_MEM_TRACE"); if (breakpoint != NULL) { sscanf(breakpoint, "%p", &xmlMemTraceBlockAt); } #endif #ifdef DEBUG_MEMORY xmlGenericError(xmlGenericErrorContext, "xmlInitMemory() Ok\n"); #endif return(0); } /** * xmlCleanupMemory: * * Free up all the memory associated with memorys */ void xmlCleanupMemory(void) { if (xmlMemInitialized == 0) return; xmlFreeMutex(xmlMemMutex); xmlMemMutex = NULL; xmlMemInitialized = 0; } /** * xmlMemSetup: * @freeFunc: the free() function to use * @mallocFunc: the malloc() function to use * @reallocFunc: the realloc() function to use * @strdupFunc: the strdup() function to use * * Override the default memory access functions with a new set * This has to be called before any other libxml routines ! * * Should this be blocked if there was already some allocations * done ? * * Returns 0 on success */ int xmlMemSetup(xmlFreeFunc freeFunc, xmlMallocFunc mallocFunc, xmlReallocFunc reallocFunc, xmlStrdupFunc strdupFunc) { if (freeFunc == NULL) return(-1); if (mallocFunc == NULL) return(-1); if (reallocFunc == NULL) return(-1); if (strdupFunc == NULL) return(-1); xmlFree = freeFunc; xmlMalloc = mallocFunc; xmlMallocAtomic = mallocFunc; xmlRealloc = reallocFunc; xmlMemStrdup = strdupFunc; return(0); } /** * xmlMemGet: * @freeFunc: place to save the free() function in use * @mallocFunc: place to save the malloc() function in use * @reallocFunc: place to save the realloc() function in use * @strdupFunc: place to save the strdup() function in use * * Provides the memory access functions set currently in use * * Returns 0 on success */ int xmlMemGet(xmlFreeFunc *freeFunc, xmlMallocFunc *mallocFunc, xmlReallocFunc *reallocFunc, xmlStrdupFunc *strdupFunc) { if (freeFunc != NULL) *freeFunc = xmlFree; if (mallocFunc != NULL) *mallocFunc = xmlMalloc; if (reallocFunc != NULL) *reallocFunc = xmlRealloc; if (strdupFunc != NULL) *strdupFunc = xmlMemStrdup; return(0); } /** * xmlGcMemSetup: * @freeFunc: the free() function to use * @mallocFunc: the malloc() function to use * @mallocAtomicFunc: the malloc() function to use for atomic allocations * @reallocFunc: the realloc() function to use * @strdupFunc: the strdup() function to use * * Override the default memory access functions with a new set * This has to be called before any other libxml routines ! * The mallocAtomicFunc is specialized for atomic block * allocations (i.e. of areas useful for garbage collected memory allocators * * Should this be blocked if there was already some allocations * done ? * * Returns 0 on success */ int xmlGcMemSetup(xmlFreeFunc freeFunc, xmlMallocFunc mallocFunc, xmlMallocFunc mallocAtomicFunc, xmlReallocFunc reallocFunc, xmlStrdupFunc strdupFunc) { if (freeFunc == NULL) return(-1); if (mallocFunc == NULL) return(-1); if (mallocAtomicFunc == NULL) return(-1); if (reallocFunc == NULL) return(-1); if (strdupFunc == NULL) return(-1); xmlFree = freeFunc; xmlMalloc = mallocFunc; xmlMallocAtomic = mallocAtomicFunc; xmlRealloc = reallocFunc; xmlMemStrdup = strdupFunc; return(0); } /** * xmlGcMemGet: * @freeFunc: place to save the free() function in use * @mallocFunc: place to save the malloc() function in use * @mallocAtomicFunc: place to save the atomic malloc() function in use * @reallocFunc: place to save the realloc() function in use * @strdupFunc: place to save the strdup() function in use * * Provides the memory access functions set currently in use * The mallocAtomicFunc is specialized for atomic block * allocations (i.e. of areas useful for garbage collected memory allocators * * Returns 0 on success */ int xmlGcMemGet(xmlFreeFunc *freeFunc, xmlMallocFunc *mallocFunc, xmlMallocFunc *mallocAtomicFunc, xmlReallocFunc *reallocFunc, xmlStrdupFunc *strdupFunc) { if (freeFunc != NULL) *freeFunc = xmlFree; if (mallocFunc != NULL) *mallocFunc = xmlMalloc; if (mallocAtomicFunc != NULL) *mallocAtomicFunc = xmlMallocAtomic; if (reallocFunc != NULL) *reallocFunc = xmlRealloc; if (strdupFunc != NULL) *strdupFunc = xmlMemStrdup; return(0); }