326 lines
7.6 KiB
C
326 lines
7.6 KiB
C
/* 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 <ctype.h>
|
|
|
|
#ifdef MICRO
|
|
#include <stdlib.h>
|
|
#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*/
|