/* memory.c * This file is a part of decomp - a disassembler. Here are the memory * allocation and deallocation routines. * * Copyright (C) 2001 Jonathan duSaint * * The memory allocation is fairly routine. The justification for not * using malloc as is is primarily a speed issue. Secondary is the * fact that this organization allows sloppy memory accounting in the * main program. When disassembling a file, there will be a lot of * allocations and deallocations of small, similarly-sized blocks of * memory (i.e. INSN). By not releasing the memory each time a section * read is completed, it allows the same block to be reused for the * next section. * The mechanism of allocation is that large, CHUNK_SIZE sized blocks * of memory are requested from the system. They are kept track of * by overlaying the first sizeof (sys_block) bytes with a * struct sys_block and having the next pointer point to the next * block, or being NULL. That way, when xfree is called, the linked * list, kept in the variable memory, is walked and each node is free'd. * Each block is then carved up into smaller blocks. Each of these * smaller blocks has the first sizeof (mem_header) bytes overlaid * by a struct mem_header. Then, if one of the smaller blocks is needed, * the struct mem_header is placed on the alloc list. If, relative * to the size of the memory request, it is fairly large, it is * partitioned into two blocks. One is placed on the alloc list and * returned to the caller; the other is placed on the free list. Then, * as space is needed, adjacent free blocks are merged to create larger * blocks. If the global variable memory_trace is non-zero, then * messages are printed to stdout as important/interesting events * take place. Also provided is the function void mem_trace (void), * which prints the alloc list, the free list, and the sys list. * * Started around 15 October 2001. */ #include #include #include #include "decomp.h" /* the default amount of memory to request from the system each time */ #define DEFAULT_CHUNK_SIZE 2048 /* the minimum size of housekeeping info in a sys block */ #define ALLOC_ADJUST (sizeof (struct sys_block) + sizeof (struct mem_header)) /* the smallest amount which will be returned */ #define MIN_ALLOC_AMOUNT 8 /* the largest amount that can be handled at one time */ #define MAX_ALLOC_AMOUNT (chunk_size - ALLOC_ADJUST) /* header for a block of memory - this is what is referenced on both the alloc and free lists */ struct mem_header { /* size of the chunk, not including the header */ unsigned long size; struct mem_header *next; } __attribute__ ((packed)); /* header for the block returned by a call to malloc(3) */ struct sys_block { struct sys_block *next; } __attribute__ ((packed)); /* the free list */ struct mem_header *free_list = NULL; /* the alloc list */ struct mem_header *alloc_list = NULL; /* the list of malloc'ed blocks */ struct sys_block *memory; /* info used for keeping stats */ /* the total memory alloc'ed from the OS */ unsigned long total_alloced; /* the number of calls to xmalloc */ unsigned long xmalloc_calls; /* the number of bytes returned by xmalloc */ unsigned long xmalloc_bytes; /* the number of bytes free'd by xfree */ unsigned long xfree_bytes; /* the allocation size for memory chunks */ unsigned long chunk_size; /* whether or not to print status messages */ volatile int memory_trace = 0; /* whether or not memory is fragmented */ int free_list_needs_compacting = 0; /* forward declaration needed for mem_trace */ void compact_free_list (void); /* mem_trace * Called in case something bad has happened. Prints the contents * of the alloc list, the free list, and the sys list. */ void mem_trace (void) { struct mem_header *tmp; struct sys_block *sys; if (free_list_needs_compacting) compact_free_list (); fprintf (stderr, "alloc list:\n"); tmp = alloc_list; while (tmp != NULL) { fprintf (stderr, " %p->(%p, %ld)\n", tmp, tmp->next, tmp->size); tmp = tmp->next; } fprintf (stderr, "free list (%d):\n", free_list_needs_compacting); tmp = free_list; while (tmp != NULL) { fprintf (stderr, " %p->(%p, %ld)\n", tmp, tmp->next, tmp->size); tmp = tmp->next; } sys = memory; fprintf (stderr, "system memory:\n"); while (sys != NULL) { fprintf (stderr, " %p - %p ->(%p)\n", sys, sys + chunk_size, sys->next); sys = sys->next; } } /* free_list_insert_sorted * Insert NEW onto the free list, sorted numerically by address. */ void free_list_insert_sorted (struct mem_header *new) { struct mem_header *free_tmp = free_list; /* check for empty list */ if (free_list == NULL) { free_list = new; free_list->next = NULL; return; } /* if NEW is the lowest address */ if (new < free_tmp) { new->next = free_tmp; free_list = new; return; } /* general case - find the insertion point */ while (free_tmp->next != NULL) { if (new < free_tmp->next) break; free_tmp = free_tmp->next; } new->next = free_tmp->next; free_tmp->next = new; } /* compact_free_list * Defragment memory by merging all adjacent free blocks. */ void compact_free_list (void) { struct mem_header *free_tmp = free_list; /* search through the free list for adjacent blocks - if two are adjacent, then merge them to create a larger block */ while (free_tmp != NULL) { if (free_tmp->next == NULL) break; /* at the end of the list */ /* if two free blocks are adjacent */ if ((void *)free_tmp + free_tmp->size + sizeof (struct mem_header) == (void *)free_tmp->next) { /* merge them */ free_tmp->size += free_tmp->next->size + sizeof (struct mem_header); free_tmp->next = free_tmp->next->next; continue; /* try again with this same block */ } free_tmp = free_tmp->next; } free_list_needs_compacting = 0; } /* safe_malloc * A wrapper around malloc which never returns NULL. */ void * safe_malloc (size_t amount) { void *mem; mem = malloc (amount); if (mem == NULL) PANIC; /* Houston, we have a problem */ /* keep stats */ total_alloced += amount; return (mem); } /* mem_init * Called when xmalloc is first invoked. Sets up the necessary * data structures. */ void mem_init (void) { /* set up stats */ total_alloced = 0; xmalloc_calls = 0; xmalloc_bytes = 0; xfree_bytes = 0; /* set up the system list */ memory = safe_malloc (chunk_size); memory->next = NULL; /* set up the free list */ free_list = (void *)memory + sizeof (struct sys_block); free_list->size = chunk_size - sizeof (struct sys_block) - sizeof (struct mem_header); free_list->next = NULL; if (memory_trace) printf ("=== alloc'ed block (%p) ===\n", memory); } /* xmalloc * Allocate AMOUNT bytes of memory and return it with all of * its bytes set to 0x00. */ void * xmalloc (size_t amount) { struct mem_header *free_tmp, *last, *ret_block; struct sys_block *sys_temp; chunk_size = DEFAULT_CHUNK_SIZE; /* if more than the default chunk size was requested */ if (amount > MAX_ALLOC_AMOUNT) chunk_size = amount + ALLOC_ADJUST; if (free_list == NULL && alloc_list == NULL) mem_init (); /* don't allocate less than MIN_ALLOC_AMOUNT */ if (amount < MIN_ALLOC_AMOUNT) amount = MIN_ALLOC_AMOUNT; if (memory_trace) printf ("=== %d bytes requested ===\n", amount); /* keep stats */ xmalloc_calls++; xmalloc_bytes += amount; /* loop until a block is found */ while (1) { /* first walk the free list, looking for the correct size */ last = NULL; free_tmp = free_list; while (free_tmp != NULL) { if (free_tmp->size >= amount) goto GOT_FREE_BLOCK; last = free_tmp; free_tmp = free_tmp->next; } /* didn't find a free block of the right size - try compacting the free list */ if (free_list_needs_compacting) { compact_free_list (); continue; } /* if we got here, then we need to allocate a new chunk */ /* find the last chunk */ sys_temp = memory; while (sys_temp->next != NULL) sys_temp = sys_temp->next; /* alloc a new chunk and save its ptr */ sys_temp->next = safe_malloc (chunk_size); sys_temp->next->next = NULL; if (memory_trace) printf ("=== alloc'ed block (%p) ===\n", sys_temp->next); /* create the new free block */ free_tmp = (void *)sys_temp->next + sizeof (struct sys_block); free_tmp->next = NULL; free_tmp->size = chunk_size - ALLOC_ADJUST; /* put that bad boy in the free list */ free_list_insert_sorted (free_tmp); /* with this new large chunk, try again */ } GOT_FREE_BLOCK: /* remove the block from the free list */ if (last == NULL) free_list = free_tmp->next; else last->next = free_tmp->next; ret_block = free_tmp; /* shrink the block, if possible */ if (ret_block->size > amount + sizeof (struct mem_header) + MIN_ALLOC_AMOUNT) { struct mem_header *new = (void *)ret_block + sizeof (struct mem_header) + amount; new->size = ret_block->size - sizeof (struct mem_header) - amount; new->next = NULL; ret_block->size = amount; free_list_insert_sorted (new); } /* put it on the alloc list */ ret_block->next = alloc_list; alloc_list = ret_block; /* and (finally) return the memory */ memset ((void *)ret_block + sizeof (struct mem_header), 0, amount); return ((void *)ret_block + sizeof (struct mem_header)); } /* xfree * Add FREEMEM to the free list. */ void xfree (void *freemem) { struct mem_header *mem; struct mem_header *tmp = alloc_list, *last = NULL; if (freemem == NULL) return; mem = freemem - sizeof (struct mem_header); /* find FREEMEM on the alloc list */ while (tmp != NULL) { if (tmp == mem) break; last = tmp; tmp = tmp->next; } /* sanity check */ if (tmp == NULL) { fprintf (stderr, "tried to free block %p (%p->(%p, %ld)) not on alloc list\n", freemem, mem, mem->next, mem->size); if (memory_trace) mem_trace (); exit (EXIT_FAILURE); } /* keep stats */ xfree_bytes += tmp->size; if (memory_trace) printf ("=== %ld byte block freed ===\n", tmp->size); /* remove from the alloc list */ if (last == NULL) alloc_list = tmp->next; else last->next = tmp->next; /* put it on the free list */ free_list_insert_sorted (mem); free_list_needs_compacting = 1; } /* free_all * Release all memory back to the OS. */ void free_all (void) { struct sys_block *temp; do { if (memory_trace) printf ("=== freeing %p ===\n", memory); temp = memory->next; free (memory); memory = temp; } while (memory != NULL); if (memory_trace) printf ("\n*** Stats ***\n" " %ld total bytes alloc'ed from the system\n" " %ld calls to xmalloc\n" " %ld total bytes alloc'ed\n" " %ld bytes freed\n", total_alloced, xmalloc_calls, xmalloc_bytes, xfree_bytes); }