/*-------------------------------------------------------------------------*/ /** @file xmemory.c @author Nicolas Devillard @date Oct 2000 @version $Revision: 1.26 $ @brief POSIX-compatible extended memory handling. xmemory is a small and efficient module offering memory extension capabitilies to ANSI C programs running on POSIX-compliant systems. If offers several useful features such as memory leak detection, protection for free on NULL or unallocated pointers, and virtually unlimited memory space. xmemory requires the @c mmap() system call to be implemented in the local C library to function. This module has been tested on a number of current Unix flavours and is reported to work fine. See the documentation attached to this module for more information. */ /*--------------------------------------------------------------------------*/ /* $Id: xmemory.c,v 1.26 2002/01/14 12:24:57 ndevilla Exp $ $Author: ndevilla $ $Date: 2002/01/14 12:24:57 $ $Revision: 1.26 $ */ /*--------------------------------------------------------------------------- Includes ---------------------------------------------------------------------------*/ #include #include #include #include #include #include #include #include #include #include /*--------------------------------------------------------------------------- Defines ---------------------------------------------------------------------------*/ /** Set the following to 1 to get zillions of debug messages */ #define XMEMORY_DEBUG 0 /** Initial number of entries in memory table */ #define XMEMORY_MINSZ 4096 /** Identify true RAM memory */ #define MEMTYPE_RAM 0 /** Identify swap memory */ #define MEMTYPE_SWAP 1 /** Identify memory-mapped file */ #define MEMTYPE_MMAP 2 /** Minimal page size in bytes */ #define MEMPAGESZ 2048 /** Size of temporary dir name */ #define TMPDIRNAMESZ 1024 /** Size of temporary file names */ #define TMPFILENAMESZ 1024 /** Size of source file names */ #define SRCFILENAMESZ 64 /** Size of mapped file names */ #define MAPFILENAMESZ 512 /** Tracing feature for gcc > 2.95 */ #if (__GNUC__>2) || ((__GNUC__ == 2) && (__GNUC_MINOR__ > 95)) #define __NOTRACE__ __attribute__((__no_instrument_function__)) #else #define __NOTRACE__ #endif /*--------------------------------------------------------------------------- Macros ---------------------------------------------------------------------------*/ /** @def xmem_debug @brief Macro to hide away debug code at compile time */ #if XMEMORY_DEBUG #define xmem_debug( code ) { code } #else #define xmem_debug( code ) #endif /*--------------------------------------------------------------------------- New types ---------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/ /** @typedef xmemory_cell @brief Allocated pointer cell (INTERNAL) This structure is strictly internal to this source file. One such structure is reserved per allocated pointer. Memory use for one cell struct should be less than 32 bytes. */ /*-------------------------------------------------------------------------*/ typedef struct _xmemory_cell_ { /* Common to all pointers */ /** Returned pointer */ void * pointer ; /** Pointed size in bytes */ size_t size ; /** Name of the source file where the alloc was requested */ char filename[SRCFILENAMESZ] ; /** Line number where the alloc was requested */ int lineno ; /** Memory type: RAM, swap, or mapped file */ int memtype ; /* Swap memory only */ /** Swap file ID */ int swapfileid ; /** Swap file descriptor */ int swapfd ; /* Mapped files only */ /** Name of mapped file */ char mm_filename[MAPFILENAMESZ]; /** Hash of mapped file name for quick search */ unsigned mm_hash ; /** Reference counter for this pointer */ int mm_refcount ; } xmemory_cell ; /*-------------------------------------------------------------------------*/ /** @var xmemory_table @brief Main memory table (INTERNAL) This table holds a list pointer cells (all the ones allocated so far). It is strictly internal to this source file. */ /*-------------------------------------------------------------------------*/ struct { /** Set this flag to 0 to deactivate all xmemory activity */ int active ; /** List of memory cells */ xmemory_cell * cell ; /** Number of active cells */ int n ; /** Current storage space in number of cells */ int size ; /** Total allocated memory in bytes */ size_t alloc_total ; /** Total allocated RAM in bytes */ size_t alloc_ram ; /** Total allocated VM in bytes */ size_t alloc_swap ; /** Peak allocation ever seen for diagnostics */ size_t alloc_max ; /** Current number of swap files */ int nswapfiles ; /** Registration counter for swap files */ int file_reg ; /** Current number of memory-mapped files */ int n_mm_files ; /** Current number of mappings derived from files */ int n_mm_mappings ; /** Flag to indicate signal initialization took place */ int siginit ; } xmemory_table = {1, 0,0,0, 0,0,0,0, 0,0, 0,0, 0} ; /*--------------------------------------------------------------------------- Global variables ---------------------------------------------------------------------------*/ /** Path to temporary directory */ static char xmemory_tmpdirname[TMPDIRNAMESZ] = "." ; /*--------------------------------------------------------------------------- Private function prototypes ---------------------------------------------------------------------------*/ static unsigned xmemory_hash(char *); static void xmemory_sig(int); static void xmemory_init(void); static void xmemory_cleanup(void); static void xmemory_addcell(void*,size_t,char*,int,int,int,int,char*); static int xmemory_remcell(void *, int); static void xmemory_dumpcell(xmemory_cell*, FILE*); static char * xmemory_tmpfilename(int); void xmemory_status_(char * filename, int lineno); /*--------------------------------------------------------------------------- Function codes ---------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/ /** @brief Activate xmemory @return void This function activates xmemory activity. Make sure it is always used consistently with xmemory_off(). */ /*--------------------------------------------------------------------------*/ void xmemory_on(void) { xmemory_table.active=1 ; } /*-------------------------------------------------------------------------*/ /** @brief Deactivate xmemory @return void This function deactivates xmemory activity. Make sure it is always used consistently with xmemory_on(). */ /*--------------------------------------------------------------------------*/ void xmemory_off(void) { xmemory_table.active=0 ; } /*-------------------------------------------------------------------------*/ /** @brief Hash a string to an unsigned value. @param key String to hash @return 1 unsigned value as a hash for the given string. This hash function has been taken from an Article in Dr Dobbs Journal. This is normally a collision-free function, distributing keys evenly. The key is stored anyway in the struct so that collision can be avoided by comparing the key itself in last resort. */ /*--------------------------------------------------------------------------*/ static unsigned xmemory_hash(char * key) { int len ; unsigned hash ; int i ; len = strlen(key); for (hash=0, i=0 ; i>6) ; } hash += (hash <<3); hash ^= (hash >>11); hash += (hash <<15); return hash ; } /*-------------------------------------------------------------------------*/ /** @brief Signal handler for this module. @param sig Signal ID. @return void This function handles Unix signals. SIGSEGV and SIGBUS will print out a bug message and exit the current process. SIGINT and SIGTERM will print a "user interruption" message and exit the current process. SIGFPE is caught and ignored. All other signals are ignored. The exit code returned by this function is -signal. */ /*--------------------------------------------------------------------------*/ static void xmemory_sig(int sig) { switch (sig) { case SIGSEGV: case SIGBUS: /* Segmentation fault: print bug art and exit */ fprintf(stderr, "\n\n" "\t \\_/ **** fatal error in pid %ld\n" "\t'-0-' **** segmentation fault or bus error\n" "\t--0--\n" "\t.-0-.\n\n\n", (long)getpid()); break; case SIGFPE: /* Ignore floating-point exceptions */ return ; break ; case SIGTERM: case SIGINT: /* CTRL-C or interrupt signal: abort with message */ fprintf(stderr, "**** user interrupted process %ld ****\n", (long)getpid()); break; default: /* Do not handle other signals */ return ; } exit(-sig); } /*-------------------------------------------------------------------------*/ /** @brief Initialize extended memory features. @return void This function is implicitly called by the first malloc() or calloc() or strdup() execution. It allocates a minimal number of memory cells into the global extended memory table. It also installs signal handlers and atexit routines the first time it is called, and increases the number of possible descriptors to the maximum. */ /*--------------------------------------------------------------------------*/ static void __NOTRACE__ xmemory_init(void) { xmemory_cell * new_cell ; struct rlimit rlim ; /* Signal and atexit initialization, only once. */ if (xmemory_table.siginit==0) { xmem_debug( fprintf(stderr, "xmem: initializing signal catcher and atexit\n"); ); /* Catch signals */ signal(SIGTERM, xmemory_sig); signal(SIGINT , xmemory_sig); signal(SIGSEGV, xmemory_sig); signal(SIGBUS , xmemory_sig); signal(SIGFPE , xmemory_sig); /* Install cleanup routine at exit */ atexit(xmemory_cleanup); /* Increase number of descriptors to maximum */ getrlimit(RLIMIT_NOFILE, &rlim) ; xmem_debug( fprintf(stderr, "xmem: increasing from %ld to %ld file handles\n", (long)rlim.rlim_cur, (long)rlim.rlim_max); ); rlim.rlim_cur = rlim.rlim_max ; setrlimit(RLIMIT_NOFILE, &rlim) ; /* Indicate that the allocation has taken place */ xmemory_table.siginit ++ ; } /* Check if table init already took place */ if (xmemory_table.size>0) return ; /* Send out message in debug mode */ xmem_debug( fprintf(stderr, "xmem: initializing main table size=%d\n", XMEMORY_MINSZ); ); /* Allocate minimal number of cells */ new_cell = calloc(XMEMORY_MINSZ, sizeof(xmemory_cell)); if (new_cell==NULL) { /* A memory allocation failure at that level is critical */ fprintf(stderr, "out of memory -- %s (%d)\n", __FILE__, __LINE__); exit(-2001); } /* Assign newly allocated cell to main memory table */ xmemory_table.cell = new_cell ; xmemory_table.size = XMEMORY_MINSZ ; return ; } /*-------------------------------------------------------------------------*/ /** @brief Removes all swap files. @return void This routine will delete all swap files from the temporary area. */ /*--------------------------------------------------------------------------*/ static void __NOTRACE__ xmemory_cleanup(void) { int reg ; if (xmemory_table.file_reg>0) { xmem_debug( fprintf(stderr, "xmem: cleaning up swap files... "); ); /* * Call remove() on all possible VM files. If the file exists, it * is effectively removed. It it does not, ignore the error. * This is not the cleanest way of doing it, but this function is * meant to be called also in cases of emergency (e.g. segfault), * so it should not rely on a correct memory table. */ for (reg=0 ; reg= xmemory_table.size) { /* Not enough space: double the array size */ xmem_debug( fprintf(stderr, "xmem: double table size to %d\n", xsize); ); xsize = xmemory_table.size ; /* Reallocate table size */ xsize *= 2 ; new_cell = realloc(xmemory_table.cell, xsize * sizeof(xmemory_cell)); if (new_cell==NULL) { /* A memory allocation failure at that level is critical */ fprintf(stderr, "xmem: out of memory -- %s (%d)\n", __FILE__, __LINE__); exit(-2001); } xmemory_table.cell = new_cell ; /* Record new size */ xmemory_table.size = xsize ; } /* Look for an empty space in cell table */ for (i=0 ; i>1) && (xn >= XMEMORY_MINSZ)) { new_cell = calloc((xsize>>1), sizeof(xmemory_cell)); if (new_cell==NULL) { /* A memory allocation failure at that level is critical */ fprintf(stderr, "out of memory -- %s (%d)\n", __FILE__, __LINE__); exit(-2001); } j=0 ; for (i=0 ; i>1 ; xmem_debug( fprintf(stderr, "xmem: shrinking main table size=%d\n", xsize>>1); ); } return 0 ; } /*-------------------------------------------------------------------------*/ /** @brief Dump a memory cell to an open file pointer. @param cell Cell to dump. @param out Open file pointer to dump to. @return void This function is meant for debugging purposes only. It takes in input a pointer to a memory cell and dumps it to the requested file pointer (it is Ok to provide stdout or stderr as file pointers). If the passed pointer is NULL or the pointer information in the cell is NULL, this function returns immediately. */ /*--------------------------------------------------------------------------*/ static void __NOTRACE__ xmemory_dumpcell(xmemory_cell * cell, FILE * out) { if (cell==NULL) return ; if (cell->pointer==NULL) return ; if (cell->memtype == MEMTYPE_MMAP) { fprintf(out, "(%p) from %s (%d) maps file [%s] for %ld bytes", cell->pointer, cell->filename, cell->lineno, cell->mm_filename, (long)cell->size); } else { fprintf(out, "(%p) from %s (%d) in %s has %ld bytes", cell->pointer, cell->filename, cell->lineno, cell->memtype==MEMTYPE_RAM ? "RAM " : "SWAP", (long)cell->size); } if (cell->memtype==MEMTYPE_SWAP) { fprintf(out, " swap[%s] fd=%d", xmemory_tmpfilename(cell->swapfileid), cell->swapfd); } fprintf(out, "\n"); } /*-------------------------------------------------------------------------*/ /** @brief Compute filename associated to a temporary file ID. @param reg Registration number of temporary file name. @return pointer to statically allocated char string. This function computes the valid file name associated to a temporary file ID. It computes the result, stores it in an internal static string and returns a pointer to it. */ /*--------------------------------------------------------------------------*/ static char * __NOTRACE__ xmemory_tmpfilename(int reg) { static char xmem_tmpfilename[TMPFILENAMESZ]; /* Create file name using tmp directory as a base */ sprintf(xmem_tmpfilename, "%s/vmswap_%05ld_%05x", xmemory_tmpdirname, (long)getpid(), reg); return xmem_tmpfilename; } /*-------------------------------------------------------------------------*/ /** @brief Allocate memory. @param size Size (in bytes) to allocate. @param filename Name of the file where the alloc took place. @param lineno Line number in the file. @return 1 newly allocated pointer. This function is a replacement call for malloc. It should never be called directly but through a macro instead, as: @code xmemory_malloc(size, __FILE__, __LINE__) @endcode Notice that if the @c XMEMORY_DIEONNOALLOC symbol is defined to 1, this function might exit the program (if the system's malloc returns NULL). Change the value of the symbol to turn that behaviour off, but you need to check the return values of your malloc calls in that case. */ /*--------------------------------------------------------------------------*/ void * __NOTRACE__ xmemory_malloc(size_t size, char * filename, int lineno) { void * ptr ; char * fname ; int swapfileid ; int swapfd ; char wbuf[MEMPAGESZ] ; int nbufs ; int memtype ; int i ; #ifdef __linux__ int p, psize ; #endif if (xmemory_table.active==0) return malloc(size); /* Protect the call */ if (size==0) { xmem_debug( fprintf(stderr, "xmem: malloc called with 0 size - %s (%d)\n", filename, lineno); ); return NULL ; } /* Initialization of the main memory table */ if (xmemory_table.size<1) { xmemory_init(); } /* Try to allocate in memory */ ptr = malloc(size); if (ptr==NULL) { /* No more RAM available: try to allocate private swap */ xmem_debug( fprintf(stderr, "xmem: hit a NULL pointer -- swapping\n"); ); /* Create swap file with rights: rw-rw-rw- */ swapfileid = ++ xmemory_table.file_reg ; fname = xmemory_tmpfilename(swapfileid); swapfd = open(fname, O_RDWR | O_CREAT); if (swapfd==-1) { fprintf(stderr, "xmem: fatal error: cannot create swap file\n"); exit(-2001); } fchmod(swapfd, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH); /* Compute number of passes to insert buffer */ nbufs = size / MEMPAGESZ ; if (size % MEMPAGESZ != 0) { nbufs ++ ; } /* Dump empty buffers into file */ memset(wbuf, 0, MEMPAGESZ); for (i=0 ; i xmemory_table.alloc_max) xmemory_table.alloc_max = xmemory_table.alloc_total ; return ptr ; } /*-------------------------------------------------------------------------*/ /** @brief Allocate memory. @param nmemb Number of elements to allocate. @param size Size (in bytes) of each element. @param filename Name of the file where the alloc took place. @param lineno Line number in the file. @return 1 newly allocated pointer. This function is a replacement call for calloc. It should never be called directly but through a macro instead, as: @code xmemory_calloc(nmemb, size, __FILE__, __LINE__) @endcode Notice that if the @c XMEMORY_DIEONNOALLOC symbol is defined to 1, this function might exit the program (if the system's calloc returns NULL). Change the value of the symbol to turn that behaviour off, but you need to check the return values of your calloc calls in that case. */ /*--------------------------------------------------------------------------*/ void * __NOTRACE__ xmemory_calloc(size_t nmemb, size_t size, char * filename, int lineno) { void * ptr ; if (xmemory_table.active==0) return calloc(nmemb, size); ptr = xmemory_malloc(nmemb * size, filename, lineno); return memset(ptr, 0, nmemb * size); } /*-------------------------------------------------------------------------*/ /** @brief Map a file's contents to memory as a char pointer. @param name Name of the file to map @param offs Offset to the first mapped byte in file. @param size Returned size of the mapped file in bytes. @param srcname Name of the source file making the call. @param srclin Line # where the call was made. @return A pointer to char, to be freed using xmemory_free(). This function takes in input the name of a file. It tries to map the file into memory and if it succeeds, returns the file's contents as a char pointer. It also modifies the input size variable to be the size of the mapped file in bytes. This function is normally never directly called but through the falloc() macro. The offset indicates the starting point for the mapping, i.e. if you are not interested in mapping the whole file but only from a given place. */ /*--------------------------------------------------------------------------*/ char * __NOTRACE__ xmemory_falloc( char * name, size_t offs, size_t * size, char * srcname, int srclin) { int i ; unsigned mm_hash ; char * ptr ; struct stat sta ; int fd ; if (xmemory_table.active==0) xmemory_table.active=1 ; /* Protect the call */ if (offs<0) { xmem_debug( fprintf(stderr, "xmem: falloc offs<0 - %s (%d)\n", srcname, srclin); ); return NULL ; } if (size!=NULL) { *size = 0 ; } /* Check if file has already been mapped */ /* Compute hash for this name */ mm_hash = xmemory_hash(name); /* Loop over all memory cells */ for (i=0 ; i= xmemory_table.cell[i].size) { xmem_debug( fprintf(stderr, "xmem: falloc offset larger than file size"); ); return NULL ; } /* Increase reference counter */ xmemory_table.cell[i].mm_refcount ++ ; xmem_debug( fprintf(stderr, "xmem: incref on %s (%d mappings)\n", name, xmemory_table.cell[i].mm_refcount); ); /* Increase number of mappings */ xmemory_table.n_mm_mappings ++ ; /* Build up return pointer */ ptr = (char*)xmemory_table.cell[i].pointer + offs ; /* Available size is filesize minus offset */ if (size!=NULL) { *size = xmemory_table.cell[i].size - offs ; } /* Return constructed pointer as void * */ return (void*)ptr ; } } } /* First mapping attempt for this file */ /* Check file's existence and compute its size */ if (stat(name, &sta)==-1) { xmem_debug( fprintf(stderr, "xmem: cannot stat file %s - %s (%d)\n", name, srcname, srclin); ); return NULL ; } /* Check offset request does not go past end of file */ if (offs>=sta.st_size) { xmem_debug( fprintf(stderr, "xmem: falloc offsets larger than file size"); ); return NULL ; } /* Open file */ if ((fd=open(name, O_RDONLY))==-1) { xmem_debug( fprintf(stderr, "xmem: cannot open file %s - %s (%d)\n", name, srcname, srclin); ); return NULL ; } /* Memory-map input file */ ptr = (char*)mmap(0, sta.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); /* Close file */ close(fd); if (ptr == (char*)-1 || ptr==NULL) { xmem_debug( perror("mmap"); fprintf(stderr, "xmem: falloc cannot mmap file %s", name); ); return NULL ; } /* Possible initialization of the main memory table */ if (xmemory_table.size<1) { xmemory_init(); } xmemory_table.n_mm_files ++ ; xmemory_table.n_mm_mappings ++ ; xmem_debug( fprintf(stderr, "xmem: falloc mmap succeeded for [%s] - %s (%d)\n", name, srcname, srclin); ); /* Add cell into general table */ xmemory_addcell((void*)ptr, sta.st_size, srcname, srclin, MEMTYPE_MMAP, -1, -1, name); if (size!=NULL) { (*size) = sta.st_size ; } return ptr + offs ; } /*-------------------------------------------------------------------------*/ /** @brief Free memory. @param ptr Pointer to free. @param filename Name of the file where the dealloc took place. @param lineno Line number in the file. @return void Free the memory associated to a given pointer. Prints out a warning on stderr if the requested pointer is NULL or cannot be found in the extended memory table. */ /*--------------------------------------------------------------------------*/ void __NOTRACE__ xmemory_free(void * ptr, char * filename, int lineno) { int xsize ; int i ; int pos ; char * swapname ; xmemory_cell * xm ; /* Do nothing for a NULL pointer */ if (ptr==NULL) { /* Output a warning */ fprintf(stderr, "xmem: free requested on NULL pointer -- %s (%d)\n", filename, lineno); return ; } if (xmemory_table.active==0) { free(ptr); return ; } /* Locate pointer in main table */ xsize = xmemory_table.size ; xm = NULL ; pos = -1 ; for (i=0 ; i= (char*)ptr)) { xm = &(xmemory_table.cell[i]) ; pos = i ; break ; } } else if (xmemory_table.cell[i].pointer == ptr) { xm = &(xmemory_table.cell[i]) ; pos = i ; break ; } } if (xm==NULL) { fprintf(stderr, "xmem: %s (%d) free requested on unallocated pointer (%p)\n", filename, lineno, ptr); /* Pointer sent to system's free() function, maybe it should not? */ free(ptr); return ; } /* Deallocate pointer */ switch (xm->memtype) { case MEMTYPE_RAM: /* --- RAM pointer */ /* Free normal memory pointer */ free(ptr); xmemory_table.alloc_ram -= xm->size ; break ; case MEMTYPE_SWAP: /* --- SWAP pointer */ swapname = xmemory_tmpfilename(xm->swapfileid); xmem_debug( fprintf(stderr, "xmem: deallocating swap file [%s]\n", swapname); ); /* Munmap file */ if (munmap(ptr, xm->size)!=0) { xmem_debug( perror("munmap"); ); } /* Close swap file */ if (close(xm->swapfd)==-1) { xmem_debug( perror("close"); ); } /* Remove swap file */ if (remove(swapname)!=0) { xmem_debug( perror("remove"); ); } xmemory_table.alloc_swap -= xm->size ; xmemory_table.nswapfiles -- ; break ; case MEMTYPE_MMAP: /* --- MEMORY-MAPPED pointer */ /* Decrease reference count */ xm->mm_refcount -- ; /* Non-null ref count means the file stays mapped */ if (xm->mm_refcount>0) { xmem_debug( fprintf(stderr, "xmem: decref on %s (%d mappings)\n", xm->mm_filename, xm->mm_refcount); ); xmemory_table.n_mm_mappings -- ; return ; } /* Ref count reached zero: unmap the file */ xmem_debug( fprintf(stderr, "xmem: unmapping file %s\n", xm->mm_filename); ); munmap((char*)xm->pointer, xm->size); xmemory_table.n_mm_files -- ; break ; default: xmem_debug( fprintf(stderr, "xmem: unknown memory cell type???"); ); break ; } if (xm->memtype!=MEMTYPE_MMAP) { /* Adjust allocated totals */ xmemory_table.alloc_total -= xm->size ; /* Print out message in debug mode */ xmem_debug( fprintf(stderr, "xmem: free(%p) %ld bytes in %s (%d)\n", ptr, (long)xm->size, filename, lineno); ); } /* Remove cell from main table */ xmemory_remcell(ptr, pos) ; return ; } /*-------------------------------------------------------------------------*/ /** @brief Duplicate a string using calloc. @param s String to duplicate. @param filename Name of the file where the call took place. @param lineno Line number in the file. @return 1 newly allocated character string. This function calls in turn calloc to perform the allocation. It should never be called directly but only through a macro, like: @code xmemory_strdup(s, __FILE__, __LINE__) @endcode This function calls xmemory_malloc() to do the allocation. */ /*--------------------------------------------------------------------------*/ char * __NOTRACE__ xmemory_strdup(char * s, char * filename, int lineno) { char * t ; if (s==NULL) return NULL ; if (xmemory_table.active==0) return strdup(s); t = xmemory_malloc(1+strlen(s), filename, lineno); return strcpy(t, s); } /*-------------------------------------------------------------------------*/ /** @brief Display memory status information. @return void This function is meant for debugging purposes, but it is recommended to call it at the end of every executable making use of the extended memory features. This function should be called through the xmemory_status() macro, which provides automatically the name of the source file and line number where the call happens. */ /*--------------------------------------------------------------------------*/ void __NOTRACE__ xmemory_status_(char * filename, int lineno) { int i ; if (xmemory_table.n<1) return ; fprintf(stderr, "#----- memory status called from %s (%d) --------\n", filename, lineno); fprintf(stderr, "#- ALL status\n" "ALL_npointers %d\n" "ALL_size %ld\n" "ALL_maxalloc_kb %ld\n", xmemory_table.n, (long)xmemory_table.alloc_total, (long)(xmemory_table.alloc_max/1024)); if (xmemory_table.alloc_ram > 0) { fprintf(stderr, "#- RAM status\n" "RAM_alloc %ld\n", (long)xmemory_table.alloc_ram); } if (xmemory_table.alloc_swap > 0) { fprintf(stderr, "#- SWP status\n" "SWP_alloc %ld\n" "SWP_files %d\n", (long)xmemory_table.alloc_swap, xmemory_table.nswapfiles); } if (xmemory_table.n_mm_files>0) { fprintf(stderr, "#- MAP status\n" "MAP_files %d\n" "MAP_mappings %d\n", xmemory_table.n_mm_files, xmemory_table.n_mm_mappings); } fprintf(stderr, "#- pointer details\n"); for (i=0 ; i