/* Memory access checker.
   Copyright 1993 Tristan Gingold
		  Written September 1993 Tristan Gingold

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with this library; see the file COPYING.LIB.  If
not, write to the Free Software Foundation, Inc., 675 Mass Ave,
Cambridge, MA 02139, USA.

   The author may be reached (Email) at the address marc@david.saclay.cea.fr,
   or (US/French mail) as Tristan Gingold 
   			  8 rue Parmentier
   			  F91120 PALAISEAU
   			  FRANCE */

#ifndef	_MALLOC_INTERNAL
#define _MALLOC_INTERNAL
#include "malloc.h"
#endif

#ifdef CHKR_USE_BITMAP

extern char etext, end;

static void chkr_access_error(__ptr_t ptr, int arq, char bm, char val);
void chkr_show_frames();
#ifdef CHKR_SAVESTACK
void chkr_remove_symtabfile();
#endif /* CHKR_SAVESTACK */

/* return the bitmap address from an address, and set the offset of addr in
     the segment.  
   return 1 if the addr is not checked by a bitmap (e.g. text zone)
   return 0 if the addr is reaaly bad (inside a bitmap) */
static unsigned char *bitmap_base(__ptr_t addr, int *offset)
{
 if (addr < (__ptr_t)&etext)		/* inside text segment */
   return (char*)1;
#ifdef CHKR_DATABITMAP
 if (addr < (__ptr_t)chkr_data_bitmap)	/* inside data segment */
 {
   *offset = (int)addr - (int)&etext;
   return chkr_data_bitmap;
 }
#endif /* CHKR_DATABITMAP */
#ifdef CHKR_HEAPBITMAP
 if (addr < chkr_heap_begin)
# ifdef CHKR_DATABITMAP
   return (char*)0;		/* inside data bitmap */
# else
   return (char*)1;		/* inside data segment */
# endif /* CHKR_DATABITMAP */
 if (addr < (__ptr_t)chkr_heap_bitmap)		/* inside the heap */
 {
   *offset = (int)addr - (int)chkr_heap_begin;
   return chkr_heap_bitmap;
 }
#endif /* CHKR_HEAPBITMAP */
 if ( (addr < (__ptr_t)&addr) && (addr > (__ptr_t)&end) )
   return (char*)0;			/* inside heap bitmap */
 else
   return (char*)1;
}

#ifdef CHKR_DEBUG
/* Disp the bitmap from addr ptr. len is the number of line.
   Use this function, only when debugging Checker */
void chkr_disp_right(__ptr_t ptr, int len)
{
 unsigned char *base;
 int seg_offset;
 int bm_offset;
 int val;
 int i,j;
 char inc = (bm_round+1) / 4;
 int addr = (int)ptr & (~bm_round);
 static char *name_right[]={ "NO", " R", " W", "RW" };
 
 /* Search the base of the bitmap */
 base = bitmap_base (ptr, &seg_offset);
 if (base == (unsigned char*)0 || base == (unsigned char*)1)
   return;	/* Can't set right */
 /* offset in the bitmap */
 bm_offset = seg_offset >> bm_log_size;
 /* print the header */
 chkr_printf("          ");
 for(i=0; i<16; i++)
   chkr_printf("%02x ",i*inc);
 chkr_printf("\n");
 for(i=0; i<len; i++)
 {
   /* address of the line */
   chkr_printf("%08x: ", addr + i*16*inc);
   /* bitmap */
   /* val = ((unsigned int*)base)[bm_offset]; */
   val = base[bm_offset] + (base[bm_offset+1]<<8)+ (base[bm_offset+2]<<16)
         + (base[bm_offset+3]<<24);
   bm_offset += 4;
   for(j=0; j<16; j++)
   {
     chkr_printf("%s ", name_right[val & 3]);
     val >>= 2;
   }
   chkr_printf("\n");
 }
}
#endif /* CHKR_DEBUG */

