/*--------------------------------------------------------------------------- * The Memory Allocator wrapper. * *--------------------------------------------------------------------------- * This file #includes the source for the memory allocator selected in * in config.h. If sysmalloc is selected, this file provides the basic * xalloc()... implementation using malloc()... . * * It is the task of the memory allocator source included to provide * simulations of malloc()... where required. *--------------------------------------------------------------------------- */ #include "driver.h" #include #include #include #include "xalloc.h" #include "backend.h" #include "gcollect.h" #include "interpret.h" #include "simulate.h" #include "exec.h" #include "object.h" #include "mstrings.h" /*-------------------------------------------------------------------------*/ /* Minimum boundary between stack and heap, should be sufficient for * error handling (which needs about 10..15KByte). * If the gap falls below this value, an error is generated and the * gap is no longer checked except for true overlap with the heap. */ #define HEAP_STACK_GAP (10 * ERROR_FMT_LEN) /* A counter type for statistics and its functions. */ typedef struct { unsigned long counter, size; } t_stat; /* A counter type for statistics and its functions: */ static inline void count_add(t_stat *a, unsigned long b) { a->size += b; } static inline void count(t_stat *a, unsigned long b) { count_add(a, b); if (b < 0) --a->counter; else ++a->counter; } static inline void count_up(t_stat *a, unsigned long b) { count_add(a, b); ++a->counter; } static inline void count_up_n(t_stat *a, unsigned long b, unsigned long c) { count_add(a, b * c); a->counter += b; } static inline void count_back(t_stat *a, unsigned long b) { count_add(a, -b); --a->counter; } static inline void count_back_n(t_stat *a, unsigned long b, unsigned long c) { count_add(a, -(b * c)); a->counter -= b; } typedef p_uint word_t; /* Our 'word' type. This should not be changed unless you check the allocators * first for assumptions that this is a p_uint. */ /*-------------------------------------------------------------------------*/ /* The extra xalloc header fields. * GC's write_malloc_trace() expects XM_FILE and XM_LINE at the end * of the header. * TODO: Let the GC use the symbolic constants. */ enum xalloc_header_fields { #ifdef MALLOC_LPC_TRACE XM_OBJ = 0, /* (object_t*) the allocating object */ XM_PROG = 1, /* (int32) allocating program's id-number */ XM_PC = 2, /* (bytecode_p) inter_pc at the allocation */ # ifdef MALLOC_TRACE XM_FILE = 3, /* (const char*) allocating source file */ XM_LINE = 4, /* (word_t) allocating line in source file */ XM_OVERHEAD = 5, # else XM_OVERHEAD = 3, # endif /* MALLOC_TRACE */ #else /* !MALLOC_LPC_TRACE */ # ifdef MALLOC_TRACE XM_FILE = 0, /* (const char*) allocating source file */ XM_LINE = 1, /* (word_t) allocating line in source file */ XM_OVERHEAD = 2, # else XM_OVERHEAD = 0, # endif /* MALLOC_TRACE */ #endif /* MALLOC_LPC_TRACE */ XM_OVERHEAD_SIZE = XM_OVERHEAD * sizeof(word_t), }; /*-------------------------------------------------------------------------*/ /* -- Global Variables/Arguments dealing with memory -- */ Bool out_of_memory = MY_FALSE; /* True: we are out of memory */ int malloc_privilege = MALLOC_USER; /* Privilege for next allocation */ char *reserved_user_area = NULL; /* Reserved memory areas */ char *reserved_master_area = NULL; char *reserved_system_area = NULL; mp_int reserved_user_size = RESERVED_USER_SIZE; /* The reserved sizes */ mp_int reserved_master_size = RESERVED_MASTER_SIZE; mp_int reserved_system_size = RESERVED_SYSTEM_SIZE; /* at startup reserve these amounts of memory for large and small blocks */ mp_int min_malloced = MIN_MALLOCED; mp_int min_small_malloced = MIN_SMALL_MALLOCED; /* this is the hard limit for memory allocations. */ static mp_int max_malloced = HARD_MALLOC_LIMIT_DEFAULT; /* this is a soft limit for memory allocations. It serves as a kind of low * watermark. If exceeded, the game driver will inform the mudlib by calling * low_memory() in the master. */ static mp_int soft_malloc_limit = SOFT_MALLOC_LIMIT_DEFAULT; /* Was the low_memory() already called in the master`*/ static Bool low_memory_applied = MY_FALSE; int stack_direction = 0; /* 0: Unknown stack behaviour * +1: Stack grows upward * -1: Stack grows downward */ static char * initial_stack = NULL; /* The stack at the start of the program */ static int in_malloc = 0; /* >0 when the core code in the allocator is executed. * This variable serves as primitive safeguard against re-entrant * calls to the allocator, for example by threads. */ static int going_to_exit = MY_FALSE; /* When the allocator detected a fatal error, it sets this * variable before starting the fatal() handling in order to * prevent recursions. */ #ifdef MALLOC_SBRK_TRACE static size_t mdb_size; static object_t *mdb_object; # if defined(MALLOC_TRACE) static const char * mdb_file; static int mdb_line; # endif /* Persistent copy of the latest memory request information, * so that the SBRK_TRACE can print it. */ #endif /* MALLOC_SBRK_TRACE */ /* --- Statistics --- */ static t_stat xalloc_stat = {0,0}; /* Total number and size of allocations done by the driver (incl overhead). */ #if defined(MALLOC_SBRK) && (defined(SBRK_OK) || defined(HAVE_MMAP)) static t_stat clib_alloc_stat = {0,0}; /* Number and size of allocations done through the clib emulation * functions (incl overhead). */ #endif /*-------------------------------------------------------------------------*/ /* Forward declarations */ #ifdef MALLOC_LPC_TRACE static void write_lpc_trace (int d, word_t *p, int oneline) __attribute__((nonnull(2))); #endif #ifdef GC_SUPPORT static void print_block (int d, word_t *block); #endif /* GC_SUPPORT */ #ifdef MALLOC_SBRK_TRACE /*-------------------------------------------------------------------------*/ static void mem_debug_log (const char * name, p_int size) /* Mem debug log function. Log the given for function * to stdout together with the original allocation request size. */ { #if defined(MALLOC_TRACE) dprintf4(1, "%s %s(%d) for %d" , (p_int)current_time_stamp, (p_int)name , (p_int)size, (p_int)mdb_size ); dprintf3(1, " , '%s':%d , obj %s\n" , (p_int)mdb_file, (p_int)mdb_line , (p_int)(mdb_object ? ( mdb_object->name ? get_txt(mdb_object->name) : ""): "") ); #else dprintf4(1, "%s %s(%d) for %d" , (p_int)current_time_stamp, (p_int)name , (p_int)size, (p_int)mdb_size ); dprintf1(1, " , obj %s\n" , (p_int)(mdb_object ? ( mdb_object->name ? get_txt(mdb_object->name) : ""): "") ); #endif /* MALLOC_TRACE */ } /* mem_debug_log() */ /*-------------------------------------------------------------------------*/ static void mdb_log_sbrk (p_int size) /* esbrk() log function: esbrk() is called to allocate bytes * from the system. Log this information to stdout together with * the original allocation request size. */ { mem_debug_log("esbrk", size); } /* mdb_log_sbrk() */ #else #define mdb_log_sbrk(size) NOOP #endif /* MALLOC_SBRK_TRACE */ /*-------------------------------------------------------------------------*/ /* Include the allocator source. * * The allocator is free to use any information found in xalloc. * In return, the allocator has to provide this interface: * * static POINTER mem_alloc(size_t size) * to allocate a memory block * * static void mem_free(POINTER p) * to deallocate a memory block * * static POINTER mem_realloc (POINTER p, size_t size) * reallocate a block. * * static void * mem_increment_size (void *vp, size_t size) * Increase a block size in place. If not possible, return NULL. * * static static size_t mem_block_size (POINTER p) * Return the size of an allocated block. * If this value is not available, implement this function as a dummy, * but also #define NO_MEM_BLOCK_SIZE. * For Garbage Collection or replacing malloc() this function must be * valid! * * static static size_t mem_overhead () * Return the size of the allocators internal overhead. * * static void mem_mark_permanent (POINTER p) * static void mem_mark_collectable (POINTER p) * Mark a block as permanent resp. collectable * * void mem_consolidate (bool force) * Do whatever consolidation is useful. * is true after a GC, and false when called from the backend. * * void mem_dump_data (strbuf_t *sbuf) * void mem_dump_extdata (strbuf_t *sbuf) * void mem_dinfo_data (svalue_t *svp, int value) * Return the statistics data. * * Bool mem_dump_memory (int fd) * Dump the memory layout to , or return FALSE. * If is -1, just return TRUE or FALSE (this is used to check if * the allocator supports memory dumps). * #ifdef MALLOC_EXT_STATISTICS * void mem_update_stats (void) * Update whatever extended statistics are available. * Called every backend cycle or so to allow for the calculation of * averages over time. * #endif #ifdef GC_SUPPORT * static void mem_clear_ref (POINTER p) * static void mem_mark_ref (POINTER p) * static Bool mem_test_ref (POINTER p) * Clear, set, test the 'referenced' marker. * * void mem_clear_ref_flags() * Clear all 'referenced' markers. * * void mem_free_unrefed_memory() * Free all memory marked as 'unreferenced'. * This routine also has to accordingly adjust xalloc_stat. * #ifdef MALLOC_TRACE * static Bool mem_is_freed (POINTER p, size_t minsize) * Return true if

