/* alloc.c - memory allocation subsystem */ /* $Id: alloc.c,v 1.23 2002/09/23 08:42:08 rmg Exp $ */ #include "copyright.h" #include "autoconf.h" #include "config.h" #include "alloc.h" /* required by mudconf */ #include "flags.h" /* required by mudconf */ #include "htab.h" /* required by mudconf */ #include "mudconf.h" /* required by code */ #include "db.h" /* required by externs */ #include "externs.h" /* required by code */ POOL pools[NUM_POOLS]; const char *poolnames[] = {"Sbufs", "Mbufs", "Lbufs", "Bools", "Descs", "Qentries", "Pcaches"}; #define POOL_MAGICNUM 0xdeadbeef void pool_init(poolnum, poolsize) int poolnum, poolsize; { pools[poolnum].pool_size = poolsize; pools[poolnum].free_head = NULL; pools[poolnum].chain_head = NULL; pools[poolnum].tot_alloc = 0; pools[poolnum].num_alloc = 0; pools[poolnum].max_alloc = 0; pools[poolnum].num_lost = 0; return; } static void pool_err(logsys, logflag, poolnum, tag, ph, action, reason) int logflag, poolnum; const char *logsys, *tag, *action, *reason; POOLHDR *ph; { if (!mudstate.logging) { STARTLOG(logflag, logsys, "ALLOC") log_printf("%s[%d] (tag %s) %s at %lx. (%s)", action, pools[poolnum].pool_size, tag, reason, (long)ph, mudstate.debug_cmd); ENDLOG } else if (logflag != LOG_ALLOCATE) { log_printf("\n***< %s[%d] (tag %s) %s at %lx. >***", action, pools[poolnum].pool_size, tag, reason, (long)ph); } } static void pool_vfy(poolnum, tag) int poolnum; const char *tag; { POOLHDR *ph, *lastph; POOLFTR *pf; char *h; int psize; lastph = NULL; psize = pools[poolnum].pool_size; for (ph = pools[poolnum].chain_head; ph; lastph = ph, ph = ph->next) { h = (char *)ph; h += sizeof(POOLHDR); h += pools[poolnum].pool_size; pf = (POOLFTR *) h; if (ph->magicnum != POOL_MAGICNUM) { pool_err("BUG", LOG_ALWAYS, poolnum, tag, ph, "Verify", "header corrupted (clearing freelist)"); /* * Break the header chain at this point so we don't * generate an error for EVERY alloc and free, * also we can't continue the scan because the * next pointer might be trash too. */ if (lastph) lastph->next = NULL; else pools[poolnum].chain_head = NULL; return; /* not safe to continue */ } if (pf->magicnum != POOL_MAGICNUM) { pool_err("BUG", LOG_ALWAYS, poolnum, tag, ph, "Verify", "footer corrupted"); pf->magicnum = POOL_MAGICNUM; } if (ph->pool_size != psize) { pool_err("BUG", LOG_ALWAYS, poolnum, tag, ph, "Verify", "header has incorrect size"); } } } void pool_check(tag) const char *tag; { int poolnum; for (poolnum = 0; poolnum < NUM_POOLS; ++poolnum) { pool_vfy(poolnum, tag); } } char *pool_alloc(poolnum, tag) int poolnum; const char *tag; { int *p; char *h; POOLHDR *ph; POOLFTR *pf; if (mudconf.paranoid_alloc) pool_check(tag); do { if (pools[poolnum].free_head == NULL) { h = (char *)RAW_MALLOC(pools[poolnum].pool_size + sizeof(POOLHDR) + sizeof(POOLFTR), "pool_alloc.ph"); if (h == NULL) { fprintf(mainlog_fp, "ABORT! alloc.c, pool_alloc() failed to get memory.\n"); abort(); } ph = (POOLHDR *) h; h += sizeof(POOLHDR); p = (int *)h; h += pools[poolnum].pool_size; pf = (POOLFTR *) h; ph->next = pools[poolnum].chain_head; ph->nxtfree = NULL; ph->magicnum = POOL_MAGICNUM; ph->pool_size = pools[poolnum].pool_size; pf->magicnum = POOL_MAGICNUM; *p = POOL_MAGICNUM; pools[poolnum].chain_head = ph; pools[poolnum].max_alloc++; } else { ph = (POOLHDR *) (pools[poolnum].free_head); h = (char *)ph; h += sizeof(POOLHDR); p = (int *)h; h += pools[poolnum].pool_size; pf = (POOLFTR *) h; pools[poolnum].free_head = ph->nxtfree; /* If corrupted header we need to throw away the * freelist as the freelist pointer may be corrupt. */ if (ph->magicnum != POOL_MAGICNUM) { pool_err("BUG", LOG_ALWAYS, poolnum, tag, ph, "Alloc", "corrupted buffer header"); /* Start a new free list and record stats */ p = NULL; pools[poolnum].free_head = NULL; pools[poolnum].num_lost += (pools[poolnum].tot_alloc - pools[poolnum].num_alloc); pools[poolnum].tot_alloc = pools[poolnum].num_alloc; } /* Check for corrupted footer, just report and fix * it */ if (pf->magicnum != POOL_MAGICNUM) { pool_err("BUG", LOG_ALWAYS, poolnum, tag, ph, "Alloc", "corrupted buffer footer"); pf->magicnum = POOL_MAGICNUM; } } } while (p == NULL); ph->buf_tag = (char *)tag; pools[poolnum].tot_alloc++; pools[poolnum].num_alloc++; pool_err("DBG", LOG_ALLOCATE, poolnum, tag, ph, "Alloc", "buffer"); /* If the buffer was modified after it was last freed, log it. */ if ((*p != POOL_MAGICNUM) && (!mudstate.logging)) { pool_err("BUG", LOG_PROBLEMS, poolnum, tag, ph, "Alloc", "buffer modified after free"); } *p = 0; return (char *)p; } void pool_free(poolnum, buf) int poolnum; char **buf; { int *ibuf; char *h; POOLHDR *ph; POOLFTR *pf; ibuf = (int *)*buf; h = (char *)ibuf; h -= sizeof(POOLHDR); ph = (POOLHDR *) h; h = (char *)ibuf; h += pools[poolnum].pool_size; pf = (POOLFTR *) h; if (mudconf.paranoid_alloc) pool_check(ph->buf_tag); /* Make sure the buffer header is good. If it isn't, log the error * and throw away the buffer. */ if (ph->magicnum != POOL_MAGICNUM) { pool_err("BUG", LOG_ALWAYS, poolnum, ph->buf_tag, ph, "Free", "corrupted buffer header"); pools[poolnum].num_lost++; pools[poolnum].num_alloc--; pools[poolnum].tot_alloc--; return; } /* Verify the buffer footer. Don't unlink if damaged, just repair */ if (pf->magicnum != POOL_MAGICNUM) { pool_err("BUG", LOG_ALWAYS, poolnum, ph->buf_tag, ph, "Free", "corrupted buffer footer"); pf->magicnum = POOL_MAGICNUM; } /* Verify that we are not trying to free someone else's buffer */ if (ph->pool_size != pools[poolnum].pool_size) { pool_err("BUG", LOG_ALWAYS, poolnum, ph->buf_tag, ph, "Free", "Attempt to free into a different pool."); return; } pool_err("DBG", LOG_ALLOCATE, poolnum, ph->buf_tag, ph, "Free", "buffer"); /* Make sure we aren't freeing an already free buffer. If we are, * log an error, otherwise update the pool header and stats */ if (*ibuf == POOL_MAGICNUM) { pool_err("BUG", LOG_BUGS, poolnum, ph->buf_tag, ph, "Free", "buffer already freed"); } else { *ibuf = POOL_MAGICNUM; ph->nxtfree = pools[poolnum].free_head; pools[poolnum].free_head = ph; pools[poolnum].num_alloc--; } } static char *pool_stats(poolnum, text) int poolnum; const char *text; { char *buf; buf = alloc_mbuf("pool_stats"); sprintf(buf, "%-15s %5d%9d%9d%9d%9d", text, pools[poolnum].pool_size, pools[poolnum].num_alloc, pools[poolnum].max_alloc, pools[poolnum].tot_alloc, pools[poolnum].num_lost); return buf; } static void pool_trace(player, poolnum, text) dbref player; int poolnum; const char *text; { POOLHDR *ph; int numfree, *ibuf; char *h; numfree = 0; notify(player, tprintf("----- %s -----", text)); for (ph = pools[poolnum].chain_head; ph != NULL; ph = ph->next) { if (ph->magicnum != POOL_MAGICNUM) { notify(player, "*** CORRUPTED BUFFER HEADER, ABORTING SCAN ***"); notify(player, tprintf("%d free %s (before corruption)", numfree, text)); return; } h = (char *)ph; h += sizeof(POOLHDR); ibuf = (int *)h; if (*ibuf != POOL_MAGICNUM) notify(player, ph->buf_tag); else numfree++; } notify(player, tprintf("%d free %s", numfree, text)); } void list_bufstats(player) dbref player; { int i; char *buff; notify(player, "Buffer Stats Size InUse Total Allocs Lost"); for (i = 0; i < NUM_POOLS; i++) { buff = pool_stats(i, poolnames[i]); notify(player, buff); free_mbuf(buff); } } void list_buftrace(player) dbref player; { int i; for (i = 0; i < NUM_POOLS; i++) pool_trace(player, i, poolnames[i]); } void pool_reset() { POOLHDR *ph, *phnext, *newchain; int i, *ibuf; char *h; for (i = 0; i < NUM_POOLS; i++) { newchain = NULL; for (ph = pools[i].chain_head; ph != NULL; ph = phnext) { h = (char *)ph; phnext = ph->next; h += sizeof(POOLHDR); ibuf = (int *)h; if (*ibuf == POOL_MAGICNUM) { RAW_FREE(ph, "pool_reset.ph"); } else { if (!newchain) { newchain = ph; ph->next = NULL; } else { ph->next = newchain; newchain = ph; } ph->nxtfree = NULL; } } pools[i].chain_head = newchain; pools[i].free_head = NULL; pools[i].max_alloc = pools[i].num_alloc; } } /* --------------------------------------------------------------------------- * Track allocations that don't use the memory pools. */ #ifdef RAW_MEMTRACKING static int sort_memtable(p1, p2) const void *p1, *p2; { return strcmp((*(MEMTRACK **)p1)->buf_tag, (*(MEMTRACK **)p2)->buf_tag); } void list_rawmemory(player) dbref player; { MEMTRACK *tptr, **t_array; int n_tags, total, c_tags, c_total, u_tags, i, j; const char *ntmp; n_tags = total = 0; raw_notify(player, "Memory Tag Allocs Bytes"); for (tptr = mudstate.raw_allocs; tptr != NULL; tptr = tptr->next) { n_tags++; total += (int) tptr->alloc; } t_array = (MEMTRACK **) RAW_CALLOC(total, sizeof(MEMTRACK *), "list_rawmemory"); for (i = 0, tptr = mudstate.raw_allocs; tptr != NULL; i++, tptr = tptr->next) { t_array[i] = tptr; } qsort((void *) t_array, n_tags, sizeof(MEMTRACK *), (int (*)(const void *, const void *))sort_memtable); for (i = 0; i < n_tags; ) { u_tags++; ntmp = t_array[i]->buf_tag; if ((i < n_tags - 1) && !strcmp(ntmp, t_array[i+1]->buf_tag)) { c_tags = 2; c_total = (int) t_array[i]->alloc + (int) t_array[i+1]->alloc; for (j = i + 2; (j < n_tags) && !strcmp(ntmp, t_array[j]->buf_tag); j++) { c_tags++; c_total += (int) t_array[j]->alloc; } i = j; } else { c_tags = 1; c_total = (int) t_array[i]->alloc; i++; } raw_notify(player, tprintf("%-35.35s %6d %8d", ntmp, c_tags, c_total)); } RAW_FREE(t_array, "list_rawmemory"); raw_notify(player, tprintf("Total: %d raw allocations in %d unique tags. %d bytes (%d K).", n_tags, u_tags, total, (int) total / 1024)); } static void trace_alloc(amount, name, ptr) size_t amount; const char *name; void *ptr; { /* We maintain an unsorted list, most recently-allocated things at * the head, based on the belief that it's basically a stack -- * when we go to free something it's usually the most recent thing * that we've allocated. */ MEMTRACK *tptr; tptr = (MEMTRACK *) RAW_MALLOC(sizeof(MEMTRACK), "trace_alloc.tptr"); if (!tptr) return; tptr->buf_tag = name; tptr->bptr = ptr; tptr->alloc = amount; tptr->next = mudstate.raw_allocs; mudstate.raw_allocs = tptr; } static void trace_free(name, ptr) const char *name; void *ptr; { MEMTRACK *tptr, *prev; prev = NULL; for (tptr = mudstate.raw_allocs; tptr != NULL; tptr = tptr->next) { if (tptr->bptr == ptr) { if (strcmp(tptr->buf_tag, name)) { STARTLOG(LOG_BUGS, "MEM", "TRACE") log_printf("Free mismatch, tag %s allocated as %s", name, tptr->buf_tag); ENDLOG } if (mudstate.raw_allocs == tptr) mudstate.raw_allocs = tptr->next; if (prev) prev->next = tptr->next; RAW_FREE(tptr, "trace_alloc.tptr"); return; } prev = tptr; } STARTLOG(LOG_BUGS, "MEM", "TRACE") log_printf("Attempt to free unknown with tag %s", name); ENDLOG } void *track_malloc(amount, name) size_t amount; const char *name; { void *r; r = malloc(amount); trace_alloc(amount, name, r); return (r); } void *track_calloc(elems, esize, name) size_t elems, esize; const char *name; { void *r; r = calloc(elems, esize); trace_alloc(elems * esize, name, r); return (r); } void *track_realloc(ptr, amount, name) void *ptr; size_t amount; const char *name; { void *r; trace_free(name, r); r = realloc(ptr, amount); trace_alloc(amount, name, r); return (r); } char *track_strdup(str, name) const char *str, *name; { char *r; r = strdup(str); trace_alloc(strlen(str) + 1, name, r); return (r); } void track_free(ptr, name) void *ptr; const char *name; { trace_free(name, ptr); free(ptr); } #else void list_rawmemory(player) dbref player; { notify(player, "Feature not supported."); } #endif /* RAW_MEMTRACKING */