/* Set the right of a zone of length len, starting at ptr */
void chkr_set_right(__ptr_t ptr, int len, int right)
{
 unsigned char *base;	/* Bitmap base */
 unsigned int seg_offset;	/* Offset in the segment */
 unsigned int bm_offset;	/* Offset in the bitmap */
 unsigned int bit_offset;	/* Offset in the byte */
 unsigned int i;	
 unsigned char val = 0;		/* Val to set in the bitmap */
 unsigned char mask = 0;	/* Mask corresponding to the val */
 
 /* Search the base of the bitmap */
 base = bitmap_base (ptr, &seg_offset);
 if (base == (unsigned char*)0 || base == (unsigned char*)1)
   return;	/* Can't set right */
 /* Be sure about right. Right is 2 bits length */
 right &= 3;
 /* offset in the bitmap */
 bm_offset = seg_offset >> bm_log_size;
 /* offset in the byte:  3  2  1  0
 			xx|xx|xx|xx */
 bit_offset = seg_offset & bm_round;
 if (bit_offset > 0)
 {
   /* Set the first byte in the bitmap. It depends of the addr */
   for( i=4 - bit_offset; i > 0 && len >0; i--)
   { 
     val <<= 2;
     val |= right;
     mask <<=2;
     mask |= 0x03;
     len--;
   }
   /* shift the byte */
   val <<= (bit_offset << 1);
   mask <<= (bit_offset << 1);
   mask = ~mask;
   base[bm_offset] &= mask;
   base[bm_offset] |= val;
   /* If finish, then return */
   if (len == 0)
     return;
   /* next byte to set */
   bm_offset++;
 }
 /* Now, we set bytes of bitmap. So len is decrease by bm_round + 1 */
 if (len > bm_round)
 {
   val = 0;
   for(i=4; i > 0; i--)
   {
     val <<= 2;
     val |= right;
   } 
   for(i = 0; len > bm_round; i++)
   {
     base[bm_offset++] = val;
     len -= bm_round + 1;
   }
   if (len == 0)
     return;
 }
 /* Now, the end. Set the last byte of bitmap */
 val = 0;
 mask = 0;
 for (i = len; i > 0; i--)
 {
   val <<= 2;
   val |= right;
   mask <<= 2;
   mask |= 0x03;
 }
 base[bm_offset] &= mask;
 base[bm_offset] |= val;
} 

/* Check the right of a zone of length len, starting at ptr */
/*				rights in bitmap
 *                    +------------+-------------+-------------+-------------+
 *                    | CHKR_UN    |   CHKR_RO   |   CHKR_WO   |   CHKR_RW   |
 *       +------------+------------+-------------+-------------+-------------+
 * right |  CHKR_UN   |               not accepted                           |
 *       +------------+------------+-------------+-------------+-------------+
 *  t    |  CHKR_RO   |   error    |     OK      |    error    |     OK      |
 *  e    +------------+------------+-------------+-------------+-------------+
 *  s    |  CHKR_WO   |   error    |    error    | -> CHKR_RW  |     OK      |
 *  t    +------------+------------+-------------+-------------+-------------+
 *  e    |  CHKR_TW   |   error    |    error    |     OK      |     OK      |
 *  d    +------------+------------+-------------+-------------+-------------+
 *       |  CHKR_RW   |   error    |    error    |    error    |     OK      | 
 *       +------------+------------+-------------+-------------+-------------+
 */
