#include "tclInt.h"
#include "tclPort.h"
#define FALSE 0
#define TRUE 1
#ifdef TCL_MEM_DEBUG
typedef struct MemTag {
int refCount;
char string[4];
} MemTag;
#define TAG_SIZE(bytesInString) ((unsigned) sizeof(MemTag) + bytesInString - 3)
static MemTag *curTagPtr = NULL;
#define LOW_GUARD_SIZE (8 + (32 - (sizeof(long) + sizeof(int)))%8)
struct mem_header {
struct mem_header *flink;
struct mem_header *blink;
MemTag *tagPtr;
char *file;
long length;
int line;
unsigned char low_guard[LOW_GUARD_SIZE];
char body[1];
};
static struct mem_header *allocHead = NULL;
#define GUARD_VALUE 0141
#define HIGH_GUARD_SIZE 8
#define BODY_OFFSET \
((unsigned long) (&((struct mem_header *) 0)->body))
static int total_mallocs = 0;
static int total_frees = 0;
static int current_bytes_malloced = 0;
static int maximum_bytes_malloced = 0;
static int current_malloc_packets = 0;
static int maximum_malloc_packets = 0;
static int break_on_malloc = 0;
static int trace_on_at_malloc = 0;
static int alloc_tracing = FALSE;
static int init_malloced_bodies = TRUE;
#ifdef MEM_VALIDATE
static int validate_memory = TRUE;
#else
static int validate_memory = FALSE;
#endif
static int MemoryCmd _ANSI_ARGS_((ClientData clientData,
Tcl_Interp *interp, int argc, char **argv));
static void ValidateMemory _ANSI_ARGS_((
struct mem_header *memHeaderP, char *file,
int line, int nukeGuards));
void
TclDumpMemoryInfo(outFile)
FILE *outFile;
{
fprintf(outFile,"total mallocs %10d\n",
total_mallocs);
fprintf(outFile,"total frees %10d\n",
total_frees);
fprintf(outFile,"current packets allocated %10d\n",
current_malloc_packets);
fprintf(outFile,"current bytes allocated %10d\n",
current_bytes_malloced);
fprintf(outFile,"maximum packets allocated %10d\n",
maximum_malloc_packets);
fprintf(outFile,"maximum bytes allocated %10d\n",
maximum_bytes_malloced);
}
static void
ValidateMemory(memHeaderP, file, line, nukeGuards)
struct mem_header *memHeaderP;
char *file;
int line;
int nukeGuards;
{
unsigned char *hiPtr;
int idx;
int guard_failed = FALSE;
int byte;
for (idx = 0; idx < LOW_GUARD_SIZE; idx++) {
byte = *(memHeaderP->low_guard + idx);
if (byte != GUARD_VALUE) {
guard_failed = TRUE;
fflush(stdout);
byte &= 0xff;
fprintf(stderr, "low guard byte %d is 0x%x \t%c\n", idx, byte,
(isprint(UCHAR(byte)) ? byte : ' '));
}
}
if (guard_failed) {
TclDumpMemoryInfo (stderr);
fprintf(stderr, "low guard failed at %lx, %s %d\n",
(long unsigned int) memHeaderP->body, file, line);
fflush(stderr);
fprintf(stderr, "%ld bytes allocated at (%s %d)\n", memHeaderP->length,
memHeaderP->file, memHeaderP->line);
panic ("Memory validation failure");
}
hiPtr = (unsigned char *)memHeaderP->body + memHeaderP->length;
for (idx = 0; idx < HIGH_GUARD_SIZE; idx++) {
byte = *(hiPtr + idx);
if (byte != GUARD_VALUE) {
guard_failed = TRUE;
fflush (stdout);
byte &= 0xff;
fprintf(stderr, "hi guard byte %d is 0x%x \t%c\n", idx, byte,
(isprint(UCHAR(byte)) ? byte : ' '));
}
}
if (guard_failed) {
TclDumpMemoryInfo (stderr);
fprintf(stderr, "high guard failed at %lx, %s %d\n",
(long unsigned int) memHeaderP->body, file, line);
fflush(stderr);
fprintf(stderr, "%ld bytes allocated at (%s %d)\n",
memHeaderP->length, memHeaderP->file,
memHeaderP->line);
panic("Memory validation failure");
}
if (nukeGuards) {
memset ((char *) memHeaderP->low_guard, 0, LOW_GUARD_SIZE);
memset ((char *) hiPtr, 0, HIGH_GUARD_SIZE);
}
}
void
Tcl_ValidateAllMemory (file, line)
char *file;
int line;
{
struct mem_header *memScanP;
for (memScanP = allocHead; memScanP != NULL; memScanP = memScanP->flink)
ValidateMemory(memScanP, file, line, FALSE);
}
int
Tcl_DumpActiveMemory (fileName)
char *fileName;
{
FILE *fileP;
struct mem_header *memScanP;
char *address;
fileP = fopen(fileName, "w");
if (fileP == NULL)
return TCL_ERROR;
for (memScanP = allocHead; memScanP != NULL; memScanP = memScanP->flink) {
address = &memScanP->body [0];
fprintf(fileP, "%8lx - %8lx %7ld @ %s %d %s",
(long unsigned int) address,
(long unsigned int) address + memScanP->length - 1,
memScanP->length, memScanP->file, memScanP->line,
(memScanP->tagPtr == NULL) ? "" : memScanP->tagPtr->string);
(void) fputc('\n', fileP);
}
fclose (fileP);
return TCL_OK;
}
char *
Tcl_DbCkalloc(size, file, line)
unsigned int size;
char *file;
int line;
{
struct mem_header *result;
if (validate_memory)
Tcl_ValidateAllMemory (file, line);
result = (struct mem_header *) TclpAlloc((unsigned)size +
sizeof(struct mem_header) + HIGH_GUARD_SIZE);
if (result == NULL) {
fflush(stdout);
TclDumpMemoryInfo(stderr);
panic("unable to alloc %d bytes, %s line %d", size, file,
line);
}
if (init_malloced_bodies) {
memset ((VOID *) result, GUARD_VALUE,
size + sizeof(struct mem_header) + HIGH_GUARD_SIZE);
} else {
memset ((char *) result->low_guard, GUARD_VALUE, LOW_GUARD_SIZE);
memset (result->body + size, GUARD_VALUE, HIGH_GUARD_SIZE);
}
result->length = size;
result->tagPtr = curTagPtr;
if (curTagPtr != NULL) {
curTagPtr->refCount++;
}
result->file = file;
result->line = line;
result->flink = allocHead;
result->blink = NULL;
if (allocHead != NULL)
allocHead->blink = result;
allocHead = result;
total_mallocs++;
if (trace_on_at_malloc && (total_mallocs >= trace_on_at_malloc)) {
(void) fflush(stdout);
fprintf(stderr, "reached malloc trace enable point (%d)\n",
total_mallocs);
fflush(stderr);
alloc_tracing = TRUE;
trace_on_at_malloc = 0;
}
if (alloc_tracing)
fprintf(stderr,"ckalloc %lx %d %s %d\n",
(long unsigned int) result->body, size, file, line);
if (break_on_malloc && (total_mallocs >= break_on_malloc)) {
break_on_malloc = 0;
(void) fflush(stdout);
fprintf(stderr,"reached malloc break limit (%d)\n",
total_mallocs);
fprintf(stderr, "program will now enter C debugger\n");
(void) fflush(stderr);
abort();
}
current_malloc_packets++;
if (current_malloc_packets > maximum_malloc_packets)
maximum_malloc_packets = current_malloc_packets;
current_bytes_malloced += size;
if (current_bytes_malloced > maximum_bytes_malloced)
maximum_bytes_malloced = current_bytes_malloced;
return result->body;
}
int
Tcl_DbCkfree(ptr, file, line)
char * ptr;
char *file;
int line;
{
struct mem_header *memp = (struct mem_header *)
(((unsigned long) ptr) - BODY_OFFSET);
if (alloc_tracing)
fprintf(stderr, "ckfree %lx %ld %s %d\n",
(long unsigned int) memp->body, memp->length, file, line);
if (validate_memory)
Tcl_ValidateAllMemory(file, line);
ValidateMemory(memp, file, line, TRUE);
if (init_malloced_bodies) {
memset((VOID *) ptr, GUARD_VALUE, (size_t) memp->length);
}
total_frees++;
current_malloc_packets--;
current_bytes_malloced -= memp->length;
if (memp->tagPtr != NULL) {
memp->tagPtr->refCount--;
if ((memp->tagPtr->refCount == 0) && (curTagPtr != memp->tagPtr)) {
TclpFree((char *) memp->tagPtr);
}
}
if (memp->flink != NULL)
memp->flink->blink = memp->blink;
if (memp->blink != NULL)
memp->blink->flink = memp->flink;
if (allocHead == memp)
allocHead = memp->flink;
TclpFree((char *) memp);
return 0;
}
char *
Tcl_DbCkrealloc(ptr, size, file, line)
char *ptr;
unsigned int size;
char *file;
int line;
{
char *new;
unsigned int copySize;
struct mem_header *memp = (struct mem_header *)
(((unsigned long) ptr) - BODY_OFFSET);
copySize = size;
if (copySize > (unsigned int) memp->length) {
copySize = memp->length;
}
new = Tcl_DbCkalloc(size, file, line);
memcpy((VOID *) new, (VOID *) ptr, (size_t) copySize);
Tcl_DbCkfree(ptr, file, line);
return(new);
}
#undef Tcl_Alloc
#undef Tcl_Free
#undef Tcl_Realloc
char *
Tcl_Alloc(size)
unsigned int size;
{
return Tcl_DbCkalloc(size, "unknown", 0);
}
void
Tcl_Free(ptr)
char *ptr;
{
Tcl_DbCkfree(ptr, "unknown", 0);
}
char *
Tcl_Realloc(ptr, size)
char *ptr;
unsigned int size;
{
return Tcl_DbCkrealloc(ptr, size, "unknown", 0);
}
static int
MemoryCmd (clientData, interp, argc, argv)
ClientData clientData;
Tcl_Interp *interp;
int argc;
char **argv;
{
char *fileName;
Tcl_DString buffer;
int result;
if (argc < 2) {
Tcl_AppendResult(interp, "wrong # args: should be \"",
argv[0], " option [args..]\"", (char *) NULL);
return TCL_ERROR;
}
if (strcmp(argv[1],"active") == 0) {
if (argc != 3) {
Tcl_AppendResult(interp, "wrong # args: should be \"",
argv[0], " active file\"", (char *) NULL);
return TCL_ERROR;
}
fileName = Tcl_TranslateFileName(interp, argv[2], &buffer);
if (fileName == NULL) {
return TCL_ERROR;
}
result = Tcl_DumpActiveMemory (fileName);
Tcl_DStringFree(&buffer);
if (result != TCL_OK) {
Tcl_AppendResult(interp, "error accessing ", argv[2],
(char *) NULL);
return TCL_ERROR;
}
return TCL_OK;
}
if (strcmp(argv[1],"break_on_malloc") == 0) {
if (argc != 3) {
goto argError;
}
if (Tcl_GetInt(interp, argv[2], &break_on_malloc) != TCL_OK) {
return TCL_ERROR;
}
return TCL_OK;
}
if (strcmp(argv[1],"info") == 0) {
TclDumpMemoryInfo(stdout);
return TCL_OK;
}
if (strcmp(argv[1],"init") == 0) {
if (argc != 3) {
goto bad_suboption;
}
init_malloced_bodies = (strcmp(argv[2],"on") == 0);
return TCL_OK;
}
if (strcmp(argv[1],"tag") == 0) {
if (argc != 3) {
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" tag string\"", (char *) NULL);
return TCL_ERROR;
}
if ((curTagPtr != NULL) && (curTagPtr->refCount == 0)) {
TclpFree((char *) curTagPtr);
}
curTagPtr = (MemTag *) TclpAlloc(TAG_SIZE(strlen(argv[2])));
curTagPtr->refCount = 0;
strcpy(curTagPtr->string, argv[2]);
return TCL_OK;
}
if (strcmp(argv[1],"trace") == 0) {
if (argc != 3) {
goto bad_suboption;
}
alloc_tracing = (strcmp(argv[2],"on") == 0);
return TCL_OK;
}
if (strcmp(argv[1],"trace_on_at_malloc") == 0) {
if (argc != 3) {
goto argError;
}
if (Tcl_GetInt(interp, argv[2], &trace_on_at_malloc) != TCL_OK) {
return TCL_ERROR;
}
return TCL_OK;
}
if (strcmp(argv[1],"validate") == 0) {
if (argc != 3) {
goto bad_suboption;
}
validate_memory = (strcmp(argv[2],"on") == 0);
return TCL_OK;
}
Tcl_AppendResult(interp, "bad option \"", argv[1],
"\": should be active, break_on_malloc, info, init, ",
"tag, trace, trace_on_at_malloc, or validate", (char *) NULL);
return TCL_ERROR;
argError:
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" ", argv[1], " count\"", (char *) NULL);
return TCL_ERROR;
bad_suboption:
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" ", argv[1], " on|off\"", (char *) NULL);
return TCL_ERROR;
}
void
Tcl_InitMemory(interp)
Tcl_Interp *interp;
{
Tcl_CreateCommand (interp, "memory", MemoryCmd, (ClientData) NULL,
(Tcl_CmdDeleteProc *) NULL);
}
#else
char *
Tcl_Alloc (size)
unsigned int size;
{
char *result;
result = TclpAlloc(size);
if (result == NULL && size )
panic("unable to alloc %d bytes", size);
return result;
}
char *
Tcl_DbCkalloc(size, file, line)
unsigned int size;
char *file;
int line;
{
char *result;
result = (char *) TclpAlloc(size);
if (result == NULL) {
fflush(stdout);
panic("unable to alloc %d bytes, %s line %d", size, file,
line);
}
return result;
}
char *
Tcl_Realloc(ptr, size)
char *ptr;
unsigned int size;
{
char *result;
result = TclpRealloc(ptr, size);
if (result == NULL)
panic("unable to realloc %d bytes", size);
return result;
}
char *
Tcl_DbCkrealloc(ptr, size, file, line)
char *ptr;
unsigned int size;
char *file;
int line;
{
char *result;
result = (char *) TclpRealloc(ptr, size);
if (result == NULL) {
fflush(stdout);
panic("unable to realloc %d bytes, %s line %d", size, file,
line);
}
return result;
}
void
Tcl_Free (ptr)
char *ptr;
{
TclpFree(ptr);
}
int
Tcl_DbCkfree(ptr, file, line)
char * ptr;
char *file;
int line;
{
TclpFree(ptr);
return 0;
}
void
Tcl_InitMemory(interp)
Tcl_Interp *interp;
{
}
#undef Tcl_DumpActiveMemory
#undef Tcl_ValidateAllMemory
extern int Tcl_DumpActiveMemory _ANSI_ARGS_((char *fileName));
extern void Tcl_ValidateAllMemory _ANSI_ARGS_((char *file,
int line));
int
Tcl_DumpActiveMemory(fileName)
char *fileName;
{
return TCL_OK;
}
void
Tcl_ValidateAllMemory(file, line)
char *file;
int line;
{
}
#endif