/**************************************************************************
*
* Copyright (c) 2013 Alcatel-Lucent
*
* Alcatel Lucent licenses this file to You under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. A copy of the License is contained the
* file LICENSE at the top level of this repository.
* You may also obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
**************************************************************************
*
* malloc():
*
* A simple memory allocator useful for embedded systems to provide
* some level of debug, plus the ability to increase heap space into
* additional memory that is not contiguous with the intial statically
* allocated array of memory. The reason for supporting the ability to
* allocate from 2 distinct blocks of memory is so that the monitor can
* be built with some reasonable amount of heap space allocated to it; then
* if the application is going to also use this malloc, it can do so
* by simply extending the heap. The monitor would not have to be
* specifically built to support the large heap allocation.
*
* The allocator data structures are part of the memory space used for
* the allocation. To test for memory overruns (using memory after the
* end of the allocated space) or underruns (using memory prior to the
* beginning of the allocated space), the data structure starts and ends
* with a fixed tag that is always checked upon entry into malloc()
* or free().
* When the memory is freed, the next and previous block is checked to
* determine if this newly freed block can be combined with a neighboring
* block. This provides some level of defragmentation. Note, at
* this point, that the blocks are only combined if they are found to be
* contiguous. This correctly implies that neighboring free blocks need
* not be within contiguous memory space.
* A function called GetMemory() must be provided as the underlying resource
* of the memory used by the allocator.
*
* NOTE THAT THERE IS ABSOLUTELY NO CONCERN FOR SPEED IN THIS MEMORY
* ALLOCATOR, IT IS SLOW! Every call to malloc/free does a sanity check
* on all allocation structures, so it is fairly good at detecting illegal
* use of the allocated memory.
*
* Original author: Ed Sutter (ed.sutter@alcatel-lucent.com)
*
*/
#include "config.h"
#if INCLUDE_MALLOC
#include "genlib.h"
#include "stddefs.h"
#include "cli.h"
#define FNAMESIZE 32
#define PRETAG 0xDEAD
#define POSTTAG 0xBEEF
#define MHDRSIZE sizeof(struct mhdr)
extern char *GetMemory(int);
extern int GetMemoryLeft(void);
extern int extendHeap(char *,int);
extern void unExtendHeap(void);
extern char *getExtHeapBase(void);
/* mhdr:
The control structure used by the memory allocator.
*/
struct mhdr {
ushort pretag; /* Fixed value used as an overrun sanity
* check for the previous memory block.
*/
int size; /* Size of useable memory block. Size is
* positive if block is free and negative if
* block is not free.
*/
struct mhdr *next; /* Points to next mhdr structure (not
* necessarily in contiguous memory space).
*/
struct mhdr *prev; /* Points to previous mhdr structure (not
* necessarily in contiguous memory space).
*/
ushort posttag; /* Fixed value used as an underrun sanity
* check for this memory block.
*/
#ifdef MALLOC_DEBUG
char fname[FNAMESIZE];
int fline;
#endif
};
/* mcalls, fcalls & mfails:
* Used to keep track of malloc and free calls. Plus keep track
* of the number of times malloc is called, but it returns 0.
*/
static int mcalls, rcalls, fcalls, mfails;
/* mtot, ftot & highwater:
* Keep track of total amount of memory allocated.
*/
static int mtot, ftot, highwater;
/* mquiet:
* If set (by heap -q), then the MALLOC ERROR messages are not
* printed at runtime when an error is detected.
*/
static char mquiet;
/* mtrace:
* If set (by heap -V), then each call to malloc or free will include
* a printed message.
*/
static char mtrace;
/* heapbase:
* Initially zero, this pointer is set to the base of the heap on
* the first call to malloc().
*/
static struct mhdr *heapbase;
static void
heapinit(void)
{
mcalls = rcalls = fcalls = mfails = 0;
mtot = ftot = highwater = 0;
heapbase = (struct mhdr *)GetMemory(ALLOCSIZE);
heapbase->pretag = PRETAG;
heapbase->posttag = POSTTAG;
heapbase->size = ALLOCSIZE - MHDRSIZE;
if(heapbase->size < 0) {
printf("heapinit(): ALLOCSIZE too small!\n");
}
heapbase->next = (struct mhdr *)0;
heapbase->prev = (struct mhdr *)0;
}
/* heapcheck():
* Called with an mhdr pointer (or NULL). This function just steps through
* the heap control structures to make sure there is some level of sanity.
* If the incoming mhdr pointer is non-zero, then it will also verify that
* the pointer is a valid control pointer in the heap.
*/
int
heapcheck(struct mhdr *mp,char *msg)
{
int i, mpok;
register struct mhdr *mptr;
mpok = 0;
if(!heapbase) {
heapinit();
}
mptr = heapbase;
for(i=0; mptr; i++,mptr=mptr->next) {
if(mp == mptr) {
mpok = 1;
}
if((mptr->pretag != PRETAG) || (mptr->posttag != POSTTAG)) {
if(!mquiet) {
printf("\007MALLOC ERROR: heap corrupted at entry %d",i);
if(msg) {
printf(" (%s)",msg);
}
printf("\n");
}
return(-1);
}
}
if((mp) && (!mpok)) {
if(!mquiet) {
printf("\007MALLOC ERROR: 0x%lx (mem @ 0x%lx) invalid heap pointer",
(ulong)mp,(ulong)(mp+1));
if(msg) {
printf(" (%s)",msg);
}
printf("\n");
}
return(-1);
}
return(0);
}
static char *
_malloc(int size)
{
register struct mhdr *mptr, *mptr1;
if(mtrace) {
printf("malloc(%d) = ",size);
}
if(size <= 0) {
if(mtrace) {
printf("0\n");
}
return(0);
}
/* Start by checking sanity of heap. */
if(heapcheck(0,0) < 0) {
if(mtrace) {
printf("00\n");
}
return((char *)0);
}
/* Keep track of number of calls to malloc for debug. */
mcalls++;
/* Make size divisible by 4: */
if(size & 3) {
size += 4;
size &= 0xfffffffc;
}
mptr = (struct mhdr *)heapbase;
while(1) {
/* If request size is equal to the free block size or
* if the free block size is only slightly larger than the
* request size, then just use that free block as is.
* If the request size is at least "MHDRSIZE * 2"
* bytes smaller than free block size, then break
* that block up into 2 smaller chunks with one of the chunks
* being the size of the request and the size of the other chunk
* being whatever is left over.
*/
if(mptr->size >= size) {
if(mptr->size <= (int)(size + (MHDRSIZE * 2))) {
mtot += mptr->size;
if((mtot - ftot) > highwater) {
highwater = (mtot - ftot);
}
mptr->size = -mptr->size;
} else {
mptr1 = (struct mhdr *)((char *)(mptr+1) + size);
mptr1->pretag = PRETAG;
mptr1->posttag = POSTTAG;
mptr1->next = mptr->next;
mptr->next = mptr1;
if(mptr1->next) {
mptr1->next->prev = mptr1;
}
mptr1->prev = mptr;
mptr1->size = (mptr->size - size) - MHDRSIZE;
mptr->size = -size;
mtot += size;
if((mtot - ftot) > highwater) {
highwater = (mtot - ftot);
}
}
if(mtrace) {
printf("0x%lx\n",(long)(mptr+1));
}
return((char *)(mptr+1));
}
if(mptr->next == (struct mhdr *)0) {
struct mhdr *moremem;
int getsize;
getsize = size + MHDRSIZE;
moremem = (struct mhdr *)GetMemory(getsize);
if(!moremem) {
mfails++;
if(!mquiet) {
printf("\007MALLOC ERROR: no more memory\n");
}
if(mtrace) {
printf("000\n");
}
return((char *)0);
}
mptr->next = moremem;
mptr->next->prev = mptr;
mptr = mptr->next;
mptr->next = (struct mhdr *)0;
mptr->pretag = PRETAG;
mptr->posttag = POSTTAG;
mptr->size = getsize - MHDRSIZE;
} else {
mptr = mptr->next;
}
}
}
#ifdef MALLOC_DEBUG
#undef malloc
char *
malloc(int size, char *fname, int fline)
{
char *cp;
struct mhdr *mptr;
cp = _malloc(size);
if(cp) {
mptr = (struct mhdr *)cp;
mptr--;
strncpy(mptr->fname,fname,FNAMESIZE-1);
mptr->fline = fline;
mptr->fname[FNAMESIZE-1] = 0;
mptr->fline = fline;
}
return(cp);
}
#else
char *
malloc(int size)
{
return(_malloc(size));
}
#endif
void
free(void *vp)
{
char *cp = vp;
struct mhdr *mptr;
if(mtrace) {
printf("free(0x%lx)\n",(long)cp);
}
/* Keep track of number of calls to free for debug. */
fcalls++;
mptr = (struct mhdr *)cp - 1;
/* Start by checking sanity of heap and make sure that the incoming
* pointer corresponds to a valid entry in the heap.
*/
if(heapcheck(mptr,0) < 0) {
return;
}
/* The first thing to do to free the block is to make the size
* positive.
*/
mptr->size = abs(mptr->size);
/* Keep track of how much memory is freed for debug. */
ftot += mptr->size;
/* To defragment the memory, see if previous and/or
* next block is free; if yes, then join them into one larger
* block. Note that the blocks will only be joined if they are in
* contiguous memory space.
*/
if(mptr->next) {
if((mptr->next->size > 0) &&
(mptr->next == (struct mhdr *)
((char *)mptr + mptr->size + MHDRSIZE))) {
mptr->size += mptr->next->size + MHDRSIZE;
mptr->next = mptr->next->next;
if(mptr->next) {
mptr->next->prev = mptr;
}
}
}
if(mptr->prev) {
if((mptr->prev->size > 0) &&
(mptr->prev == (struct mhdr *)
((char *)mptr-mptr->prev->size - MHDRSIZE))) {
if(mptr->next) {
mptr->next->prev = mptr->prev;
}
mptr->prev->next = mptr->next;
mptr->prev->size += mptr->size + MHDRSIZE;
}
}
}
/* calloc():
* Allocate space for an array of nelem elements of size elsize.
* Initialize the space to zero.
*/
static char *
_calloc(int nelem, int elsize)
{
register char *cp, *end;
char *base;
int size;
size = nelem * elsize;
base = _malloc(size);
if(base) {
cp = base;
end = base+size;
while(cp<end) {
*cp++ = 0;
}
}
return(base);
}
#ifdef MALLOC_DEBUG
char *
calloc(int nelem, int elsize, char *fname, int fline)
{
char *cp;
struct mhdr *mptr;
cp = _calloc(nelem, elsize);
if(cp) {
mptr = (struct mhdr *)cp;
mptr--;
strncpy(mptr->fname,fname,FNAMESIZE-1);
mptr->fline = fline;
mptr->fname[FNAMESIZE-1] = 0;
mptr->fline = fline;
}
return(cp);
}
#else
char *
calloc(int nelem, int elsize)
{
return(_calloc(nelem,elsize));
}
#endif
static char *
_realloc(char *cp,int newsize)
{
char *new;
int asize, delta;
struct mhdr *mptr, tmphdr;
rcalls++;
/* If incoming pointer is NULL, then do a regular malloc. */
if(!cp) {
return(_malloc(newsize));
}
/* If newsize is zero and pointer is not null, then do a free. */
if(!newsize) {
free(cp);
return((char *)0);
}
/* Set the mhdr pointer to the header attached to the incoming
* char pointer. We assume here that the incoming pointer is the
* base address of the block of memory that is being reallocated.
*/
mptr = (struct mhdr *)cp - 1;
/* Start by checking sanity of heap and make sure that the incoming
* pointer corresponds to a valid entry in the heap.
*/
if(heapcheck(mptr,0) < 0) {
return((char *)0);
}
/* Recall that mptr->size is negative since the block is not free, so
* use the absolute value of mptr->size...
*/
asize = abs(mptr->size);
/* Make requested size divisible by 4:
*/
if(newsize & 3) {
newsize += 4;
newsize &= 0xfffffffc;
}
/* If newsize is less than or equal to current size, just return with
* the same pointer. At some point, this should be improved so that
* the memory made made available by this reallocation is put back in
* the pool.
*/
if(newsize <= asize) {
return(cp);
}
/* Now we do the actual reallocation...
* If there is a fragment after this one (next != NULL) AND it is
* available (size > 0) AND the combined size of the next fragment
* along with the current fragment exceeds the request, then we can
* reallocate quickly.
* Otherwise, we have to just malloc a whole new block and copy the
* old buffer to the new larger space.
*/
if((mptr->next) && (mptr->next->size > 0) &&
((asize + mptr->next->size + MHDRSIZE) > newsize)) {
/* At this point we know we have the space to reallocate without
* the malloc/free step. Now we need to add the necessary space
* to the current fragment, and take that much away from the next
* fragment...
*/
delta = newsize - asize;
/* next line used to be: tmphdr = *mptr->next... */
memcpy((char *)&tmphdr,(char *)mptr->next, sizeof(struct mhdr));
mptr->size = -newsize;
mptr->next = (struct mhdr *)(delta + (int)(mptr->next));
mptr->next->size = (abs(tmphdr.size) - delta);
mptr->next->pretag = PRETAG;
mptr->next->posttag = POSTTAG;
mptr->next->next = tmphdr.next;
mptr->next->prev = tmphdr.prev;
/* Keep track of totals and highwater:
*/
mtot += (newsize - asize);
if((mtot - ftot) > highwater) {
highwater = (mtot - ftot);
}
return(cp);
}
/* If the next fragment is not large enough, then malloc new space,
* copy the existing data to that block, free the old space and return
* a pointer to the new block.
*/
new = _malloc(newsize);
if(!new) {
return((char *)0);
}
memcpy(new,cp,asize);
free(cp);
return(new);
}
#ifdef MALLOC_DEBUG
#undef realloc
char *
realloc(char *buf, int newsize, char *fname, int fline)
{
char *cp;
struct mhdr *mptr;
cp = _realloc(buf, newsize);
if(cp) {
mptr = (struct mhdr *)cp;
mptr--;
strncpy(mptr->fname,fname,FNAMESIZE-1);
mptr->fline = fline;
mptr->fname[FNAMESIZE-1] = 0;
mptr->fline = fline;
}
return(cp);
}
#else
char *
realloc(char *buf, int newsize)
{
return(_realloc(buf,newsize));
}
#endif
void
heapdump(int verbose)
{
register struct mhdr *mptr;
char freenow;
int i, alloctot, freetot, size;
heapcheck(0,0);
mptr = heapbase;
i=0;
freetot = 0;
alloctot = 0;
if(verbose) {
printf(" addr size free? mptr nxt prv ascii@addr\n");
} else {
printf("Heap summary:\n");
}
for(i=0; mptr; i++) {
if(mptr->size < 0) {
freenow = 'n';
size = -mptr->size;
alloctot += size;
} else {
freenow = 'y';
size = mptr->size;
freetot += size;
}
if(verbose) {
printf("%3d: 0x%08lx %5d %c 0x%08lx 0x%08lx 0x%08lx ",
i,(ulong)(mptr+1),size,freenow,(ulong)mptr,
(ulong)(mptr->next),(ulong)(mptr->prev));
prascii((unsigned char *)(mptr+1),size > 16 ? 16 : size);
putchar('\n');
#ifdef MALLOC_DEBUG
if(freenow == 'n') {
printf(" %s %d\n",mptr->fname,mptr->fline);
}
#endif
}
mptr = mptr->next;
}
if(verbose) {
putchar('\n');
}
printf(" Malloc/realloc/free calls: %d/%d/%d\n",mcalls,rcalls,fcalls);
printf(" Malloc/free totals: %d/%d\n",mtot,ftot);
printf(" High-water level: %d\n",highwater);
printf(" Malloc failures: %d\n",mfails);
printf(" Bytes overhead: %d\n",i * MHDRSIZE);
printf(" Bytes currently allocated: %d\n",alloctot);
printf(" Bytes free on current heap: %d\n",freetot);
printf(" Bytes left in allocation pool: %d\n",GetMemoryLeft());
}
/* releaseExtendedHeap():
* If memory has been allocated through the extended heap established
* by the heap -x{start,size} command, this function will attempt
* to "undo" that. It can only be un-done if there is no currently active
* allocations in that range.
*
* This function is accessible by the application through monlib.
*/
int
releaseExtendedHeap(int verbose)
{
int i;
struct mhdr *mptr, *extbase;
extbase = (struct mhdr *)getExtHeapBase();
if(!extbase) {
if(verbose) {
printf("Heap extension not set\n");
}
return(-1);
}
heapcheck(0,0);
mptr = heapbase;
for(i=0; mptr; i++) {
if(mptr->next == extbase) {
if(mptr->next->next == (struct mhdr *)0) {
mptr->next = (struct mhdr *)0;
unExtendHeap();
if(verbose) {
printf("Heap extension cleared\n");
}
return(0);
} else if(verbose) {
printf("Extended heap space is in use.\n");
}
break;
}
mptr = mptr->next;
}
if(!mptr) { /* Heap was extended, but not used. */
unExtendHeap(); /* Remove the extension. */
if(verbose) {
printf("Heap extension cleared.\n");
}
}
return(0);
}
char *HeapHelp[] = {
"Display heap statistics.",
"-[cf:m:qtvX:x]",
#if INCLUDE_VERBOSEHELP
"Options:",
" -c clear high-water level and malloc/free totals",
" -f{ptr} free block @ 'ptr'",
" -m{size} malloc 'size' bytes",
" -q quiet runtime (don't print MALLOC ERROR msgs)",
" -v verbose (more detail)",
" -t toggle runtime malloc/free trace",
" -X{base,size}",
" extend heap by 'size' bytes starting at 'base'",
" -x clear heap extension",
#endif
0
};
int
Heap(int argc,char *argv[])
{
char *establish_extended_heap, buf[32];
int verbose, release_extended_heap, showheap, opt;
showheap = 1;
establish_extended_heap = (char *)0;
release_extended_heap = verbose = 0;
while((opt=getopt(argc,argv,"cf:m:qtvX:x")) != -1) {
switch(opt) {
case 'c':
mcalls = fcalls = 0;
mtot = ftot = highwater = 0;
showheap = 0;
break;
case 'f':
free((char *)strtoul(optarg,0,0));
showheap = 0;
break;
case 'm':
shell_sprintf("MALLOC","0x%lx",
(ulong)_malloc(strtoul(optarg,0,0)));
if(verbose) {
printf("%s\n",buf);
}
showheap = 0;
break;
case 'q':
showheap = 0;
mquiet = 1;
break;
case 't':
mtrace = ~mtrace;
printf("Runtime trace: %sabled\n",
mtrace ? "en" : "dis");
return(CMD_SUCCESS);
case 'v':
verbose = 1;
break;
case 'X':
establish_extended_heap = optarg;
showheap = 0;
break;
case 'x':
release_extended_heap = 1;
showheap = 0;
break;
default:
return(CMD_PARAM_ERROR);
}
}
if(release_extended_heap) {
releaseExtendedHeap(verbose);
}
if(establish_extended_heap) {
int size, rc;
char *comma, *begin;
comma = strchr(establish_extended_heap,',');
if(!comma) {
return(CMD_PARAM_ERROR);
}
*comma = 0;
begin = (char *)strtoul(establish_extended_heap,0,0);
size = (int)strtoul(comma+1,0,0);
rc = extendHeap(begin,size);
if(rc == -1) {
printf("Extended heap already exists @ 0x%lx\n",
(ulong)getExtHeapBase());
}
if(rc < 0) {
return(CMD_FAILURE);
}
}
if(!showheap) {
return(CMD_SUCCESS);
}
if(optind == argc) {
heapdump(verbose);
}
return(CMD_SUCCESS);
}
#else
char *
malloc(int size)
{
return(0);
}
void
free(char *buf)
{
}
char *
realloc(char *buf, int newsize, char *fname, int fline)
{
return(0);
}
#endif