/* Copyright (c) 2002, 2004, E. Biagioni. * This code may be redistributed and modified subject to the terms * of the X license, http://www.x.org/Downloads_terms.html. * In particular, there is no warranty whatsoever. */ /* mem.c: memory allocation and deallocation routines, * with debugging options. */ /* to use, call debug_malloc and debug_free instead of malloc and free. * in debug_malloc, the ID specifies the call point, e.g. use 1, 2, 3, etc * to remind yourself which part of the code is calling this malloc. * ID must be 65535 or less, since it is stored in 16 bits. The ID is * only used if DEBUGMALLOCPLUS is defined. */ /* most errors simply cause a segmentation fault, which can generally * be examined with your favorite debugger. */ /* these two/three prototypes are needed for programs to use the debugging * functionality. Copy them to your .h or .c file: */ void * debug_malloc (int bytes, int id); void debug_free (void * data); /* the following is needed if you want your program to print the allocations, * for example to look for memory leaks. */ void debug_prtallocmem (); /* end prototypes */ #include /* define this for more checking */ #define DEBUGMEMALLOCPLUS /* define this to crash on the first bad magic block detected */ #define DEBUGCRASHONBADMAGIC /* maximum size that can be allocated -- modify as needed */ #define ALLOCMEMSIZE 1000000 static char malloc_buffer [ALLOCMEMSIZE]; static int malloc_next = 0; static int malloc_sequence = 0; struct mymalloc_header { int mlchdr_magic; /* set to a value so we can tell if it is overwritten. */ int mlchdr_count; /* number of bytes overall in block */ #ifdef DEBUGMEMALLOCPLUS int mlchdr_allocator; /* 16-bit ID and 16-bit allocation count */ #endif /* DEBUGMEMALLOCPLUS */ }; struct mymalloc_trailer { /* should match header */ #ifdef DEBUGMEMALLOCPLUS int mlctrl_allocator; #endif /* DEBUGMEMALLOCPLUS */ int mlctrl_count; int mlctrl_magic; }; #define HEADERSIZE (sizeof (struct mymalloc_header)) #define TRAILERSIZE (sizeof (struct mymalloc_trailer)) /* use to mark part of the header so we can detect corruption */ #define MAGICHEADER_ALLOCATED 0xED0B1A61 #define MAGICHEADER_EMPTY 0x12F4E59E /* this is better than using "exit", since the debugger will tell us the * point of call of segfault_exit(). */ static char * segfaultnull = NULL; static void segfault_exit () { /* dereferencing a null pointer should terminate the program (SIGSEGV) */ printf ("error: %c", *segfaultnull); } /* initialize the data strutures, but only the first time we are called, * when malloc_sequence == 0. */ static void debug_malloc_init () { struct mymalloc_header * header; struct mymalloc_trailer * trailer; if (malloc_sequence == 0) { header = (struct mymalloc_header *) malloc_buffer; trailer = (struct mymalloc_trailer *) (malloc_buffer + ALLOCMEMSIZE - TRAILERSIZE); header->mlchdr_magic = MAGICHEADER_EMPTY; trailer->mlctrl_magic = MAGICHEADER_EMPTY; header->mlchdr_count = ALLOCMEMSIZE; trailer->mlctrl_count = ALLOCMEMSIZE; #ifdef DEBUGMEMALLOCPLUS header->mlchdr_allocator = 0; trailer->mlctrl_allocator = 0; #endif /* DEBUGMEMALLOCPLUS */ malloc_sequence = 1; } } /* print descriptors for each allocated memory block. */ void debug_prtallocmem () { struct mymalloc_header * header = (struct mymalloc_header *) malloc_buffer; struct mymalloc_trailer * trailer; debug_malloc_init (); while (((char *) header) < malloc_buffer + ALLOCMEMSIZE) { if ((header == NULL) || ((((int) header) % 4) != 0)) { printf ("bad header %p\n", header); /* code below will cause a segfault. If that is not desired, break */ } trailer = (struct mymalloc_trailer *) (((char *) header) + header->mlchdr_count - TRAILERSIZE); printf ("%p->%p: ", header, ((int) trailer) + TRAILERSIZE - 1); if (header->mlchdr_magic == MAGICHEADER_ALLOCATED) { printf ("alloc"); } else if (header->mlchdr_magic == MAGICHEADER_EMPTY) { printf ("empty"); } else { printf ("bad magic"); #ifdef DEBUGCRASHONBADMAGIC segfault_exit (); #endif /* DEBUGCRASHONBADMAGIC */ } #ifdef DEBUGCRASHONBADMAGIC if ((trailer == NULL) || ((((int) trailer) % 4) != 0)) { printf ("\nbad trailer %p\n", trailer); segfault_exit (); } if ((trailer->mlctrl_magic != MAGICHEADER_ALLOCATED) && (trailer->mlctrl_magic != MAGICHEADER_EMPTY)) { printf ("\nbad trailer magic %p\n", trailer->mlctrl_magic); segfault_exit (); } #endif /* DEBUGCRASHONBADMAGIC */ printf (" (%p) %d -> %p, %d\n", header->mlchdr_magic, header->mlchdr_count, trailer->mlctrl_magic, trailer->mlctrl_count); #ifdef DEBUGMEMALLOCPLUS if (header->mlchdr_magic != MAGICHEADER_EMPTY) { printf (" allocated by %d, sequence %d (and %d/%d)\n", (header->mlchdr_allocator & 0xffff), (header->mlchdr_allocator >> 16), (trailer->mlctrl_allocator & 0xffff), (trailer->mlctrl_allocator >> 16)); } #endif /* DEBUGMEMALLOCPLUS */ header = (struct mymalloc_header *) (((char *) header) + header->mlchdr_count); } printf ("\n"); } /* call as you would malloc, but use the ID to specify where in the * program this is being called from. */ void * debug_malloc (int bytes, int id) { struct mymalloc_header * header; struct mymalloc_trailer * trailer; int start_point; void * retval; #ifdef DEBUGMEMALLOCPLUS printf ("debug_malloc (%d, %d)\n", bytes, id); #endif /* DEBUGMEMALLOCPLUS */ /* initialize if this is the first time. */ debug_malloc_init (); if (bytes <= 0) { printf ("error: debug_malloc (%d) <= 0\n", bytes); #ifdef DEBUGMEMALLOCPLUS printf ("called from %d\n", id); #endif /* DEBUGMEMALLOCPLUS */ segfault_exit (); } /* align the allocation size to a 4-byte boundary. */ switch (bytes % 4) { case 1: bytes++; case 2: bytes++; case 3: bytes++; default: /* do nothing, but need something here to shut up the compiler */ bytes = bytes; } /* loop through the blocks to find a free one that is big enough. */ /* search starts at malloc_next, so in general we do not always * start the search at the same point. Algorithm is called "first fit", * meaning we select the first block that is at least big enough for * the request. */ /* this search is somewhat inefficient -- a free list might make * this search faster, but then merge would be inefficient. */ start_point = -1; header = (struct mymalloc_header *) (malloc_buffer + malloc_next); while ((header->mlchdr_magic != MAGICHEADER_EMPTY) || (header->mlchdr_count < bytes + HEADERSIZE + TRAILERSIZE)) { /* increment and if necessary wrap around to go back to the beginning. */ int next = (malloc_next + header->mlchdr_count) % ALLOCMEMSIZE; if (start_point == -1) { start_point = malloc_next; } else if (malloc_next == start_point) { printf ("debug_malloc: no memory space left, %d-byte buffer\n", ALLOCMEMSIZE); /* returning NULL is the correct behavior for malloc. */ return NULL; } malloc_next = next; header = (struct mymalloc_header *) (malloc_buffer + malloc_next); } /* if we get here, we have found an empty block with at least bytes + HEADERSIZE + TRAILERSIZE bytes */ trailer = (struct mymalloc_trailer *) (malloc_buffer + malloc_next + header->mlchdr_count - TRAILERSIZE); #ifdef DEBUGMEMALLOCPLUS /* check to make sure all the "magic" field have not been modified * by a programming error... */ if ((header->mlchdr_magic != MAGICHEADER_EMPTY) || (trailer->mlctrl_magic != MAGICHEADER_EMPTY) || (header->mlchdr_count != trailer->mlctrl_count)) { printf ("inconsistency in the allocation memory, %p %p\n", header, trailer); /* if they have, dump all the memory descriptors and quit. */ debug_prtallocmem (); segfault_exit (); } #endif /* DEBUGMEMALLOCPLUS */ /* found, allocate! */ header->mlchdr_magic = MAGICHEADER_ALLOCATED; retval = malloc_buffer + malloc_next + HEADERSIZE; #ifdef DEBUGMEMALLOCPLUS /* keep track of the sequence number, but do not let it wrap around. */ if (malloc_sequence < 0x7fffffff) malloc_sequence++; /* put the sequence number and ID into the header, 16 bits each. */ header->mlchdr_allocator = (id & 0xffff) | (malloc_sequence << 16); #endif /* DEBUGMEMALLOCPLUS */ if (header->mlchdr_count >= 2 * (HEADERSIZE + TRAILERSIZE) + bytes + 4) { /* block is big enough, split it into two */ struct mymalloc_header * new_header; struct mymalloc_trailer * new_trailer; int shortsize = HEADERSIZE + bytes; int size = shortsize + TRAILERSIZE; new_trailer = (struct mymalloc_trailer *) (((char *) header) + shortsize); new_header = (struct mymalloc_header *) (((char *) new_trailer) + TRAILERSIZE); new_trailer->mlctrl_magic = MAGICHEADER_ALLOCATED; new_header->mlchdr_magic = MAGICHEADER_EMPTY; new_trailer->mlctrl_count = size; new_header->mlchdr_count = header->mlchdr_count - size; #ifdef DEBUGMEMALLOCPLUS new_trailer->mlctrl_allocator = (id & 0xffff) | (malloc_sequence << 16); new_header->mlchdr_allocator = trailer->mlctrl_allocator; #endif /* DEBUGMEMALLOCPLUS */ trailer->mlctrl_count = header->mlchdr_count - size; header->mlchdr_count = size; } else { /* use the entire block, since it cannot be used for anything else */ trailer->mlctrl_magic = MAGICHEADER_ALLOCATED; #ifdef DEBUGMEMALLOCPLUS trailer->mlctrl_allocator = (id & 0xffff) | (malloc_sequence << 16); #endif /* DEBUGMEMALLOCPLUS */ } malloc_next = ((((char *) header) - malloc_buffer)+ header->mlchdr_count) % ALLOCMEMSIZE; /* malloc always zeroes the memory returned. */ bzero (retval, bytes); #ifdef DEBUGMEMALLOCPLUS printf ("debug_malloc (%d, %d) => %x\n", bytes, id, retval); #endif /* DEBUGMEMALLOCPLUS */ return retval; #if 0 /* if we do not want to run tests */ char * result; /* printf ("malloc (%d)\n", bytes); */ result = malloc (bytes); /* printf ("malloc (%d) = %p\n", bytes, result); */ return result; #endif } /* call as you would free. * You cannot call free on data created by debug_malloc, * nor call debug_free on data created by malloc. */ void debug_free (void * data) { char * start; struct mymalloc_header * header; struct mymalloc_trailer * trailer; struct mymalloc_header * nextheader; struct mymalloc_trailer * prevtrailer; int bytes; #ifdef DEBUGMEMALLOCPLUS printf ("debug_free (%x)\n", data); #endif /* DEBUGMEMALLOCPLUS */ /* sanity checking */ if (malloc_sequence == 0) { printf ("debug_free: memory allocation space not initialized\n"); segfault_exit (); } if (data == NULL) { printf ("freeing null pointer!\n"); debug_prtallocmem (); segfault_exit (); } /* verify that the data being freed is the result of a prior allocation, * and has not already been freed. */ start = ((char *) data) - HEADERSIZE; header = (struct mymalloc_header *) start; /* pointer less than the malloc range. */ if (start < malloc_buffer) { printf ("freeing pointer not in the allocated range, %p < %p!\n", start, malloc_buffer); debug_prtallocmem (); segfault_exit (); } /* pointer larger than the malloc range. */ if (start + header->mlchdr_count >= malloc_buffer + ALLOCMEMSIZE) { printf ("freeing pointer not in the allocated range, %p + %x > %p + %x\n", start, header->mlchdr_count, malloc_buffer, ALLOCMEMSIZE); debug_prtallocmem (); segfault_exit (); } /* in the malloc range, but freed before (or not the exact pointer * that was returned by debug_malloc). */ if (header->mlchdr_magic != MAGICHEADER_ALLOCATED) { printf ("freeing memory block %p that was already free!\n", header); #ifdef DEBUGMEMALLOCPLUS printf ("allocated by %d\n", header->mlchdr_allocator & 0xffff); #endif /* DEBUGMEMALLOCPLUS */ debug_prtallocmem (); segfault_exit (); } /* be paranoid! ;-) check that the size allocated was positive. * i.e. that nobody has scribbled over our memory. */ if (header->mlchdr_count <= 0) { printf ("freeing memory block of negative or zero size %d!\n", header->mlchdr_count); #ifdef DEBUGMEMALLOCPLUS printf ("allocated by %d\n", header->mlchdr_allocator & 0xffff); #endif /* DEBUGMEMALLOCPLUS */ debug_prtallocmem (); segfault_exit (); } /* start of block is in range, but end is not */ if (start + header->mlchdr_count > malloc_buffer + ALLOCMEMSIZE ) { printf ("freeing memory block of size %p start %p exceeding %p + %x\n", header->mlchdr_count, header, malloc_buffer, ALLOCMEMSIZE); #ifdef DEBUGMEMALLOCPLUS printf ("allocated by %d\n", header->mlchdr_allocator & 0xffff); #endif /* DEBUGMEMALLOCPLUS */ debug_prtallocmem (); segfault_exit (); } trailer = (struct mymalloc_trailer *) (start + header->mlchdr_count - TRAILERSIZE); #ifdef DEBUGMEMALLOCPLUS /* check the magic and matching fields in the trailer, again fishing * for errors in the main code. */ if ((trailer->mlctrl_magic != MAGICHEADER_ALLOCATED) || (trailer->mlctrl_count != header->mlchdr_count) || (trailer->mlctrl_allocator != header->mlchdr_allocator)) { printf ("trailer does not match header in block %p allocated by %d\n", header, header->mlchdr_allocator & 0xffff); printf ("%x/%x, %d/%d, %x/%x\n", trailer->mlctrl_magic, MAGICHEADER_ALLOCATED, trailer->mlctrl_count, header->mlchdr_count, trailer->mlctrl_allocator, header->mlchdr_allocator); debug_prtallocmem (); segfault_exit (); } #endif /* DEBUGMEMALLOCPLUS */ /* now free this space */ header->mlchdr_magic = MAGICHEADER_EMPTY; trailer->mlctrl_magic = MAGICHEADER_EMPTY; /* clear the data, so any use is likely to get an error -- Ray Strode's suggestion, 2004 (and my bugs if any). */ memset (data, 0, ((char *) trailer) - ((char *) data)); /* try to merge with the next space, if it is free */ /* this would be harder to do if we had a free list... */ if (start + header->mlchdr_count < malloc_buffer + ALLOCMEMSIZE) { nextheader = (struct mymalloc_header *) (start + header->mlchdr_count); if (nextheader->mlchdr_magic == MAGICHEADER_EMPTY) { /* merge */ struct mymalloc_trailer * nexttrailer = (struct mymalloc_trailer *) (((char *) nextheader) + nextheader->mlchdr_count - TRAILERSIZE); int new_size = header->mlchdr_count + nextheader->mlchdr_count; nexttrailer->mlctrl_count = new_size; header->mlchdr_count = new_size; #ifdef DEBUGMEMALLOCPLUS /* kind of bogus, because there is no sensible value of allocator * for the combined block. We randomly select the allocator for * the first block. */ nexttrailer->mlctrl_allocator += header->mlchdr_allocator; #endif /* DEBUGMEMALLOCPLUS */ trailer = nexttrailer; } #ifdef DEBUGMEMALLOCPLUS /* just checking that the next header has not been corrupted. */ else if (nextheader->mlchdr_magic != MAGICHEADER_ALLOCATED) { /* error */ printf ("error trying to merge with next header, magic %x at %p\n", nextheader->mlchdr_magic, nextheader); debug_prtallocmem (); segfault_exit (); } #endif /* DEBUGMEMALLOCPLUS */ } /* try to merge with the previous space, if it is free */ /* probably could be turned into a subroutine and shared with * the previous block. */ if (start > malloc_buffer) { prevtrailer = (struct mymalloc_trailer *) (start - TRAILERSIZE); if (prevtrailer->mlctrl_magic == MAGICHEADER_EMPTY) { /* merge */ struct mymalloc_header * prevheader = (struct mymalloc_header *) (start - prevtrailer->mlctrl_count); int new_size = header->mlchdr_count + prevtrailer->mlctrl_count; prevheader->mlchdr_count = new_size; trailer->mlctrl_count = new_size; #ifdef DEBUGMEMALLOCPLUS /* kind of bogus, but at least they will match */ trailer->mlctrl_allocator += prevheader->mlchdr_allocator; #endif /* DEBUGMEMALLOCPLUS */ header = prevheader; } #ifdef DEBUGMEMALLOCPLUS else if (prevtrailer->mlctrl_magic != MAGICHEADER_ALLOCATED) { /* error */ printf ("error trying to merge with previous header, magic %x at %p\n", prevtrailer->mlctrl_magic, prevtrailer); debug_prtallocmem (); segfault_exit (); } #endif /* DEBUGMEMALLOCPLUS */ } /* make sure the search hint still points to a valid header */ if ((malloc_buffer + malloc_next > start) && (malloc_buffer + malloc_next <= ((char *) trailer))) { malloc_next = ((char *) header) - malloc_buffer; } #if 0 /* if we do not want to run tests */ /* printf ("freeing %p\n", data); */ free (data); #endif }