void chkr_check_addr(__ptr_t ptr, int len, int right1)
{
 unsigned char *base;		/* Bitmap base */
 unsigned int seg_offset;	/* Offset in the segment */
 unsigned int bm_offset;	/* Offset in the bitmap */
 unsigned int bit_offset;	/* Offset in the byte */
 unsigned int i;	
 unsigned char val = 0;		/* Val to set in the bitmap */
 unsigned char mask = 0;
 int right;			/* Good Value of right1 */

 /* Be sure about right. Right is 2 bits length */
 right = right1 &  3;  
 /* check for stupidities. Could be removed */
 if (right == 0 || len == 0)
 {
   chkr_errno = 10;	/* bad check, must never happen */
   chkr_perror();
   chkr_abort();
 }
 /* Search the base of the bitmap */
 base = bitmap_base (ptr, &seg_offset);
 if (base == (unsigned char*)1)
 {
   if ( ptr < (__ptr_t)&etext && 
           ((ptr < (__ptr_t)NULL_POINTER_ZONE) || (right & CHKR_WO)) )
     chkr_access_error(ptr, right, 0, 0);
   return;
 }
 if (base == (unsigned char*)0)
 {
   chkr_access_error(ptr, right, 0, 0);
   return;	/* check fails */
 }
 /* offset in the bitmap */
 bm_offset = seg_offset >> bm_log_size;
 /* offset in the byte:  3  2  1  0
 			xx|xx|xx|xx */
 bit_offset = seg_offset & bm_round;
 if (bit_offset > 0)
 {
   /* Set the first byte in the bitmap. It depends of the addr */
   for( i=4 - bit_offset; i > 0 && len >0; i--)
   {
     val <<= 2;
     val |= right;
     mask <<= 2;
     mask |= CHKR_RW;
     len--;
   }
   /* shift the byte */
   val <<= (bit_offset << 1);
   mask <<= (bit_offset << 1);
   if ((base[bm_offset] & val) != val)
   {
     chkr_access_error(ptr, right, base[bm_offset], val);
     return;	/* check fails */
   }
   if (right1 == CHKR_WO)
     base[bm_offset] |= mask;	/* CHKR_WO -> CHKR_RW */
   /* If finish, then return */
   if (len == 0)
     return;
   /* next byte to set */
   bm_offset++;
 }
 /* Now, we set bytes of bitmap. So len is decrease by bm_round + 1 */
 if (len > bm_round)
 {
   val = 0;
   mask = CHKR_RW | (CHKR_RW << 2) | (CHKR_RW << 4) | (CHKR_RW << 6);
   for(i=4; i > 0; i--)
   {
     val <<= 2;
     val |= right;
   } 
   for(i = 0; len > bm_round; i++)
   {
     if (base[bm_offset] & val != val)
     {
       chkr_access_error(ptr, right, base[bm_offset], val);
       return;	/* check fails */
     }
     if (right1 == CHKR_WO)
       base[bm_offset] |= mask;
     bm_offset++;
     len -= bm_round + 1;
   }
   if (len == 0)
     return; /* OK */
 }
 /* Now, the end. Set the last byte of bitmap */
 val = 0;
 mask = 0;
 for (i = len; i > 0; i--)
 {
   val <<= 2;
   val |= right;
   mask <<= 2;
   mask |= CHKR_RW;
 }
 if (base[bm_offset] & val != val)
 {
   chkr_access_error(ptr, right, base[bm_offset], val);
   return;	/* check fails */
 }
 if (right1 == CHKR_WO)
   base[bm_offset] |= mask;
 return;
} 

/* Check for a string.
   Is this implementation poor ? */
void chkr_check_str(__ptr_t ptr, int right)
{
 chkr_check_addr(ptr, strlen(ptr), right);
}

static __ptr_t *ptr_on_block;

static void disp_block_history(int status)
{
  if (status == 0)
    (*__chkr_trap)(0,"Can't access to the symbol table.");
  else
  {
    (*__chkr_trap)(1,"The block was allocated from:");
    for(; *ptr_on_block; ptr_on_block++)
      chkr_show_addr((__ptr_t)ptr_on_block);
  }
}

/* Display all information */
static void chkr_access_error_heap (__ptr_t ptr)
{
  size_t block;
  __ptr_t real_ptr;	/* begin of the block */
  int type,i;
  struct list *next;

  if(ptr < (__ptr_t)_heapbase || ptr > ADDRESS(_heaplimit))
   return;	/* not inside the heap */
   
  block = BLOCK (ptr);

  /* Don't forget that ptr can point to anywhere */
  if(_heapinfo[block].status.state!=MDHEAD &&
     _heapinfo[block].status.state!=MDCORP && 
     _heapinfo[block].status.state!=MDTAIL )
  {
    (*__chkr_trap)(0,"inside a free block.");
    return;	/* pointer on free block */
  }
    
  /* Search the begin of the block */
  while(_heapinfo[block].status.state!=MDHEAD)
    block--;
 
  type=_heapinfo[block].busy.type;
  
  if(type==0)
  {
    real_ptr = ADDRESS(block);	/* found the begin of the block */
    if (real_ptr == (__ptr_t)_heapinfo) 
    {
     (*__chkr_trap)(0,"inside the heapinfo structure");
      return;	/* point on the _heapinfo[] structure */
    }
  }
  else
  {
   real_ptr = (__ptr_t)((unsigned int)ptr & (0xffffffff << type));
   /* Get the address of the first free fragment in this block.  */
   next = (struct list *) ((char *) ADDRESS (block) +
			   (_heapinfo[block].busy.info.frag.first << type));
		
   /* Test if this fragment is free */
   for( i=_heapinfo[block].busy.info.frag.nfree; i > 0; i--)
   {
     if(next==real_ptr)
     {
       (*__chkr_trap)(0,"inside a free fragment");
       return;	/* free block */
     }
     next=next->next;
   }
  }
  if (((struct hdr_red_zone*)real_ptr)->magic != RED_ZONE_MAGIC)
  {
    (*__chkr_trap)(0,"access error: bad magic number at block %u.",block);
    return;
  }
  if (ptr < (real_ptr + be_red_zone + sizeof(struct hdr_red_zone)))
    (*__chkr_trap)(0,"%d bytes before the begin of the block",
		(int)(real_ptr + be_red_zone + sizeof(struct hdr_red_zone)) -
		(int)(ptr));
  else if (ptr < (real_ptr + be_red_zone + sizeof(struct hdr_red_zone) +
  			((struct hdr_red_zone*)real_ptr)->real_size) )
    (*__chkr_trap)(0,"%d bytes after the begin of the block",
		(int)(ptr) -
		(int)(real_ptr + be_red_zone + sizeof(struct hdr_red_zone)));
  else
    (*__chkr_trap)(0,"%d bytes after the end of the block",
    		(int)(ptr) -
    		(int)(real_ptr + be_red_zone + sizeof(struct hdr_red_zone) +
  			((struct hdr_red_zone*)real_ptr)->real_size));
#if CHKR_SAVESTACK
  ptr_on_block = (__ptr_t*)((int)real_ptr + be_red_zone + 
       	sizeof(struct hdr_red_zone) + 
       	((struct hdr_red_zone*)real_ptr)->real_size);
  chkr_use_symtab(disp_block_history);
#endif /* CHKR_SAVESTACK */  			
}

