/* SCCS Id: @(#)heaputil.c 3.3 94/07/17 */ /* Copyright (c) Michael Allison, Toronto, 1994 */ /* NetHack may be freely redistributed. See license for details. */ /* * We want to know the following information: * * 1. allocations that are made but never freed, and where they occur. * 2. attempts to free unallocated blocks. * 3. the peak amount of heap space allocated during the program. * */ #include "config.h" #include #ifdef MICRO #include #endif #ifdef MICRO /* see src/alloc.c */ # define MONITOR_HEAP_FMT #endif #ifdef MONITOR_HEAP_FMT # define PTR_FMT "%p" # define PTR_TYP genericptr_t /* (void *) */ #else # define PTR_FMT "%lx" # define PTR_TYP unsigned long #endif extern genericptr_t FDECL(malloc,(size_t)); #ifdef free #undef free #endif extern void FDECL(free,(genericptr_t)); #define quit() exit(EXIT_FAILURE) static void NDECL(out_of_memory); static void FDECL(doline,(char *)); static void FDECL(chain,(char *)); static void FDECL(unchain,(char *)); static void NDECL(walkblocks); static struct memblock *FDECL(findprev,(PTR_TYP)); static void FDECL(btempl,(char *)); #ifdef VMS static FILE *vms_fopen(name, mode) const char *name, *mode; { return fopen(name, mode, "mbc=64", "shr=nil"); } # define fopen(f,m) vms_fopen(f,m) #endif #define DEFAULTNAME "heapuse.log" #define MAXERR 4 #ifndef _MAX_PATH #define _MAX_PATH 120 #endif struct memblock { struct memblock *next; long sequence, bsize; PTR_TYP address; char fileinfo[5 + _MAX_PATH + 1]; }; /* a cheap way to try to split addresses into two categories */ #define CHAIN_SELECT(X) (((unsigned long)X >> 10) & 1) #define OPERATION 0 /* first character; + for alloc, - for free */ #define BLKSIZE 1 /* block size; 5 digits starting at 2nd char */ #define ADDRESS 7 /* sizeof "+12345 " - sizeof "" */ #define FILEINFO fileinfo_offset #define terminate_fields(a) \ a[ADDRESS - 1] = a[FILEINFO - 1] = '\0' #define zero_fields(a) \ a[OPERATION] = a[BLKSIZE] = a[ADDRESS] = a[FILEINFO] = '\0' static int fileinfo_offset; static struct memblock *firstblock[2] = {0,0}, *blkcache = 0; static long peakmem; static long curmem; static long totaldynmem; static long lineno; static FILE *infile; static char line[255]; static const char *infilenm; static int errcount; static int have_template; int main(argc, argv) int argc; char *argv[]; { infilenm = (argc < 2) ? getenv("NH_HEAPLOG") : argv[1]; if (!infilenm || !*infilenm) infilenm = DEFAULTNAME; infile = fopen(infilenm,"r"); if (!infile) { printf("%s not found or unavailable\n",infilenm); quit(); } while (fgets(line, sizeof line, infile)) { ++lineno; doline(line); zero_fields(line); } fclose(infile); walkblocks(); printf("Peak heap usage was %ld bytes.\n",peakmem); printf("Total heap memory allocations was %ld bytes.\n", totaldynmem); exit(EXIT_SUCCESS); /*NOTREACHED*/ return 0; } static void out_of_memory() { printf("heaputil: out of memory at line %ld of %s\n", lineno, infilenm); quit(); } /* * Sample: + 43 6852:0A0A 568 ..\win\tty\wintty.c - 6852:0A0A 1291 ..\win\tty\wintty.c * Description: ^ size address line file ^: + => alloc, - => free * * Note: In environments other than MSDOS 16 bit segmented ones, * the address field will just be a single string of digits. * * Parsing: * The operation flag and allocation size are in fixed columns; * the address starts at a fixed offset and its width is expected * to be the same for every entry, but that width may vary from * platform to platform; the line number is a right-justified * 4-digit number separated from the address by one space and * followed after a space by the filename; line+file+newline * are treated as a single unit throughout. * * Only the first line obtained from the file will be scanned * to determine the offsets of the various fields: operation, * size, address, and fileinfo. Subsequent lines are processed * by depositing NUL at the appropriate offsets in the string. * This method seems to be quite fast and avoids complicated * parsing. */ static void doline(line) char *line; { if (!have_template) btempl(line); if (line[OPERATION] == '+') chain(line); else if (line[OPERATION] == '-') unchain(line); else { printf("%6ld: invalid operation, badly formatted line.\n", lineno); printf(" -> %s",line); if (++errcount > MAXERR) { printf("Giving up on this file, its not right.\n"); quit(); } } } static void chain(line) char *line; { struct memblock *block; int i; terminate_fields(line); if (blkcache) { block = blkcache; blkcache = block->next; } else { block = (struct memblock *)malloc(sizeof(struct memblock)); if (!block) out_of_memory(); } block->sequence = lineno; block->bsize = atol(&line[BLKSIZE]); (void)sscanf(&line[ADDRESS], PTR_FMT, &block->address); Strcpy(block->fileinfo, &line[FILEINFO]); if (block->address == 0) { printf("%6ld: allocation of %ld bytes returned null pointer, %s", block->sequence, block->bsize, block->fileinfo); return; } i = CHAIN_SELECT(block->address); block->next = firstblock[i]; firstblock[i] = block; curmem += block->bsize; if (curmem > peakmem) peakmem = curmem; totaldynmem += block->bsize; } static void unchain(line) char *line; { struct memblock *block; struct memblock *rmblock = (struct memblock *)0; PTR_TYP address; int i; terminate_fields(line); (void)sscanf(&line[ADDRESS], PTR_FMT, &address); i = CHAIN_SELECT(address); if (address == 0) { printf("%6ld: attempt to free null pointer, %s", lineno, &line[FILEINFO]); return; } else if (firstblock[i]) { /* special case, first one so no prev available */ if (address == firstblock[i]->address) { rmblock = firstblock[i]; firstblock[i] = rmblock->next; } else { block = findprev(address); if (block) { rmblock = block->next; block->next = rmblock->next; } } } if (!rmblock) { printf("%6ld: attempt to free unallocated block, %s", lineno, &line[FILEINFO]); } else { curmem -= rmblock->bsize; if (curmem < 0) { printf("%6ld: impossible, current memory use negative, %s", lineno, &line[FILEINFO]); } rmblock->next = blkcache; blkcache = rmblock; } } static struct memblock * findprev(maddress) PTR_TYP maddress; { struct memblock *tmpblk, *nextblk; tmpblk = firstblock[CHAIN_SELECT(maddress)]; while (tmpblk) { nextblk = tmpblk->next; if (nextblk) { if (maddress == nextblk->address) return tmpblk; } tmpblk = nextblk; } return (struct memblock *)0; } static void walkblocks() { struct memblock *tmpblk, *nextblk; int i, k; long unfreedmem = 0L; /* make a sorted list from all the chains; expected to be short */ tmpblk = 0; do { k = 0; for (i = 1; i < SIZE(firstblock); i++) if (!firstblock[k] || (firstblock[i] && firstblock[i]->sequence > firstblock[k]->sequence)) k = i; nextblk = firstblock[k]; if (nextblk) { firstblock[k] = nextblk->next; nextblk->next = tmpblk; tmpblk = nextblk; } } while (nextblk); while (tmpblk) { printf("%6ld: not freed: %5ld bytes, %s", tmpblk->sequence, tmpblk->bsize, tmpblk->fileinfo); unfreedmem += tmpblk->bsize; nextblk = tmpblk->next; free((genericptr_t)tmpblk); tmpblk = nextblk; } printf("Total of %ld bytes not freed before exit.\n", unfreedmem); /* free memblock cache */ while (blkcache) { tmpblk = blkcache; blkcache = tmpblk->next; free((genericptr_t)tmpblk); } } static void btempl(line) char *line; { char *p; p = &line[ADDRESS - 1]; while (isspace(*p)) ++p; while (*p && *p != ' ' && *p != '\n') ++p; if (*p == ' ') ++p; /* skip first space */ fileinfo_offset = p - line; ++have_template; } /*heaputil.c*/