/* Copyright (c) 1992 by David Moore. All rights reserved. */
/* debug_malloc.c,v 2.7 1996/01/26 01:50:33 dmoore Exp */
/* written by dmoore 12/26/91, to reduce the MALLOC, FREE, etc macros
into subroutines. */
#include "config.h"
#ifdef DEBUG_MALLOCS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "externs.h"
/* The trick is, we will always ask malloc for the requested memory
PLUS enough memory to hold this stucture at the beginning. Then
we will fill in the appropriate fields, and return the pointer
just past the structure to the caller. The magic numbers will
hopefully be useful to track illegal frees/reallocs. By keeping
a linked list of the things we have malloced, as well as the file
and line number where they were malloced, memory leaks can be
tracked. */
/* You want the size of this structure to be a multiple of your
alignment size. That way, when the real malloc returns something aligned
the result these routines return will be aligned. */
union debug_malloc_info {
struct {
int magic1;
const char *file;
int line;
#ifdef DEBUG_MALLOCS_RCHECK
size_t size;
#endif /* DEBUG_MALLOCS_RCHECK */
#ifdef DEBUG_MALLOCS_TRACE
union debug_malloc_info *next, *prev;
#endif /* DEBUG_MALLOCS_TRACE */
int magic2;
} info;
char align_force[ALIGN_BYTES];
};
/* Two magic numbers, one to specify a valid pointer that our malloc
has generated. The other set on freed objects. Note that the freed
information can get lost, though, because a later call to malloc might
reuse that space. */
#define MAGIC_NUMBER 0xEEEEEEEE
/* #define MAGIC_NUMBER 15000 *//* Number of students. :) */
#define MAGIC_NUMBER2 91973 /* For me to know, and you to guess at! */
/* Magic number used to fill out freed memory space, use 0 to try to
make things dereference them badly. Use a large character to
flush out things which check if 0 and then use it anyways, though
they shouldn't (since it was freed). */
#define MAGIC_FREE_NUMBER 0xCC
union debug_malloc_info *info_head = 0;
static int align_size(int size)
{
if (!(size % ALIGN_BYTES)) return size;
else return size + ALIGN_BYTES - (size % ALIGN_BYTES);
}
/* Verify that the pointer we've just recieved is one generated by an
earlier call to malloc. This is done by checking for some magic
numbers that have been stored just in front of the pointer the user has. */
static void check_magic(const void *cp, const char *file, const int line)
{
union debug_malloc_info *info, *info2;
if (!cp) {
log_status("REALLOC/FREE: null pointer at %d in %s.",
line, file);
panic("Bad realloc/free.");
/* NEVER REACHED */
}
info = (union debug_malloc_info *) cp;
info--;
if ((info->info.magic1 == MAGIC_NUMBER2) || (info->info.magic2 == MAGIC_NUMBER2)) {
log_status("REALLOC/FREE: already freed pointer at %d in %s. Originally freed at %d in %s.", line, file, info->info.line, info->info.file);
panic("Bad realloc/free.");
/* NEVER REACHED */
}
if ((info->info.magic1 != MAGIC_NUMBER) || (info->info.magic2 != MAGIC_NUMBER)) {
log_status("REALLOC/FREE: illegal pointer at %d in %s.",
line, file);
panic("Bad realloc/free.");
/* NEVER REACHED */
}
#ifdef DEBUG_MALLOCS_RCHECK
info2 = (union debug_malloc_info *) (((char *) cp) + info->info.size);
if ((info2->info.magic1 != info->info.magic1) ||
(info2->info.magic2 != info->info.magic2) ||
(info2->info.file != info->info.file) ||
(info2->info.line != info->info.line) ||
(info2->info.size != info->info.size)) {
log_status("REALLOC/FREE: range check failed at %d in %s.",
line, file);
log_status("REALLOC/FREE: (could be invalid) malloced at %d in %s.",
info->info.line, info->info.file);
panic("Bad realloc/free.");
/* NEVER REACHED */
}
#endif /* DEBUG_MALLOCS_RCHECK */
}
/* This stores the appropriate magic numbers for a new memory allocation
and chains this node into a linked list of all of the malloced nodes. */
static void set_magic(void *cp, const char *file, const int line, const size_t size)
{
union debug_malloc_info *info, *info2;
info = (union debug_malloc_info *) cp;
info->info.magic1 = MAGIC_NUMBER;
info->info.file = file;
info->info.line = line;
info->info.magic2 = MAGIC_NUMBER;
#ifdef DEBUG_MALLOCS_TRACE
/* Chain it into the linked list. */
info->info.next = info_head;
info->info.prev = 0;
if (info_head) info_head->info.prev = info;
info_head = info;
#endif /* DEBUG_MALLOCS_TRACE */
#ifdef DEBUG_MALLOCS_RCHECK
info->info.size = size;
info2 = (union debug_malloc_info *) (((char *) cp) + size);
info2++;
info2->info.magic1 = info2->info.magic2 = MAGIC_NUMBER;
info2->info.file = file;
info2->info.line = line;
info2->info.size = size;
#endif /* DEBUG_MALLOCS_RCHECK */
}
static void reset_size(void *cp, const size_t size)
{
union debug_malloc_info *info, *info2;
info = (union debug_malloc_info *) cp;
info--;
/* Fix up the linked list,in case of a moved realloc. */
if (!info->info.prev) {
/* It was info_head. */
info_head = info;
}
if (info->info.next) info->info.next->info.prev = info;
if (info->info.prev) info->info.prev->info.next = info;
#ifdef DEBUG_MALLOCS_RCHECK
info2 = (union debug_malloc_info *) (((char *) cp) + size);
info->info.size = size;
info2->info.magic1 = info2->info.magic2 = MAGIC_NUMBER;
info2->info.file = info->info.file;
info2->info.line = info->info.line;
info2->info.size = size;
#endif /* DEBUG_MALLOCS_RCHECK */
}
/* This routine removes the node from the list of malloced memories,
and resets the magic numbers so we can hopefully detect illegal frees
to the same address again. It also zeroes out the memory that was
allocated, hopeing to flush out problems. */
static void clear_node(void *cp, const char *file, const int line)
{
union debug_malloc_info *info, *info2;
char *ptr;
info = (union debug_malloc_info *) cp;
info--;
#ifdef DEBUG_MALLOCS_TRACE
/* Remove it from the linked list. */
if (info->info.next) info->info.next->info.prev = info->info.prev;
if (info == info_head) {
info_head = info->info.next;
} else {
info->info.prev->info.next = info->info.next;
}
info->info.next = 0;
info->info.prev = 0;
#endif /* DEBUG_MALLOCS_TRACE */
/* Set magic so we know if they try to free it again! */
info->info.magic1 = MAGIC_NUMBER2;
info->info.magic2 = MAGIC_NUMBER2;
info->info.file = file;
info->info.line = line;
#ifdef DEBUG_MALLOCS_RCHECK
info2 = (union debug_malloc_info *) (((char *) cp) + info->info.size);
info2->info.magic1 = info2->info.magic2 = MAGIC_NUMBER2;
info2->info.file = file;
info2->info.line = line;
for (ptr = (char *) cp; ptr != (char *) info2; ptr++)
*ptr = MAGIC_FREE_NUMBER;
#endif /* DEBUG_MALLOCS_RCHECK */
}
void *debug_realloc(void *cp, size_t size, const char *file, const int line)
{
union debug_malloc_info *result, *arg;
check_magic(cp, file, line);
size = align_size(size);
if (size == 0) {
log_status("REALLOC: tried to realloc(0) at %d in %s.",
line, file);
debug_free(cp, file, line); /* Know it's not null from check_magic. */
return NULL;
}
/* realloc will copy over the header struct for us. */
arg = (union debug_malloc_info *) cp;
arg--;
#ifndef DEBUG_MALLOCS_RCHECK
result = realloc(arg, size + sizeof(union debug_malloc_info));
#else
result = realloc(arg, size + 2*sizeof(union debug_malloc_info));
#endif /* DEBUG_MALLOCS_RCHECK */
if (result == NULL) {
log_status("REALLOC: out of memory at %d in %s.",
line, file);
panic("Out of memory.");
/* NEVER REACHED */
}
result++;
reset_size(result, size);
return (void *) result;
}
void *debug_malloc(size_t size, const char *file, const int line)
{
union debug_malloc_info *result;
size = align_size(size);
if (size == 0) {
log_status("MALLOC: tried to malloc(0) at %d in %s.",
line, file);
return NULL;
}
#ifndef DEBUG_MALLOCS_RCHECK
result = malloc(size + sizeof(union debug_malloc_info));
#else
result = malloc(size + 2*sizeof(union debug_malloc_info));
#endif /* DEBUG_MALLOCS_RCHECK */
if (result == NULL) {
log_status("MALLOC: out of memory at %d in %s.",
line, file);
panic("Out of memory.");
/* NEVER REACHED */
}
set_magic(result, file, line, size);
result++;
return (void *) result;
}
void debug_free(void *cp, const char *file, const int line)
{
union debug_malloc_info *arg;
check_magic(cp, file, line);
clear_node(cp, file, line);
arg = (union debug_malloc_info *) cp;
arg--;
#ifndef DEBUG_MALLOCS_WASTE_MEMORY
/* free the memory, unless we have chosen not to free anything,
which is the only guaranteed way to detect multiple frees of the same
data, and will most likely waste memory like mad. */
free((void *) arg);
#endif /* DEBUG_MALLOCS_WASTE_MEMORY */
}
void *debug_calloc(size_t size, const char *file, const int line)
{
void *result;
result = debug_malloc(size, file, line);
if (result)
memset(result, '\0', size);
return result;
}
void debug_mstats(const char *s)
{
#ifdef DEBUG_MALLOCS_TRACE
union debug_malloc_info *temp, *ptr;
int counter = 0;
fprintf(stderr, "(%s): Unfreed resources allocated by:\n", s);
for (temp = info_head; temp; temp = temp->info.next) {
ptr = temp + 1;
check_magic(ptr, temp->info.file, temp->info.line);
if (temp->info.file)
fprintf(stderr, "%s %d\n", temp->info.file, temp->info.line);
else
fprintf(stderr, "NULL file field\n");
if (counter++ > 1000) {
fprintf(stderr, "possible loop in malloc list, stopping.\n");
break;
}
}
fprintf(stderr, "(%s): End of list.\n", s);
#endif /* DEBUG_MALLOCS_TRACE */
}
#endif /* DEBUG_MALLOCS */