static char *right_name[]={
	"*internal error*",
	"Reading",
	"Writing",
	"modifying"};
	
static void chkr_access_error(__ptr_t ptr, int arq, char bm, char val)
{
#ifdef CHKR_SAVESTACK
 int must_remove = 0;
#endif /* CHKR_SAVESTACK */ 
 
 (*__chkr_trap)(1,"Memory access error");
 (*__chkr_trap)(0,"When %s at address %p", right_name[arq], ptr);
 do
 {
   if (ptr < (__ptr_t)NULL_POINTER_ZONE)
   {
     (*__chkr_trap)(0,"inside the null pointer zone");
     (*__chkr_trap)(0,"You probably use a NULL pointer.");
     (*__chkr_trap)(0,"THIS WILL PRODUCE A SEGMENTATION FAULT.");
#ifdef CHKR_SAVESTACK     
     must_remove = 1;
#endif /* CHKR_SAVESTACK */
     break;
   }
   if (ptr < (__ptr_t)&etext && (arq & CHKR_WO) )
   {
     (*__chkr_trap)(0,"inside the text segment");
     (*__chkr_trap)(0,"You may not modifie it.");
     (*__chkr_trap)(0,"THIS WILL PRODUCE A SEGMENTATION FAULT.");
#ifdef CHKR_SAVESTACK     
     must_remove = 1;
#endif /* CHKR_SAVESTACK */     
     break;
   }
#ifdef CHKR_DATABITMAP
   if (ptr < (__ptr_t)chkr_data_bitmap)	/* inside data segment */
   {
     (*__chkr_trap)(0,"inside the data segment");
     break;
   }
#endif /* CHKR_DATABITMAP */
#ifdef CHKR_HEAPBITMAP
   if (ptr < chkr_heap_begin)
   {
# ifdef CHKR_DATABITMAP
     (*__chkr_trap)(0,"inside the data bitmap");
# else
     (*__chkr_trap)(0,"inside the data segment");
# endif /* CHKR_DATABITMAP */
     break;
   }
   if (ptr < (__ptr_t)chkr_heap_bitmap)		/* inside the heap */
   {
     (*__chkr_trap)(0,"inside the heap");
     chkr_access_error_heap (ptr);
     break;
   }
#endif /* CHKR_HEAPBITMAP */
   if (ptr < (__ptr_t)&ptr)
   {
     (*__chkr_trap)(0,"inside the heap bitmap");
     break;
   }
   else
   {
     (*__chkr_trap)(0,"inside the stack");
     break;
   }
  }
 while(0);
 chkr_errno = 9;
#ifdef CHKR_SAVESTACK
 chkr_show_frames();
 if (must_remove)
   chkr_remove_symtabfile();
#endif  /* CHKR_SAVESTACK */
}
#endif /* CHKR_USE_BITMAP */