/*///---------------------------------------------------------------------\\\*/
/*// Copyright (c)               (_)/=\~/_(_)         All rights reserved. \\*/
/*/  2005-2006                (_)  /=\  ~/_  (_)        Released under the  \*/
/*\  Jim Peters            (_)    /=\    ~/_    (_)             GNU GPL v2  /*/
/*\\ http://uazu.net    (_) ____ /=\ ____ ~/_ ____ (_)      http://fsf.org //*/
/*\\\---------------------------------------------------------------------///*/

//
//	MALLOC DEBUGGING
//

#ifdef HEADER

// Set to supress exit dump of unfreed blocks
#define MEMDEBUG_NODUMP 0

// Length of testing block before/after allocated data (normally 32)
#define MEMHDR_TLEN 32
#define MEMHDR_MAGIC 0x71AE36BC

typedef struct memhdr memhdr;
struct memhdr {
   int magic;		// 0x71AE36BC
   memhdr *nxt;
   char *callnam;	// Caller name + address
   int len;
   char test[MEMHDR_TLEN];	// To test for writing before; also 32 bytes added after end of block
};


#else

#include "all.h"

#if MEMDEBUG_ON

// Globals
static memhdr *memlist;

static void 
hexdump(void *ptr, int len) {
   int cnt= (len + 15) / 16;
   char *p= ptr;

   while (cnt-- > 0) {
      char buf[80], *bp= buf;
      char *q= p;
      int a;

      bp += sprintf(bp, "%08X:", (int)q);
      for (a= 0; a<16; a++) {
	 if (a == 8) *bp++= ' ';
	 bp += sprintf(bp, " %02X", 255 & *q++);
      }
      *bp++= ' '; *bp++= ' ';
      for (a= 0; a<16; a++) {
	 if (a == 8) *bp++= ' ';
	 *bp++= (*p < 32 || *p >= 127) ? '.' : *p;
	 p++;
      }
      *bp= 0;
      fprintf(stderr, "MDB:   %s\n", buf);
   }
}

// All the stuff to get the hooks installed
static void my_init_hook(void);
static void *my_malloc_hook(size_t size, const void *caller);
static void *my_realloc_hook(void *ptr, size_t size, const void *caller);
static void *my_memalign_hook(size_t alignment, size_t size, const void *caller);
static void my_free_hook(void *ptr, const void *caller);

static void *(*old_malloc_hook)(size_t size, const void *caller);
static void *(*old_realloc_hook)(void *ptr, size_t size, const void *caller);
static void *(*old_memalign_hook)(size_t alignment, size_t size, const void *caller);
static void (*old_free_hook)(void *ptr, const void *caller);

static void 
my_atexit() {
   if (memlist && !MEMDEBUG_NODUMP) {
      fprintf(stderr, "MDB: ----- UNRELEASED MEMORY BLOCKS -----\n");
      memdebug_list();
   }
}

static void 
my_init_hook_firsttime() {
   atexit(my_atexit);
   my_init_hook();
}

void (*__malloc_initialize_hook)(void)= my_init_hook_firsttime;


static void
my_init_hook(void) {
   old_malloc_hook= __malloc_hook;
   __malloc_hook= my_malloc_hook;
   old_realloc_hook= __realloc_hook;
   __realloc_hook= my_realloc_hook;
   old_memalign_hook= __memalign_hook;
   __memalign_hook= my_memalign_hook;
   old_free_hook= __free_hook;
   __free_hook= my_free_hook;
}

static void
my_term_hook(void) {
   __malloc_hook= old_malloc_hook;
   __realloc_hook= old_realloc_hook;
   __memalign_hook= old_memalign_hook;
   __free_hook= old_free_hook;
}

#define __OLDHOOKS my_term_hook()
#define __NEWHOOKS my_init_hook()

// Read a particular line out of a particular file, including the
// newline (plus prefixed "MDB:"), or returns ""; NOT THREAD-SAFE
static char *
fileline(char *spec) {
   static char buf[256];
   int lin;
   FILE *in;
   
   if (2 != sscanf(spec, "%255[^:]:%*[^:]:%d", buf, &lin)) 
      return "";
   
   in= fopen(buf, "r");
   if (!in) return "";

   strcpy(buf, "MDB:");
   while (lin > 1) {
      if (!fgets(buf+4, sizeof(buf)-4, in)) { fclose(in); return ""; }
      if (strchr(buf, '\n')) lin--;
   }
   if (!fgets(buf+4, sizeof(buf)-4, in)) { fclose(in); return ""; }
   fclose(in);
   return buf;
}


