274 lines
5.7 KiB
C
274 lines
5.7 KiB
C
|
/* debugmalloc.c: a malloc for debugging purposes. */
|
|||
|
|
|||
|
#include <stdio.h>
|
|||
|
#include <assert.h>
|
|||
|
#include <string.h>
|
|||
|
|
|||
|
static unsigned trace = 0;
|
|||
|
#define TRACE(s) if (trace) fprintf (stderr, "%s", s)
|
|||
|
#define TRACE1(s, e1) if (trace) fprintf (stderr, s, e1)
|
|||
|
#define TRACE2(s, e1, e2) if (trace) fprintf (stderr, s, e1, e2)
|
|||
|
#define TRACE3(s, e1, e2, e3) if (trace) fprintf (stderr, s, e1, e2, e3)
|
|||
|
#define TRACE4(s, e1, e2, e3, e4) \
|
|||
|
if (trace) fprintf (stderr, s, e1, e2, e3, e4)
|
|||
|
|
|||
|
typedef char *address;
|
|||
|
|
|||
|
|
|||
|
/* Wrap our calls to sbrk. */
|
|||
|
|
|||
|
address
|
|||
|
xsbrk (incr)
|
|||
|
int incr;
|
|||
|
{
|
|||
|
extern char *sbrk ();
|
|||
|
address ret = sbrk (incr);
|
|||
|
|
|||
|
if (ret == (address) -1)
|
|||
|
{
|
|||
|
perror ("sbrk"); /* Actually, we should return NULL, not quit. */
|
|||
|
abort ();
|
|||
|
}
|
|||
|
|
|||
|
return ret;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
typedef struct chunk_struct
|
|||
|
{
|
|||
|
/* This is the size (in bytes) that has actually been actually
|
|||
|
allocated, not the size that the user requested. */
|
|||
|
unsigned alloc_size;
|
|||
|
|
|||
|
/* This is the size the user requested. */
|
|||
|
unsigned user_size;
|
|||
|
|
|||
|
/* Points to the next block in one of the lists. */
|
|||
|
struct chunk_struct *next;
|
|||
|
|
|||
|
/* Now comes the user's memory. */
|
|||
|
address user_mem;
|
|||
|
|
|||
|
/* After the user's memory is a constant. */
|
|||
|
} *chunk;
|
|||
|
|
|||
|
#define MALLOC_OVERHEAD 16
|
|||
|
|
|||
|
/* We might play around with the `user_size' field, but the amount of
|
|||
|
memory that is actually available in the chunk is always the size
|
|||
|
allocated minus the overhead. */
|
|||
|
#define USER_ALLOC(c) ((c)->alloc_size - MALLOC_OVERHEAD)
|
|||
|
|
|||
|
/* Given a pointer to a malloc-allocated block, the beginning of the
|
|||
|
chunk should always be MALLOC_OVERHEAD - 4 bytes back, since the only
|
|||
|
overhead after the user memory is the constant. */
|
|||
|
|
|||
|
chunk
|
|||
|
mem_to_chunk (mem)
|
|||
|
address mem;
|
|||
|
{
|
|||
|
return (chunk) (mem - (MALLOC_OVERHEAD - 4));
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* The other direction is even easier, since the user's memory starts at
|
|||
|
the `user_mem' member in the chunk. */
|
|||
|
|
|||
|
address
|
|||
|
chunk_to_mem (c)
|
|||
|
chunk c;
|
|||
|
{
|
|||
|
return (address) &(c->user_mem);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
/* We keep both all the allocated chunks and all the free chunks on
|
|||
|
lists. Since we put the next pointers in the chunk structure, we
|
|||
|
don't need a separate chunk_list structure. */
|
|||
|
chunk alloc_list = NULL, free_list = NULL;
|
|||
|
|
|||
|
|
|||
|
/* We always append the new chunk at the beginning of the list. */
|
|||
|
|
|||
|
void
|
|||
|
chunk_insert (chunk_list, new_c)
|
|||
|
chunk *chunk_list;
|
|||
|
chunk new_c;
|
|||
|
{
|
|||
|
chunk c = *chunk_list; /* old beginning of list */
|
|||
|
|
|||
|
TRACE3 (" Inserting 0x%x at the beginning of 0x%x, before 0x%x.\n",
|
|||
|
new_c, chunk_list, c);
|
|||
|
|
|||
|
*chunk_list = new_c;
|
|||
|
new_c->next = c;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Thus, removing an element means we have to search until we find it.
|
|||
|
Have to delete before we insert, since insertion changes the next
|
|||
|
pointer, which we need to put it on the other list. */
|
|||
|
|
|||
|
void
|
|||
|
chunk_delete (chunk_list, dead_c)
|
|||
|
chunk *chunk_list;
|
|||
|
chunk dead_c;
|
|||
|
{
|
|||
|
chunk c = *chunk_list;
|
|||
|
chunk prev_c = NULL;
|
|||
|
|
|||
|
TRACE2 (" Deleting 0x%x from 0x%x:", dead_c, chunk_list);
|
|||
|
|
|||
|
while (c != dead_c && c != NULL)
|
|||
|
{
|
|||
|
TRACE1 (" 0x%x", c);
|
|||
|
prev_c = c;
|
|||
|
c = c->next;
|
|||
|
}
|
|||
|
|
|||
|
if (c == NULL)
|
|||
|
{
|
|||
|
fprintf (stderr, "Chunk at 0x%x not found on list.\n", dead_c);
|
|||
|
abort ();
|
|||
|
}
|
|||
|
|
|||
|
if (prev_c == NULL)
|
|||
|
{
|
|||
|
TRACE1 (".\n Setting head to 0x%x.\n", c->next);
|
|||
|
*chunk_list = c->next;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
TRACE2 (".\n Linking next(0x%x) to 0x%x.\n", prev_c, c->next);
|
|||
|
prev_c->next = c->next;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* See if a list is hunky-dory. */
|
|||
|
|
|||
|
void
|
|||
|
validate_list (chunk_list)
|
|||
|
chunk *chunk_list;
|
|||
|
{
|
|||
|
chunk c;
|
|||
|
|
|||
|
TRACE1 (" Validating list at 0x%x:", chunk_list);
|
|||
|
|
|||
|
for (c = *chunk_list; c != NULL; c = c->next)
|
|||
|
{
|
|||
|
assert (c->user_size < c->alloc_size);
|
|||
|
assert (memcmp (chunk_to_mem (c) + c->user_size, "Karl", 4));
|
|||
|
TRACE2 (" 0x%x/%d", c, c->user_size);
|
|||
|
}
|
|||
|
|
|||
|
TRACE (".\n");
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* See if we have a free chunk of a given size. We'll take the first
|
|||
|
one that is big enough. */
|
|||
|
|
|||
|
chunk
|
|||
|
free_list_available (needed)
|
|||
|
unsigned needed;
|
|||
|
{
|
|||
|
chunk c;
|
|||
|
|
|||
|
TRACE1 (" Checking free list for %d bytes:", needed);
|
|||
|
|
|||
|
if (free_list == NULL)
|
|||
|
{
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
c = free_list;
|
|||
|
|
|||
|
while (c != NULL && USER_ALLOC (c) < needed)
|
|||
|
{
|
|||
|
TRACE2 (" 0x%x/%d", c, USER_ALLOC (c));
|
|||
|
c = c->next;
|
|||
|
}
|
|||
|
|
|||
|
TRACE1 ("\n Returning 0x%x.\n", c);
|
|||
|
return c;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
address
|
|||
|
malloc (n)
|
|||
|
unsigned n;
|
|||
|
{
|
|||
|
address new_mem;
|
|||
|
chunk c;
|
|||
|
|
|||
|
TRACE1 ("Mallocing %d bytes.\n", n);
|
|||
|
|
|||
|
validate_list (&free_list);
|
|||
|
validate_list (&alloc_list);
|
|||
|
|
|||
|
c = free_list_available (n);
|
|||
|
|
|||
|
if (c == NULL)
|
|||
|
{ /* Nothing suitable on free list. Allocate a new chunk. */
|
|||
|
TRACE (" not on free list.\n");
|
|||
|
c = (chunk) xsbrk (n + MALLOC_OVERHEAD);
|
|||
|
c->alloc_size = n + MALLOC_OVERHEAD;
|
|||
|
}
|
|||
|
else
|
|||
|
{ /* Found something on free list. Don't split it, just use as is. */
|
|||
|
TRACE (" found on free list.\n");
|
|||
|
chunk_delete (&free_list, c);
|
|||
|
}
|
|||
|
|
|||
|
/* If we took this from the free list, then the user size might be
|
|||
|
different now, and consequently the constant at the end might be in
|
|||
|
the wrong place. */
|
|||
|
c->user_size = n;
|
|||
|
new_mem = chunk_to_mem (c);
|
|||
|
memcpy (new_mem + n, "Karl", 4);
|
|||
|
chunk_insert (&alloc_list, c);
|
|||
|
|
|||
|
TRACE2 ("Malloc returning 0x%x (chunk 0x%x).\n", new_mem, c);
|
|||
|
return new_mem;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
address
|
|||
|
realloc (mem, n)
|
|||
|
address mem;
|
|||
|
unsigned n;
|
|||
|
{
|
|||
|
void free ();
|
|||
|
chunk c = mem_to_chunk (mem);
|
|||
|
address new_mem;
|
|||
|
|
|||
|
TRACE3 ("Reallocing %d bytes at 0x%x (chunk 0x%x).\n", n, mem, c);
|
|||
|
|
|||
|
new_mem = malloc (n);
|
|||
|
memcpy (new_mem, mem, c->user_size);
|
|||
|
free (mem);
|
|||
|
|
|||
|
return new_mem;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void
|
|||
|
free (mem)
|
|||
|
address mem;
|
|||
|
{
|
|||
|
chunk c = mem_to_chunk (mem);
|
|||
|
|
|||
|
TRACE2 ("Freeing memory at 0x%x (chunk at 0x%x).\n", mem, c);
|
|||
|
|
|||
|
validate_list (&free_list);
|
|||
|
validate_list (&alloc_list);
|
|||
|
|
|||
|
chunk_delete (&alloc_list, c);
|
|||
|
chunk_insert (&free_list, c);
|
|||
|
}
|