/*
   re-written version of the original mcheck.c because the old version
   just plain sucked and didn't work.

   dbg@be.com
*/   
#ifndef	_MALLOC_INTERNAL
#define	_MALLOC_INTERNAL
#include "malloc.h"
#include <stdio.h>
#endif

#define USE_POST_PADS

#define PAD_SIZE 3   /* number of ints to use as padding */

/* these numbers are (literally) random 32 bit values */
int pre_pad[PAD_SIZE]  = { 0x35f4987d, 0x142c7f19, 0x69696969};
int post_pad[PAD_SIZE] = { 0x7b571265, 0x57dadc31, 0xa5a5a5a5};

int free_pad[PAD_SIZE] = { 0x7c59c5f7, 0xc3e5b8f9, 0x13131313};


#define FREEFLOOD    0x95
#define MALLOCFLOOD  0xd7
#define REALLOCFLOOD 0x3f

struct hdr {       /* we always want this to be a multiple of 8 or 16 */
	int size;
	int pre_pad[PAD_SIZE];
};


#ifdef USE_POST_PADS
 #define PAD_OVERHEAD() (sizeof(struct hdr) + sizeof(post_pad))
#else
 #define PAD_OVERHEAD() (sizeof(struct hdr))
#endif

static enum mcheck_status
checkhdr (const struct hdr *hdr,  malloc_funcs *mf)
{
	char *end;

	if (memcmp(&hdr->pre_pad[1], &free_pad[1], sizeof(free_pad)-4) == 0) {
		fprintf(stderr, "memory freed twice: ptr @ 0x%x, size %d\n",
				&hdr[1], hdr->size);
		(*mf->abortfunc) (MCHECK_FREE);
	}

	if (memcmp(&hdr->pre_pad, pre_pad, sizeof(pre_pad)) != 0) {
		fprintf(stderr, "memory clobbered before start of allocation: ptr @ "
				"0x%x, size = %d\n", &hdr[1], hdr->size);
		(*mf->abortfunc) (MCHECK_HEAD);
	}

	end = &((char *)&hdr[1])[hdr->size];
	
#ifdef USE_POST_PADS
	if (memcmp(end, post_pad, sizeof(post_pad)) != 0) {
		fprintf(stderr, "memory clobbered beyond end of allocation: ptr @ 0x%x"
				", size = %d\n", &hdr[1], hdr->size);
		(*mf->abortfunc) (MCHECK_TAIL);
	}
#endif

	if (hdr->size < 0 || hdr->size > 512 * 1024 * 1024) {
		fprintf(stderr, "bogus memory allocation size %d for ptr @ 0x%x\n",
				hdr->size, &hdr[1]);
		(*mf->abortfunc) (MCHECK_HEAD);
	}
	return MCHECK_OK;
}

static void
freehook (void *ptr, malloc_state *ms, malloc_funcs *mf)
{
  struct hdr *hdr;

  if (ptr != NULL) {
	  hdr = ((struct hdr *) ptr) - 1;	  
	  checkhdr (hdr, mf);
	  memcpy(hdr->pre_pad, free_pad, sizeof(free_pad));
	  memset(ptr, FREEFLOOD, hdr->size);
  } else {
	  hdr = NULL;
  }
  
  _free (hdr, ms, mf);
}

static void *
mallochook (size_t size, malloc_state *ms, malloc_funcs *mf)
{
	struct hdr *hdr;

	hdr = (struct hdr *) _malloc (PAD_OVERHEAD() + size, ms, mf);
	if (hdr == NULL)
		return NULL;

	hdr->size = size;
	memcpy(hdr->pre_pad, pre_pad, sizeof(pre_pad));
	memset((void *)(hdr + 1), MALLOCFLOOD, size);
#ifdef USE_POST_PADS
	memcpy(&((char *)&hdr[1])[size], post_pad, sizeof(post_pad));
#endif
	return (void *) (hdr + 1);
}

static void *
reallochook (void *ptr, size_t size, malloc_state *ms, malloc_funcs *mf)
{
	struct hdr *hdr = NULL;
	size_t osize = 0;
	
	if (ptr != NULL) {
		hdr = ((struct hdr *) ptr) - 1;
		osize = hdr->size;
		checkhdr (hdr, mf);
		if (size < osize)
			memset((char *) ptr + size, FREEFLOOD, osize - size);
	} else {
		hdr = ptr;
	}
	
	hdr = (struct hdr *) _realloc (hdr, PAD_OVERHEAD() + size, ms, mf);
	if (hdr == NULL)
		return NULL;

	hdr->size = size;
	memcpy(hdr->pre_pad, pre_pad, sizeof(pre_pad));
	if (size > osize)
		memset((char *) (hdr + 1) + osize, REALLOCFLOOD, size - osize);
	
#ifdef USE_POST_PADS
	memcpy(&((char *)&hdr[1])[size], post_pad, sizeof(post_pad));
#endif

	return (void *) (hdr + 1);
}

static void
mabort (enum mcheck_status status)
{
  const char *msg;
  switch (status)
    {
    case MCHECK_OK:
      msg = "memory is consistent, library is buggy";
      break;
    case MCHECK_HEAD:
      msg = "memory clobbered before allocated block";
      break;
    case MCHECK_TAIL:
      msg = "memory clobbered past end of allocated block";
      break;
    case MCHECK_FREE:
      msg = "block freed twice";
      break;
    default:
      msg = "bogus mcheck_status, library is buggy";
      break;
    }

  debugger(msg);
}

static int mcheck_used = 0;

int
_mcheck (void (*func) (enum mcheck_status), malloc_state *ms, malloc_funcs *mf)
{
  mf->abortfunc = (func != NULL) ? func : &mabort;

  /* These hooks may not be safely inserted if malloc is already in use.  */
  if (!ms->malloc_initialized && !mcheck_used)
    {
      mf->old_free_hook    = mf->free_hook;
      mf->free_hook        = freehook;
      mf->old_malloc_hook  = mf->malloc_hook;
      mf->malloc_hook      = mallochook;
      mf->old_realloc_hook = mf->realloc_hook;
      mf->realloc_hook     = reallochook;
      mcheck_used = 1;
    }

  return mcheck_used ? 0 : -1;
}

enum mcheck_status
_mprobe (void * ptr, malloc_funcs *mf)
{
  return mcheck_used ? checkhdr (((struct hdr *) ptr) - 1, mf) : MCHECK_DISABLED;
}