// Lookup the 'num'th caller return address on the stack
static void*
find_caller(int num) {
   // Docs say that arg must be a constant, so we can't use the variable directly
#define CASE(num) case num: return __builtin_return_address(num)
   switch (num) {
      CASE(0); CASE(1); CASE(2); CASE(3); CASE(4); 
      CASE(5); CASE(6); CASE(7); CASE(8); CASE(9); 
      CASE(10); CASE(11); CASE(12); CASE(13); CASE(14); 
      CASE(15); CASE(16); CASE(17); CASE(18); CASE(19); 
      CASE(20); CASE(21); CASE(22); CASE(23); CASE(24); 
      CASE(25); CASE(26); CASE(27); CASE(28); CASE(29); 
      CASE(30); CASE(31);
   }
   return 0;
}


// Convert an address into a "filename:function():linenumber (hexaddr)" format
static char *
addr2line(void *addr) {
   struct a2l_cache { 
      struct a2l_cache *nxt;
      void *addr;
      char *str;
   } *cp;
   static struct a2l_cache *cache;	// Cache list memory is not recorded in memlist
   char buf1[1024];
   char buf2[1024];
   char buf[4096];
   FILE *in;
   char *p, *q, *bp;

   for (cp= cache; cp; cp= cp->nxt)
      if (cp->addr == addr)
	 return cp->str;

   __OLDHOOKS;
   cp= malloc(sizeof(*cp));
   cp->nxt= cache; cache= cp;
   cp->addr= addr;
   p= buf;
   if (addr < (void*)0x80000000) {
      sprintf(buf, "addr2line -f -e ./symirr 0x%08x", (int)addr);
      in= popen(buf, "r");
      if (!in || 
	  !fgets(buf1, sizeof(buf1)-16, in) || 
	  !fgets(buf2, sizeof(buf2)-16, in)) {
	 warn("Can't execute addr2line utility to obtain file/line info");
	 buf1[0]= buf2[0]= 0;
      }
      if (in) pclose(in);
      
      p= strchr(buf1, 0);
      while (p > buf1 && isspace(p[-1])) *--p= 0;
      p= strchr(buf2, 0);
      while (p > buf2 && isspace(p[-1])) *--p= 0;
      p= q= strchr(buf2, ':');
      while (p > buf2 && p[-1] != '/') p--;
      bp= buf; memcpy(bp, p, q-p); bp += q-p;
      bp += sprintf(bp, ":%s()%s", buf1, q);
      
      p= strchr(buf, 0);
      if (0 == strcmp(buf, "??:??:0")) p= buf;
   }
   if (p > buf) *p++= ' ';
   sprintf(p, "(%08X)", (int)addr);
   cp->str= strdup(buf);
   __NEWHOOKS;

   return cp->str;
}

// Get caller name/line, skipping up out of libraries into user code,
// and skipping out of some functions into their caller, where this
// information is more useful
char *
caller_name(const void *caller0) {
   void *caller= (void*)caller0;
   char *nam;
   int a= -1;

   while (a<32) {
      nam= addr2line(caller);
      if (nam[0] != '(' &&
	  !strstr(nam, "util.c:Alloc") &&
	  !strstr(nam, "util.c:StrDup") &&
	  !strstr(nam, "util.c:list_") &&
	  !strstr(nam, "smtree.c:smtree()"))
	 return nam;
		   
      if (a < 0) {
	 for (a= 2; a<32; a++) 
	    if (find_caller(a) == caller)
	       break;
      }
      caller= find_caller(++a);
   }

   return addr2line((void*)caller0);
}


// Check that all the pre/post sections are untouched and that the
// memlist itself is intact
void 
checkall(char *callnam) {
   memhdr *p;
   int a;

   for (p= memlist; p; p= p->nxt) {
      int bad;
      unsigned char *q;

      if (p->magic != MEMHDR_MAGIC) {
	 fprintf(stderr, 
		 "MDB: Fatal corruption of MDB list at memory block %08X, "
		 "detected during call from %s\n%s", 
		 (int)p, callnam, fileline(callnam));
	 hexdump(p, sizeof(*p)+32);
	 error("Memory corruption");
      }

      for (bad= a= 0, q= &p->test[0]; a<MEMHDR_TLEN; a++, q++) 
	 if (*q != 0xE5) { bad= 1; break; }
      if (bad) {
	 fprintf(stderr, 
		 "MDB: Corruption before memory block %08X of %d bytes "
		 "allocated by %s, detected during call from %s\n%s%s", 
		 (int)(p+1), p->len, p->callnam, callnam, 
		 fileline(p->callnam), fileline(callnam));
	 hexdump(-MEMHDR_TLEN + (char*)(p+1), 32 + MEMHDR_TLEN);
	 memset(&p->test[0], 0xE5, MEMHDR_TLEN);
      }

      for (bad= a= 0, q= p->len+(char*)(p+1); a<MEMHDR_TLEN; a++, q++) 
	 if (*q != 0xE5) { bad= 1; break; }
      if (bad) {
	 fprintf(stderr, 
		 "MDB: Corruption after memory block %08X of %d bytes "
		 "allocated by %s, detected during call from %s\n%s%s", 
		 (int)(p+1), p->len, p->callnam, callnam,
		 fileline(p->callnam), fileline(callnam));
	 hexdump((p->len-32) + (char*)(p+1), 32 + MEMHDR_TLEN);
	 memset(p->len+(char*)(p+1), 0xE5, MEMHDR_TLEN);
      }
   }
}