is a free block. #endif #endif * * #define REPLACE_MALLOC * if the allocator's mem_alloc()/mem_free() can be used to replace the * libc allocation routines (ie. the allocator doesn't use malloc() * itself). The actual replacement is provided by xalloc. * #define MEM_MAIN_THREADSAFE * The allocator is safe to use from the main thread. * #define MEM_THREADSAFE * The allocator is altogether threadsafe. */ #if defined(MALLOC_smalloc) # include "smalloc.c" #elif defined(MALLOC_slaballoc) # include "slaballoc.c" #elif defined(MALLOC_sysmalloc) # include "sysmalloc.c" #elif defined(MALLOC_ptmalloc) # include "xptmalloc.c" #else # error "No allocator specified." #endif #ifdef NO_MEM_BLOCK_SIZE # if defined(GC_SUPPORT) || defined(REPLACE_MALLOC) # error "For GC_SUPPORT or REPLACE_MALLOC, mem_block_size() must exist!" # endif #endif #if defined(USE_SQLITE) && defined(SQLITE3_USES_PTHREADS) && !defined(MEM_THREADSAFE) && !defined(MEM_MAIN_THREADSAFE) # warning "" # warning "-----------------------------------" # warning "SQLite3 uses PThreads, but the allocator" # warning "is not threadsafe!" # warning "-----------------------------------" # warning "" #endif #if defined(MALLOC_ptmalloc) && defined(GC_SUPPORT) && defined(__FreeBSD__) # warning "" # warning "-----------------------------------" # warning "PTMalloc selected, but the allocator" # warning "doesn't support GC under FreeBSD!" # warning "-----------------------------------" # warning "" #endif /*-------------------------------------------------------------------------*/ size_t xalloced_size (POINTER p #ifdef NO_MEM_BLOCK_SIZE UNUSED #endif /* NO_MEM_BLOCK_SIZE */ ) /* Return the allocation size (incl. overhead) of the block

. */ { #ifndef NO_MEM_BLOCK_SIZE return mem_block_size((word_t*)p - XM_OVERHEAD); #else # ifdef __MWERKS__ # pragma unused(p) # endif return 0; #endif } /* xalloced_size() */ /*-------------------------------------------------------------------------*/ size_t xalloc_overhead (void) /* Return the total overhead of an allocation - xalloc and allocator. */ { return mem_overhead() + XM_OVERHEAD_SIZE; } /* xalloc_overhead() */ /*-------------------------------------------------------------------------*/ static Bool retry_alloc (size_t size MTRACE_DECL) /* An allocation for bytes just failed - try to free the reserves. * Return TRUE if the allocation can be retried, FALSE is not. * If allocation privilege is SYSTEM and the allocation can't be retried, * abort the driver. */ { /* Out of memory - try to recover */ static char mess1[] = "Temporarily out of MEMORY. Freeing user reserve.\n"; static char mess2[] = "Temporarily out of MEMORY. Freeing master reserve.\n"; static char mess3[] = "Temporarily out of MEMORY. Freeing system reserve.\n"; static char mess4[] = "Totally out of MEMORY.\n"; static char mess_d1[] = "Low on MEMORY: Trying to allocate "; static char mess_d2[] = " bytes for "; static char mess_d3[] = " bytes request"; static char mess_d4[] = " ("; static char mess_d7[] = ", prog "; static char mess_d6[] = ")"; #if defined(MALLOC_TRACE) static char mess_d5[] = " line "; #endif static char mess_nl[] = "\n"; /* Print the Out-Of-Mem diagnostic */ writes(2, mess_d1); writed(2, size); writes(2, mess_d2); writed(2, size - XM_OVERHEAD_SIZE); writes(2, mess_d3); #ifdef MALLOC_TRACE writes(2, mess_d4); writes(2, malloc_trace_file); writes(2, mess_d5); writed(2, malloc_trace_line); writes(2, mess_d6); #endif writes(2, mess_d4); writes(2, current_object ? get_txt(current_object->name) : ""); writes(2, mess_d7); writes(2, current_prog ? get_txt(current_prog->name) : ""); writes(2, mess_d6); writes(2, mess_nl); /* Free the next reserve, the try again */ if (gc_request == gcDont) gc_request = gcMalloc; extra_jobs_to_do = MY_TRUE; if (reserved_user_area) { xfree(reserved_user_area); reserved_user_area = NULL; writes(2, mess1); return MY_TRUE; } if (malloc_privilege >= MALLOC_MASTER && reserved_master_area) { xfree(reserved_master_area); reserved_master_area = NULL; writes(2, mess2); return MY_TRUE; } if (malloc_privilege >= MALLOC_SYSTEM && reserved_system_area) { xfree(reserved_system_area); reserved_system_area = 0; writes(2, mess3); return MY_TRUE; } if (malloc_privilege < MALLOC_SYSTEM) { out_of_memory = MY_TRUE; return MY_FALSE; } /* Totally out of memory: exit */ max_malloced = 0; /* Disable the checking for a clean exit */ going_to_exit = MY_TRUE; /* Prevent recursions */ writes(2, mess4); (void)dump_trace(MY_FALSE, NULL); fatal("Out of memory (%lu bytes)\n", (unsigned long)size); /* NOTREACHED */ return MY_FALSE; } /* retry_alloc() */ /*-------------------------------------------------------------------------*/ static INLINE Bool check_max_malloced (void) /* If max_malloced is set, check if the allocated memory exceeds it. * If not, return FALSE. * If yes, and malloc_privilege < MALLOC_SYSTEM: return TRUE. * If yes, and malloc_privilege == MALLOC_SYSTEM: abort. */ { #ifndef NO_MEM_BLOCK_SIZE if (max_malloced > 0 && (mp_int)xalloc_stat.size > max_malloced) { static const char mess[] = "HARD_MALLOC_LIMIT reached.\n"; writes(2, mess); if (malloc_privilege < MALLOC_SYSTEM) return MY_TRUE; /* Totally out of memory: exit */ max_malloced = 0; /* Disable the checking for a clean exit */ going_to_exit = MY_TRUE; /* Prevent recursions */ (void)dump_trace(MY_FALSE, NULL); fatal("Out of memory.\n"); /* NOTREACHED */ } #endif return MY_FALSE; } /* check_max_malloced() */ /*-------------------------------------------------------------------------*/ POINTER xalloc_traced (size_t size MTRACE_DECL) /* Allocate bytes of memory like malloc() does. * This function catches out of memory situations and tries to recover * from them by using the reserved memory areas. If it totally runs * out of memory, the program exit()s with code 3. */ { word_t *p; if (going_to_exit) /* A recursive call while we're exiting */ exit(3); #ifdef MALLOC_SBRK_TRACE mdb_size = size; mdb_object = current_object; #ifdef MALLOC_TRACE mdb_file = malloc_trace_file; mdb_line = malloc_trace_line; #endif #endif /* MALLOC_SBRK_TRACE */ size += XM_OVERHEAD_SIZE; do { p = mem_alloc(size); } while (p == NULL && retry_alloc(size MTRACE_PASS)); if (p == NULL) { return NULL; } #ifdef MALLOC_TRACE p[XM_FILE] = (word_t)malloc_trace_file; p[XM_LINE] = (word_t)malloc_trace_line; #endif #ifdef MALLOC_LPC_TRACE p[XM_OBJ] = (word_t)current_object; p[XM_PROG] = current_prog ? current_prog->id_number : 0; p[XM_PC] = (word_t)inter_pc; #endif #ifdef NO_MEM_BLOCK_SIZE count_up(&xalloc_stat, XM_OVERHEAD_SIZE); #else count_up(&xalloc_stat, mem_block_size(p)); if (check_max_malloced()) return NULL; #endif return (POINTER)(p + XM_OVERHEAD); } /* xalloc_traced() */ /*-------------------------------------------------------------------------*/ void xfree (POINTER p) /* Free the memory block

. */ { if (NULL != p) { word_t *q = (word_t*)p - XM_OVERHEAD; #ifdef NO_MEM_BLOCK_SIZE count_back(&xalloc_stat, XM_OVERHEAD_SIZE); #else count_back(&xalloc_stat, mem_block_size(q)); #endif mem_free(q); } } /* xfree() */ /*-------------------------------------------------------------------------*/ POINTER pxalloc_traced (size_t size MTRACE_DECL) /* Allocate a block of bytes - like xalloc(), just that the * memory is not subject to GC. */ { POINTER temp; temp = xalloc_traced(size MTRACE_PASS); if (temp) { mem_mark_permanent((word_t *)temp - XM_OVERHEAD); } return temp; } /* pxalloc_traced() */ /*-------------------------------------------------------------------------*/ void pfree (POINTER p) /* Deallocate a permanent block

. */ { if (p) { mem_mark_collectable((word_t *)p - XM_OVERHEAD); } xfree(p); } /* pfree() */ /*-------------------------------------------------------------------------*/ POINTER prexalloc_traced (POINTER p, size_t size MTRACE_DECL) /* Reallocate block

to the new size of and return the pointer. * The memory is not subject to GC. #ifdef MALLOC_TRACE * The trace arguments are admittedly unused in the function, but come * in handy if the allocation code needs to be instrumented for debugging * purposes. #endif */ { POINTER temp; if (p) { mem_mark_collectable((word_t *)p - XM_OVERHEAD); } temp = rexalloc_traced(p, size MTRACE_PASS); if (temp) { mem_mark_permanent((word_t *)temp - XM_OVERHEAD); } return temp; } /* prexalloc_traced() */ /*-------------------------------------------------------------------------*/ void * malloc_increment_size (void *vp, size_t size) /* Try to extent the allocation block for in place to hold more * bytes. If this is not possible, return NULL, otherwise return a pointer * to the start of the block extension. */ { word_t * block = (word_t*)vp - XM_OVERHEAD; #ifndef NO_MEM_BLOCK_SIZE size_t old_size; #endif void * rc; if (going_to_exit) /* A recursive call while we're exiting */ exit(3); #ifndef NO_MEM_BLOCK_SIZE old_size = mem_block_size(block); #endif rc = mem_increment_size(block, size); #ifndef NO_MEM_BLOCK_SIZE if (rc != NULL) { count_back(&xalloc_stat, old_size); count_up(&xalloc_stat, mem_block_size(block)); if (check_max_malloced()) return NULL; } #endif return rc; } /* malloc_increment_size() */ /*-------------------------------------------------------------------------*/ POINTER rexalloc_traced (POINTER p, size_t size MTRACE_DECL #ifndef MALLOC_SBRK_TRACE UNUSED #endif /* MALLOC_SBRK_TRACE */ ) /* Reallocate block

to the new size of and return the pointer. * The memory is not aligned and subject to GC. #ifdef MALLOC_TRACE * The trace arguments are admittedly unused in the function, but come * in handy if the allocation code needs to be instrumented for debugging * purposes. #endif */ { #ifndef MALLOC_SBRK_TRACE #ifdef MALLOC_TRACE # ifdef __MWERKS__ # pragma unused(malloc_trace_file) # pragma unused(malloc_trace_line) # endif #endif /* MALLOC_TRACE */ #endif /* MALLOC_SBRK_TRACE */ word_t *block, *t; #ifndef NO_MEM_BLOCK_SIZE size_t old_size; #endif if (going_to_exit) /* A recursive call while we're exiting */ exit(3); if (!p) { return xalloc_traced(size MTRACE_ARG); } #ifdef MALLOC_SBRK_TRACE mdb_size = size; mdb_object = current_object; #ifdef MALLOC_TRACE mdb_file = malloc_trace_file; mdb_line = malloc_trace_line; #endif #endif /* MALLOC_SBRK_TRACE */ size += XM_OVERHEAD_SIZE; block = (word_t *)p - XM_OVERHEAD; #ifndef NO_MEM_BLOCK_SIZE old_size = mem_block_size(block); t = malloc_increment_size(p, size - old_size); if (t) return p; #endif do { t = mem_realloc(block, size); } while (t == NULL && retry_alloc(size MTRACE_ARG)); if (t) { #ifndef NO_MEM_BLOCK_SIZE count_back(&xalloc_stat, old_size); count_up(&xalloc_stat, mem_block_size(t)); if (check_max_malloced()) return NULL; #endif t += XM_OVERHEAD; } return (POINTER)t; } /* rexalloc() */ /*=========================================================================*/ /* GARBAGE COLLECTOR */ #ifdef GC_SUPPORT /*-------------------------------------------------------------------------*/ void x_clear_ref (POINTER p) /* GC Support: Clear the 'referenced' flag for block

. */ { mem_clear_ref((word_t *)p - XM_OVERHEAD); } /* x_clear_ref() */ /*-------------------------------------------------------------------------*/ int x_mark_ref (POINTER p) /* GC Support: Set the 'referenced' flag for block

. * This function returns a value (1) so that it can be used in macros * more easily. */ { mem_mark_ref((word_t *)p - XM_OVERHEAD); return 1; } /* x_mark_ref() */ /*-------------------------------------------------------------------------*/ Bool x_test_ref (POINTER p) /* GC Support: Check the memory block marker for

, return TRUE if _not_ * set. */ { return mem_test_ref((word_t *)p - XM_OVERHEAD); } /* x_test_ref() */ /*-------------------------------------------------------------------------*/ #ifdef MALLOC_TRACE static int num_dispatched_types = 0; /* Used size of the dispatch_table */ static struct { char *file; word_t line; void (*func)(int, void *, int); } dispatch_table[12]; /* The dispatch table used to recognize and print datablocks. * The recognition is simple and uses the file/line information received * from sample allocations done by gcollect. If an allocation matches * one entry in the table, the function is called with (filedescriptor, * blockaddress, 0). */ #ifdef CHECK_OBJECT_GC_REF /*-------------------------------------------------------------------------*/ /* Some extra variables to explicitely store the location of program * and object allocations. */ static char * object_file; static word_t object_line; static char * program_file; static word_t program_line; void note_object_allocation_info ( void *block ) { object_file = (char *)((word_t*)block)[XM_FILE-XM_OVERHEAD]; object_line = ((word_t*)block)[XM_LINE-XM_OVERHEAD]; } void note_program_allocation_info ( void *block ) { program_file = (char *)((word_t*)block)[XM_FILE-XM_OVERHEAD]; program_line = ((word_t*)block)[XM_LINE-XM_OVERHEAD]; } Bool is_object_allocation ( void *block ) { return (object_file == (char *)((word_t*)block)[XM_FILE-XM_OVERHEAD]) && (object_line == ((word_t*)block)[XM_LINE-XM_OVERHEAD]); } Bool is_program_allocation ( void *block ) { return (program_file == (char *)((word_t*)block)[XM_FILE-XM_OVERHEAD]) && (program_line == ((word_t*)block)[XM_LINE-XM_OVERHEAD]); } #endif /*-------------------------------------------------------------------------*/ void store_print_block_dispatch_info (void *block , void (*func)(int, void *, int) ) /* Add a new block type: get the file/line information from the * allocation and store it with the tion. */ { int i; i = num_dispatched_types++; if (i >= (int)(sizeof(dispatch_table)/sizeof(dispatch_table[0]))) { writes(2, "dispatch_table for print_block() to small\n"); return; } dispatch_table[i].file = (char *)((word_t*)block)[XM_FILE-XM_OVERHEAD]; dispatch_table[i].line = ((word_t*)block)[XM_LINE-XM_OVERHEAD]; dispatch_table[i].func = func; } /* store_print_block_dispatch_info() */ /*-------------------------------------------------------------------------*/ Bool is_freed (void *p, p_uint minsize) /* Check if block for the allocation

is a free block of at least * . Blocks outside the heap always qualify. * The function might return false for joined blocks. */ { word_t *block; block = (word_t *) p - XM_OVERHEAD; return mem_is_freed(block, minsize + XM_OVERHEAD_SIZE); } /* is_freed() */ #endif /* MALLOC_TRACE */ /*-------------------------------------------------------------------------*/ static void print_block (int d, word_t *block) /* Block was recognized as lost - print it onto file . * If possible, use the information in the dispatch_table, otherwise * print the first characters as they are. */ { word_t size; int i; #ifdef MALLOC_TRACE char *file = (char *)block[XM_FILE]; word_t line = block[XM_LINE]; for (i = num_dispatched_types; --i >= 0; ) { if (dispatch_table[i].file == file && dispatch_table[i].line == line) { (*dispatch_table[i].func)(d, (char *)(block+XM_OVERHEAD), 0); write(d, "\n", 1); return; } } #endif /* Print a hexdump, but not more than 80 bytes */ { int limit = 80; char * cp; size = mem_block_size(block) - XM_OVERHEAD; cp = (char *)(block + XM_OVERHEAD); while (size > 0 && limit > 0) { /* Start of line: print the address */ dprintf1(d, "%x:", (p_int)cp); /* Print the up to 16 bytes after cp as hex values */ for (i = 0; i < 16 && i < (int)size && i < limit; i++) dprintf1(d, " %X", cp[i]); /* Align foward to the character interpretation */ for (; i < 16; i++) writes(d, " "); writes(d, " "); /* Print the same data as characters */ for (i = 0; i < 16 && i < (int)size && i < limit; i++) { if (isprint((unsigned char)cp[i])) write(d, cp+i, 1); else writes(d, "."); } writes(d, "\n"); cp += i; size -= i; limit -= i; } } writes(d, "\n"); } /* print_block() */ #endif /* GC_SUPPORT */ /*-------------------------------------------------------------------------*/ #ifdef MALLOC_LPC_TRACE static void write_lpc_trace (int d, word_t *p, int oneline) /* Write the object and program which allocated the memory block

* onto file . * if is true, all information is printed in one line. */ { object_t *obj, *o; bytecode_p pc; program_t *prog; int line; int32 id; /* Try to find the object which allocated this block */ if (!oneline) { if ( NULL != (obj = (object_t *)p[XM_OBJ]) ) { writes(d, " By object: "); if (obj->flags & O_DESTRUCTED) writes(d, "(destructed) "); for (o = obj_list; o && o != obj; o = o->next_all) NOOP; if (!o) writes(d, "(not in list) "); else if (o->name) writes(d, get_txt(o->name)); /* TODO: If this cores, it has to go again */ } else writes(d, " No object."); writes(d, "\n"); } else { if ( NULL != (obj = (object_t *)p[XM_OBJ]) ) { for (o = obj_list; o && o != obj; o = o->next_all) NOOP; if (!o || !o->name) writes(d, "?"); else writes(d, get_txt(o->name)); /* TODO: If this cores, it has to go again */ if (obj->flags & O_DESTRUCTED) writes(d, " (destructed)"); } else writes(d, "-"); } /* Try to find the program which allocated this block */ if ( 0 != (id = p[XM_PROG]) ) { pc = NULL; prog = NULL; for ( o = obj_list ; o && !(o->flags & O_DESTRUCTED) && ((p_int)o->prog&1 || o->prog->id_number != id); ) o = o->next_all; /* Unlikely, but possible: ids might have been renumbered. */ if (o) { pc = (bytecode_p)p[XM_PC]; prog = o->prog; if (prog->program > pc || pc > PROGRAM_END(*prog)) o = NULL; } if (o) { string_t *file; line = get_line_number(pc, prog, &file); if (!oneline) { dprintf2(d, " By program: %s line:%d\n", (p_int)get_txt(file), line); } else { dprintf2(d, " , %s %d", (p_int)get_txt(file), line); } if (file) free_mstring(file); } else { if (!oneline) writes(d, " By program: Not found at old address.\n"); } } if (oneline) writes(d, "\n"); } /* write_lpc_trace() */ #endif /* MALLOC_LPC_TRACE */ /*-------------------------------------------------------------------------*/ void dump_lpc_trace (int d , void *p #ifndef MALLOC_LPC_TRACE UNUSED #endif ) /* Write the object and program which allocated the memory block

* onto file . * In contrast to write_lpc_trace(), the address of the memory block is * the one returned by xalloc(), ie. pointing after the memory block * header. */ { #if defined(MALLOC_LPC_TRACE) write_lpc_trace(d, ((word_t *)p) - XM_OVERHEAD, MY_FALSE); #else # ifdef __MWERKS__ # pragma unused(p) # endif writes(d, "No malloc lpc trace.\n"); #endif /* MALLOC_LPC_TRACE */ } /* dump_lpc_trace() */ /*-------------------------------------------------------------------------*/ void dump_malloc_trace (int d , void *adr #if !defined(MALLOC_TRACE) && !defined(MALLOC_LPC_TRACE) UNUSED #endif ) /* Write the allocation information (file, linenumber, object and such) of * the memory block onto file . * is the address returned by xalloc(), ie. pointing after the memory * block header. */ { #if !defined(MALLOC_TRACE) && !defined(MALLOC_LPC_TRACE) # ifdef __MWERKS__ # pragma unused(adr) # endif writes(d, "No malloc trace.\n"); #else word_t *p = ((word_t *)adr) - XM_OVERHEAD; # ifdef MALLOC_TRACE word_t size = mem_block_size(p); dprintf3(d, " %s %d size 0x%x\n", p[XM_FILE], p[XM_LINE], size ); # endif # ifdef MALLOC_LPC_TRACE write_lpc_trace(d, p, MY_FALSE); # endif #endif } /* dump_malloc_trace() */ /*=========================================================================*/ /* CLIB ALLOCATION FUNCTIONS */ #if defined(REPLACE_MALLOC) && (defined(SBRK_OK) || defined(HAVE_MMAP)) /*-------------------------------------------------------------------------*/ POINTER malloc (size_t size) /* Allocate an empty memory block of size . * The memory is aligned and not subject to GC. */ { POINTER result = pxalloc(size); if (!result) { int save_privilege = malloc_privilege; malloc_privilege = MALLOC_SYSTEM; result = pxalloc(size); malloc_privilege = save_privilege; } if (result) { count_up(&clib_alloc_stat, xalloced_size(result) + mem_overhead()); } return result; } /* malloc() */ /*-------------------------------------------------------------------------*/ FREE_RETURN_TYPE free (POINTER ptr) /* Free the memory block which was allocated with malloc(). */ { if (ptr) { count_back(&clib_alloc_stat, xalloced_size(ptr) + mem_overhead()); } pfree(ptr); FREE_RETURN } /* free() */ /*-------------------------------------------------------------------------*/ POINTER calloc (size_t nelem, size_t sizel) /* Allocate an empty memory block for objects of size . * The memory is aligned and not subject to GC. */ { char *p; if (nelem == 0 || sizel == 0) return NULL; p = malloc(nelem * sizel); if (p == NULL) return NULL; memset(p, '\0', nelem * sizel); return p; } /* calloc() */ /*-------------------------------------------------------------------------*/ POINTER realloc (POINTER p, size_t size) /* Reallocate block

to the new size of and return the pointer. * The memory is aligned and not subject to GC. */ { size_t old_size; POINTER t; if (!p) return malloc(size); old_size = xalloced_size(p) - XM_OVERHEAD_SIZE; // usable size if (old_size >= size) return p; t = malloc(size); if (t == NULL) return NULL; memcpy(t, p, old_size); free(p); return t; } /* realloc() */ #endif /* REPLACE_MALLOC */ /* ======================================================================= */ /*-------------------------------------------------------------------------*/ void get_stack_direction (void) /* Find the direction of the stackgrowth and store the result (+1 or -1) * into the global stack_direction. */ { char local; /* to get stack address */ if (initial_stack == NULL) /* initial call */ { initial_stack = &local; get_stack_direction (); /* recurse once */ } else /* recursive call */ if (&local > initial_stack) stack_direction = 1; /* stack grew upward */ else stack_direction = -1; /* stack grew downward */ } /* get_stack_direction() */ /*-------------------------------------------------------------------------*/ void assert_stack_gap (void) /* Test if the stack is far enough away from the heap area and throw * an error if not. */ { static enum { Initial, Normal, Error, Fatal } condition = Initial; char * stack_start, * stack_end; ptrdiff_t gap; char local; /* used to yield a stack address */ /* Don't check the gap after a Fatal error or if the system is * not fully initialised yet. */ if (stack_direction == 0 || condition == Fatal || heap_end == NULL) return; /* On the first call, test if checking the gap actually makes sense. * If the stack-gap check is not necessary, the 'condition' will be set to * Fatal, otherwise the check will be enabled by setting condition to * 'Normal'. */ if (condition == Initial) { /* Currently there are no limitations on checking the heap/stack gap. */ condition = Normal; } /* Determine the stack limits */ if (stack_direction < 0) { stack_start = &local; stack_end = initial_stack; } else { stack_start = initial_stack; stack_end = &local; } /* Check if the heap and stack overlap */ if ((stack_end > (char *)heap_end && stack_start < (char *)heap_end) || (stack_end > (char *)heap_start && stack_start < (char *)heap_start) ) { if (condition != Fatal) { condition = Fatal; fatal("Out of memory: Stack (%p..%p) overlaps heap (%p..%p).\n" , stack_start, stack_end, heap_start, heap_end); /* NOTREACHED */ } return; /* else: Recursive call during fatal() handling */ } /* Check if the stack grows towards the heap. If it doesn't * we don't have to worry about the gap. */ if ((stack_direction > 0) ? (stack_start > (char *)heap_end) : (stack_end < (char *)heap_start) ) { /* No worries about the gap */ condition = Normal; return; } /* Heap and stack may overlap - do the normal gap checking. * Note that on machines with big address spaces the computation * may overflow. */ if (stack_direction < 0) gap = (char *)stack_start - (char *)heap_end; else gap = (char *)heap_start - stack_end; if (gap < 0) gap = HEAP_STACK_GAP + 1; /* If the gap is big enough, mark that condition and return */ if (gap >= HEAP_STACK_GAP) { condition = Normal; return; } /* The gap is too small. * Throw an error only if the condition was normal before, * otherwise the error handling would again get an error. */ if (condition == Normal) { condition = Error; errorf("Out of memory: Gap between stack and heap: %ld.\n" , (long)gap); /* NOTREACHED */ } } /* assert_stack_gap() */ /*-------------------------------------------------------------------------*/ void reserve_memory (void) /* Reserve the memory blocks according to reserved_xxx_area, and the * min_{small}_malloced limits. */ { void * ptr; /* First, check if max_malloced is a sensible value. * We overestimate the requirement a bit... */ if (max_malloced > 0) { mp_int required_mem = 0; mp_int required_reserve = 0; if (reserved_user_size > 0) required_reserve += reserved_user_size; if (reserved_master_size > 0) required_reserve += reserved_master_size; if (reserved_system_size > 0) required_reserve += reserved_system_size; if (min_malloced > 0) { if (min_malloced > required_reserve) required_mem += min_malloced; else required_mem += required_reserve + min_malloced; } else required_mem += required_reserve; if (min_small_malloced > 0) required_mem += min_small_malloced; if (max_malloced < required_mem) { printf("%s max_malloced is %ld bytes, " "but driver requires %ld bytes.\n" , time_stamp(), (long)max_malloced, (long)required_mem ); debug_message("%s max_malloced is %ld bytes, " "but driver requires %ld bytes.\n" , time_stamp(), (long)max_malloced, (long)required_mem ); max_malloced = required_mem; } } if (min_malloced > 0) { ptr = xalloc(min_malloced); if (ptr) xfree(ptr); else { printf("%s Failed to allocate MIN_MALLOCED block of %ld bytes.\n" , time_stamp(), (long)min_malloced); debug_message("%s Failed to allocate MIN_MALLOCED block of %ld bytes.\n" , time_stamp(), (long)min_malloced); } } #ifdef MALLOC_smalloc if (min_small_malloced > 0) small_chunk_size = min_small_malloced; #endif /* MALLOC_smalloc */ if (reserved_system_size > 0) { reserved_system_area = xalloc((size_t)reserved_system_size); if (reserved_system_area == NULL) { printf("%s Failed to allocate system reserve of %ld bytes.\n" , time_stamp(), (long)reserved_system_area); debug_message("%s Failed to allocate system reserve of %ld bytes.\n" , time_stamp(), (long)reserved_system_area); } } if (reserved_master_size > 0) { reserved_master_area = xalloc((size_t)reserved_master_size); if (reserved_master_area == NULL) { printf("%s Failed to allocate master reserve of %ld bytes.\n" , time_stamp(), (long)reserved_master_area); debug_message("%s Failed to allocate master reserve of %ld bytes.\n" , time_stamp(), (long)reserved_master_area); } } if (reserved_user_size > 0) { reserved_user_area = xalloc((size_t)reserved_user_size); if (reserved_user_area == NULL) { printf("%s Failed to allocate user reserve of %ld bytes.\n" , time_stamp(), (long)reserved_user_area); debug_message("%s Failed to allocate user reserve of %ld bytes.\n" , time_stamp(), (long)reserved_user_area); } } } /* reserve_memory() */ /*-------------------------------------------------------------------------*/ void reallocate_reserved_areas (void) /* Try to reallocate the reserved memory areas. If this is possible, * a pending slow-shutdown is canceled and the out_of_memory flag is reset. */ { char *p; malloc_privilege = MALLOC_USER; if (reserved_system_size && !reserved_system_area) { if ( !(reserved_system_area = xalloc((size_t)reserved_system_size)) ) { slow_shut_down_to_do = 1; return; } else { p = "Reallocated System reserve.\n"; write(1, p, strlen(p)); } } if (reserved_master_size && !reserved_master_area) { if ( !(reserved_master_area = xalloc((size_t)reserved_master_size)) ) { slow_shut_down_to_do = 1; return; } else { p = "Reallocated Master reserve.\n"; write(1, p, strlen(p)); } } if (reserved_user_size && !reserved_user_area) { if ( !(reserved_user_area = xalloc((size_t)reserved_user_size)) ) { slow_shut_down_to_do = 6; return; } else { p = "Reallocated User reserve.\n"; write(1, p, strlen(p)); } } slow_shut_down_to_do = 0; out_of_memory = MY_FALSE; } /* reallocate_reserved_areas() */ /*-------------------------------------------------------------------------*/ char * string_copy_traced (const char *str MTRACE_DECL) /* string_copy() acts like strdup() with the additional bonus that it can * trace file/line of the calling place if MALLOC_TRACE is defined. */ { char *p; size_t len; len = strlen(str)+1; memsafe(p = xalloc_traced(len MTRACE_PASS), len, "string_copy"); if (p) { (void)memcpy(p, str, len); } return p; } /* string_copy_traced() */ /*-------------------------------------------------------------------------*/ void notify_lowmemory_condition(enum memory_limit_types what) /* Calls low_memory(what, , , ) * in the master. */ { short reservestate = 0; push_number(inter_sp, what); if (what == SOFT_MALLOC_LIMIT_EXCEEDED) push_number(inter_sp, soft_malloc_limit); else if (what == HARD_MALLOC_LIMIT_EXCEEDED) push_number(inter_sp, max_malloced); else push_number(inter_sp, 0); push_number(inter_sp, xalloc_stat.size); if (reserved_user_area) reservestate |= USER_RESERVE_AVAILABLE; if (reserved_master_area) reservestate |= MASTER_RESERVE_AVAILABLE; if (reserved_system_area) reservestate |= SYSTEM_RESERVE_AVAILABLE; push_number(inter_sp, reservestate); callback_master(STR_LOW_MEMORY, 4); } /* notify_lowmemory_condition */ /*-------------------------------------------------------------------------*/ void check_for_soft_malloc_limit (void) /* If soft_malloc_limit is set, check if the allocated memory exceeds it. * If yes and the master was not notified until now, low_memory() is called * in the mudlib master and low_memory_applied ist set to prevent any further * notifications. * If the limit is not exceeded, low_memory_applied is reset. * Should be called from the backend. */ { #ifndef NO_MEM_BLOCK_SIZE if (soft_malloc_limit > 0) { // check first, if the soft limit is > than the hard limit and issue // a debug message. (Gnomi wanted to check this here to be able to set // a soft limit before a hard limit without error.) if (soft_malloc_limit >= max_malloced) { debug_message("%s The soft memory limit (%"PRIdMPINT") is bigger " "than the hard memory limit (%"PRIdMPINT") - " "disabling the soft limit!", time_stamp(), soft_malloc_limit, max_malloced); soft_malloc_limit = 0; } else if ((mp_int)xalloc_stat.size > soft_malloc_limit) { if (!low_memory_applied) { /* call low_memory(malloced_memory) in the master but first * set the flag to prevent calling every backend cycle in case * of errors. */ low_memory_applied = MY_TRUE; notify_lowmemory_condition(SOFT_MALLOC_LIMIT_EXCEEDED); } } else if (low_memory_applied) { /* OK, memory consumption shrunk below the soft limit. Reset the * warning, so that the next time the limit is exceeded, * the master apply is done again. */ low_memory_applied = MY_FALSE; } } #endif } /* check_for_soft_malloc_limit() */ /*-------------------------------------------------------------------------*/ Bool set_memory_limit(enum memory_limit_types what, mp_int limit) /* Sets the memory limit to . * for SOFT_MLIMIT has to be < HARD_MLIMIT. * return TRUE on success and FALSE otherwise. * If the current memory allocation is smaller than a new soft limit, the flag * low_memory_applied is reset. */ { #ifndef NO_MEM_BLOCK_SIZE // limits smaller than the sum of reserves (and some more) are harmful and // lead anyway to immediate crashes... But we ignore this here, because // reserve_memory() will deal with the needed sizes for the reserves and the // minimum allocations. And later on in the game we will just prevent to // set the limit smaller then the already allocated memory. if (what == MALLOC_SOFT_LIMIT) { if (limit < 0) return MY_FALSE; soft_malloc_limit = limit; // reset flag if appropriate. if ((mp_int)xalloc_stat.size < soft_malloc_limit && low_memory_applied) low_memory_applied = MY_FALSE; } else { // setting the limit below the currently allocated memory seems to be // a very bad idea. if (limit && limit <= (mp_int)xalloc_stat.size) return MY_FALSE; max_malloced = limit; } return MY_TRUE; #else return MY_FALSE; #endif // NO_MEM_BLOCK_SIZE } /* set_memory_limit */ /*-------------------------------------------------------------------------*/ mp_int get_memory_limit(enum memory_limit_types what) /* Return the value of limit . */ { #ifndef NO_MEM_BLOCK_SIZE if (what == MALLOC_SOFT_LIMIT) return soft_malloc_limit; else return max_malloced; #else return 0; #endif } /***************************************************************************/