static void *
my_malloc_hook(size_t size, const void *caller) {
   char *callnam= caller_name(caller);
   void *rv;
   memhdr *p;

   checkall(callnam);
   __OLDHOOKS;
   p= rv= malloc(size + sizeof(memhdr) + MEMHDR_TLEN);
   if (p) {
      rv= p+1;
      p->magic= MEMHDR_MAGIC;
      p->nxt= memlist;
      p->callnam= callnam;
      p->len= size;
      memset(p->test, 0xE5, MEMHDR_TLEN);
      memset(rv + p->len, 0xE5, MEMHDR_TLEN);
      memlist= p;
   }
   __NEWHOOKS;
   return rv;
}

static void *
my_realloc_hook(void *ptr, size_t size, const void *caller) {
   char *callnam= caller_name(caller);
   void *rv;
   memhdr *p, **prvp;

   checkall(callnam);
   __OLDHOOKS;
   rv= ptr - sizeof(memhdr);
   for (prvp= &memlist; (p= *prvp); prvp= &p->nxt) 
      if (p == rv) break;
   if (!p) {
      fprintf(stderr, 
	      "MDB: Realloc called for a block not on our list, address %08X, to "
	      "realloc to %d bytes, detected during call from %s; adding to our list\n%s", 
	      (int)ptr, size, callnam, fileline(callnam));
      hexdump(p+1, 64);
      rv= my_malloc_hook(size, caller);
      if (rv) memcpy(rv, ptr, size);
   } else {
      rv= realloc(rv, size);
      if (rv) { p= rv; *prvp= p; rv += sizeof(memhdr); }
   }
   __NEWHOOKS;
   return rv;
}

static void *
my_memalign_hook(size_t alignment, size_t size, const void *caller) {
   if (alignment <= 8) 
      return my_malloc_hook(size, caller);
   warn("memalign not supported at %s", caller_name(caller));
   return 0;
}

static void 
my_free_hook(void *ptr, const void *caller) {
   char *callnam= caller_name(caller);
   void *rv;
   memhdr *p, **prvp;

   checkall(callnam);
   if (!ptr) return;

   __OLDHOOKS;
   rv= ptr - sizeof(memhdr);
   for (prvp= &memlist; (p= *prvp); prvp= &p->nxt) 
      if (p == rv) 
	 break;
   if (p) {
      *prvp= p->nxt;
      memset(p+1, 0xE5, p->len);	// Nuke contents
      free(p);
   } else {
      fprintf(stderr, 
	      "MDB: Free called for a block not on our list, address %08X, "
	      "detected during call from %s\n%s", 
	      (int)ptr, callnam, fileline(callnam));
      hexdump(ptr, 64);
   }
   __NEWHOOKS;
}

// Check everything is okay
void 
memdebug_check() {
   void *caller= __builtin_return_address(0);
   checkall(caller_name(caller));
}

// List all outstanding memory blocks
void 
memdebug_list() {
   int ignored= 0;
   int ignored_size= 0;
   memhdr *p;
   void *caller= __builtin_return_address(0);
   checkall(caller_name(caller));

   for (p= memlist; p; p= p->nxt) {
      char *lin= fileline(p->callnam);
      if (strstr(lin, "MEMDEBUG-IGNORE")) {
	 ignored++;
	 ignored_size += p->len;
      } else {
	 fprintf(stderr, "MDB: block %08X size %d bytes, allocated by %s\n%s",
		 (int)(p+1), p->len, p->callnam, lin);
	 hexdump(p+1, p->len > 64 ? 64 : p->len);
      }
   }
   if (ignored) {
      fprintf(stderr, "MDB: Ignored %d additional blocks of total size "
	      "%d bytes due to MEMDEBUG-IGNORE flags in source\n",
	      ignored, ignored_size);
   }
}

#endif

#endif

/*///---------------------------------------------------------------------\\\*/
/*// )`( )()/)(`/\`//\`          (_)/=\~/_(_)           '/\\'/\')(\()( )'( \\*/
/*/ ()(')()\)(/`/\\ `         (_)  /=\  ~/_  (_)          ' //\'\)(/()(`)() \*/
/*\  )`()( )(/'            (_)    /=\    ~/_    (_)             `\)( )()'(  /*/
/*\\  ()()(`)(/         (_) ____ /=\ ____ ~/_ ____ (_)          \)(')()()  //*/
/*\\\---------------------------------------------------------------------///*/
