// The contents of this file are subject to the Mozilla Public License
// Version 1.1 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License
// at http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
// the License for the specific language governing rights and
// limitations under the License.
//
// The Initial Developer of the Original Code is Kipp E.B. Hickman.

#include "libmalloc.h"
#include <memory.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <setjmp.h>
#include <dlfcn.h>

#ifdef NTO
#include <sys/link.h>
extern r_debug _r_debug;
#else
#include <link.h>
#endif

#ifdef NTO
#define JB_BP 0x08
#include <setjmp.h>
#endif

extern "C" {
#ifdef NEED_WRAPPERS
  void* __wrap_malloc(size_t);
  void* __wrap_realloc(void*, size_t);
  void __wrap_free(void*);
  void* __wrap___builtin_new(size_t);
  void __wrap___builtin_delete(void*);
  void* __wrap___builtin_vec_new(size_t);
  void __wrap___builtin_vec_delete(void*);
  void* __wrap_PR_Malloc(size_t);
  void* __wrap_PR_Calloc(size_t, size_t);
  void* __wrap_PR_Realloc(void*, size_t);
  void __wrap_PR_Free(void*);
#endif
}

static int gLogFD = -1;
static u_long gFlags;
static int gFillCount = 0;

#define MARKER0		0xFFFFFFFF
#define MARKER1		0xEEEEEEEE
#define MARKER2_0	0xD8D8D8D8
#define MARKER2_1	0xE8E8E8E8

#define PATTERN_BYTE      0xFE
#define FREE_PATTERN_BYTE 0xDE

struct Header {
  u_long marker0;
  size_t rawSize;		// user requested size
  size_t size;			// size rounded up
  u_long marker1;
};

struct Trailer {
  u_long marker2[2];
};

//----------------------------------------------------------------------

#if defined(i386)
static void CrawlStack(malloc_log_entry* me, jmp_buf jb)
{
#ifdef NTO
  u_long* bp = (u_long*) (jb[0].__savearea[JB_BP]);
#else
  u_long* bp = (u_long*) (jb[0].__jmpbuf[JB_BP]);
#endif
  u_long numpcs = 0;
  int skip = 2;
  while (numpcs < MAX_STACK_CRAWL) {
    u_long* nextbp = (u_long*) *bp++;
    u_long pc = *bp;
    if ((pc < 0x08000000) || (pc > 0x7fffffff) || (nextbp < bp)) {
      break;
    }
    if (--skip < 0) {
      me->pcs[numpcs++] = (char*) pc;
    }
    bp = nextbp;
  }
  me->numpcs = numpcs;
}
#endif

//----------------------------------------------------------------------

#if defined(linux) || defined(NTO)
static void DumpAddressMap()
{
  int mfd = open("malloc-map", O_CREAT|O_WRONLY|O_TRUNC, 0666);
  if (mfd >= 0) {
    malloc_map_entry mme;
    link_map* map = _r_debug.r_map;
    while (NULL != map) {
      if (0 != map->l_addr) {
	mme.nameLen = strlen(map->l_name);
	mme.address = map->l_addr;
	write(mfd, &mme, sizeof(mme));
	write(mfd, map->l_name, mme.nameLen);
#if 0
	write(1, map->l_name, mme.nameLen);
	write(1, "\n", 1);
#endif
      }
      map = map->l_next;
    }
    close(mfd);
  }
}
#endif

//----------------------------------------------------------------------

static int Verify(Header* h)
{
  // Sanity check the header first
  if ((h->marker0 != MARKER0) ||
      (h->marker1 != MARKER1) ||
      (h->rawSize > h->size)) {
    DumpAddressMap();
    abort();
  }

  // Sanity check the trailer second
  Trailer* t = (Trailer*) ((char*)(h + 1) + h->size);
  if ((t->marker2[0] != MARKER2_0) ||
      (t->marker2[1] != MARKER2_1)) {
    DumpAddressMap();
    abort();
  }

  // Verify there were no overruns
  size_t fill = h->size - h->rawSize;
  if (0 != fill) {
    unsigned char* cp = ((unsigned char*)(h + 1)) + h->rawSize;
    unsigned char* end = cp + fill;
    while (cp < end) {
      unsigned char ch = *cp++;
      if (ch != PATTERN_BYTE) {
	DumpAddressMap();
	abort();
      }
    }
  }
  return 1;
}

static void
Log(int aType, void* aAddr, size_t aSize, void* aOldAddr)
{
  malloc_log_entry me;

  me.type = (u_long) aType;
  me.address = (u_long) aAddr;
  me.size = (u_long) aSize;
  me.oldaddress = (u_long) aOldAddr;

  jmp_buf jb;
  setjmp(jb);
  CrawlStack(&me, jb);

  write(gLogFD, &me, sizeof(me) - MAX_STACK_CRAWL*sizeof(char*) +
	me.numpcs*sizeof(char*));
}

static void*
MallocHook(size_t aSize, u_long aLogType)
{
  size_t roundedSize = aSize;
  roundedSize = ((roundedSize + 4 + gFillCount) >> 2) << 2;

  void* ptr = REAL_MALLOC(sizeof(Header) + roundedSize + sizeof(Trailer));

  if (NULL != ptr) {
    Header* h = (Header*) ptr;
    h->rawSize = aSize;
    h->size = roundedSize;
    h->marker0 = MARKER0;
    h->marker1 = MARKER1;

    ptr = (void*) ((char*)(h+1));

    // Fill new memory with a pattern to help detect overruns and
    // usage of un-written memory
    memset(ptr, PATTERN_BYTE, roundedSize);

    Trailer* t = (Trailer*) ((char*)ptr + roundedSize);
    t->marker2[0] = MARKER2_0;
    t->marker2[1] = MARKER2_1;

    if (LIBMALLOC_LOG & gFlags) {
      Log(aLogType, ptr, aSize, 0);
    }
  }

  return ptr;
}

static void
FreeHook(void* aAddr, u_long aLogType)
{
  if (0 == aAddr) {
    return;
  }
  if (LIBMALLOC_LOG & gFlags) {
    Log(aLogType, aAddr, 0, 0);
  }
  Header* h = (Header*) ((char*)aAddr - sizeof(Header));
  if (Verify(h)) {
    // Munge the header so that a dup-free will fail the verify
    h->marker0 = 0xDEADBEEF;
    h->marker1 = 0xDEADBEEF;

    // Munge the body of the allocation so that if the user
    // still has a live reference they get messed up
    void* ptr = (void*) ((char*)(h+1));
    memset(ptr, FREE_PATTERN_BYTE, h->rawSize);

    if (0 == (LIBMALLOC_NOFREE & gFlags)) {
      REAL_FREE(h);
    }
  }
  else {
    if (0 == (LIBMALLOC_NOFREE & gFlags)) {
      REAL_FREE(aAddr);
    }
  }
}

static void*
ReallocHook(void* aOldAddr, size_t aSize)
{
  if (0 == aOldAddr) {
    return MallocHook(aSize, malloc_log_malloc);
  }
  Header* oldh = (Header*) ((char*)aOldAddr - sizeof(Header));
  if (!Verify(oldh)) {
    return REAL_REALLOC(aOldAddr, aSize);
  }
  size_t oldSize = oldh->rawSize;

  size_t roundedSize = aSize;
  roundedSize = ((roundedSize + 4) >> 2) << 2;

  void* ptr = REAL_MALLOC(sizeof(Header) + roundedSize + sizeof(Trailer));

  if (NULL != ptr) {
    Header* h = (Header*) ptr;
    h->rawSize = aSize;
    h->size = roundedSize;
    h->marker0 = MARKER0;
    h->marker1 = MARKER1;

    ptr = (void*) ((char*)(h+1));

    Trailer* t = (Trailer*) ((char*)ptr + roundedSize);
    t->marker2[0] = MARKER2_0;
    t->marker2[1] = MARKER2_1;

    // Copy old memory into new memory (don't copy too much!)
    size_t copy = oldSize;
    if (copy > aSize) copy = aSize;
    memcpy(ptr, aOldAddr, copy);

    // Fill any uncopied memory with the overrun pattern
    size_t fill = roundedSize - copy;
    if (0 != fill) {
      memset((char*)ptr + copy, PATTERN_BYTE, fill);
    }

    if (0 == (LIBMALLOC_NOFREE & gFlags)) {
      REAL_FREE(oldh);
    }
    else {
      // Mark the old header so that a verify will fail if the caller
      // dup free's it.
      oldh->marker0 = 0xDEADBEEF;
      oldh->marker1 = 0xDEADBEEF;

      // Munge the body of the old allocation so that if the user
      // still has a live reference they get messed up
      void* optr = (void*) ((char*)(oldh+1));
      memset(optr, FREE_PATTERN_BYTE, oldh->rawSize);
    }

    if (LIBMALLOC_LOG & gFlags) {
      Log(malloc_log_realloc, ptr, aSize, aOldAddr);
    }
  }
  return ptr;
}

u_long
SetMallocFlags(u_long aFlags)
{
  u_long old = gFlags;
  gFlags = aFlags;

  if ((-1 == gLogFD) && ((LIBMALLOC_LOG|LIBMALLOC_LOG_RC) & gFlags)) {
    gLogFD = open("malloc-log", O_CREAT|O_WRONLY|O_TRUNC, 0666);
    if (gLogFD < 0) {
      gFlags &= ~LIBMALLOC_LOG;
      printf("unable to create malloc-log: %d\n", errno);
    }
  }
  if ((gLogFD >= 0) && (0 == ((LIBMALLOC_LOG|LIBMALLOC_LOG_RC) & gFlags))) {
    close(gLogFD);
    gLogFD = -1;
  }
#ifndef NTO
  if (LIBMALLOC_CHECK & gFlags) {
    mallopt(M_CHECK_ACTION, 1);
  }
#endif

  // Try to guarantee that the address map is always dumped
  atexit(DumpAddressMap);

  return old;
}

static int gFirstTime = 1;

static void Init()
{
  gFirstTime = 0;
  u_long flags = 0;
  char* s = getenv("LIBMALLOC_LOG");
  if (s) {
    flags = atoi(s);
    if (LIBMALLOC_LOG & flags) {
      char m1[] = "dbgmalloc: enabled memory logging\n";
      write(1, m1, sizeof(m1)-1);
    }
    if (LIBMALLOC_LOG_RC & flags) {
      char m2[] = "dbgmalloc: enabled refcnt logging\n";
      write(1, m2, sizeof(m2)-1);
    }
    if (LIBMALLOC_NOFREE & flags) {
      char m3[] = "dbgmalloc: disabled free\n";
      write(1, m3, sizeof(m3)-1);
    }
  }
  SetMallocFlags(flags);
  s = getenv("LIBMALLOC_FILL");
  if (s) {
    gFillCount = atoi(s);
    char m4[] = "dbgmalloc: adding extra memory fill ";
    write(1, m4, sizeof(m4)-1);
    write(1, s, strlen(s));
    write(1, "\n", 1);
  }
}

//----------------------------------------------------------------------

#ifdef NEED_WRAPPERS
void* __wrap_malloc(size_t aSize)
{
  if (gFirstTime) {
    Init();
  }
  return MallocHook(aSize, malloc_log_malloc);
}

void* __wrap_realloc(void* aPtr, size_t aSize)
{
  if (gFirstTime) {
    Init();
  }
  return ReallocHook(aPtr, aSize);
}

void __wrap_free(void* aPtr)
{
  if (gFirstTime) {
    Init();
  }
  FreeHook(aPtr, malloc_log_free);
}

void* __wrap___builtin_new(size_t aSize)
{
  if (gFirstTime) {
    Init();
  }
  return MallocHook(aSize, malloc_log_new);
}

void __wrap___builtin_delete(void* aPtr)
{
  if (gFirstTime) {
    Init();
  }
  FreeHook(aPtr, malloc_log_delete);
}

void* __wrap___builtin_vec_new(size_t aSize)
{
  if (gFirstTime) {
    Init();
  }
  return MallocHook(aSize, malloc_log_new);
}

void __wrap___builtin_vec_delete(void* aPtr)
{
  if (gFirstTime) {
    Init();
  }
  FreeHook(aPtr, malloc_log_delete);
}

void* __wrap_PR_Malloc(size_t aSize)
{
  if (gFirstTime) {
    Init();
  }
  return MallocHook(aSize, malloc_log_malloc);
}

void* __wrap_PR_Calloc(size_t aSize, size_t aBsize)
{
  if (gFirstTime) {
    Init();
  }
  size_t size = aSize*aBsize;
  void* ptr = MallocHook(size, malloc_log_malloc);
  if (NULL != ptr) {
    memset(ptr, 0, size);
  }
  return ptr;
}

void* __wrap_PR_Realloc(void* aPtr, size_t aSize)
{
  if (gFirstTime) {
    Init();
  }
  return ReallocHook(aPtr, aSize);
}

void __wrap_PR_Free(void* aPtr)
{
  if (gFirstTime) {
    Init();
  }
  FreeHook(aPtr, malloc_log_free);
}
#endif

//----------------------------------------

// Strong symbols so that libc references are routed to us

void* malloc(size_t aSize)
{
  if (gFirstTime) {
    Init();
  }
  return MallocHook(aSize, malloc_log_malloc);
}

void* realloc(void* aPtr, size_t aSize)
{
  if (gFirstTime) {
    Init();
  }
  return ReallocHook(aPtr, aSize);
}

void free(void* aPtr)
{
  if (gFirstTime) {
    Init();
  }
  FreeHook(aPtr, malloc_log_free);
}

void* calloc(size_t aSize, size_t aBsize)
{
  if (gFirstTime) {
    Init();
  }
  size_t size = aSize*aBsize;
  void* ptr = MallocHook(size, malloc_log_malloc);
  if (NULL != ptr) {
    memset(ptr, 0, size);
  }
  return ptr;
}

void cfree(void* ptr)
{
  if (gFirstTime) {
    Init();
  }
  FreeHook(ptr, malloc_log_free);
}

void* memalign(size_t alignment, size_t size)
{
  ::abort();
}

void* valloc(size_t size)
{
  ::abort();
}

void* pvalloc(size_t size)
{
  ::abort();
}

void* __builtin_new(size_t aSize)
{
  if (gFirstTime) {
    Init();
  }
  return MallocHook(aSize, malloc_log_new);
}

void __builtin_delete(void* aPtr)
{
  if (gFirstTime) {
    Init();
  }
  FreeHook(aPtr, malloc_log_delete);
}

void* __builtin_vec_new(size_t aSize)
{
  if (gFirstTime) {
    Init();
  }
  return MallocHook(aSize, malloc_log_new);
}

void __builtin_vec_delete(void* aPtr)
{
  if (gFirstTime) {
    Init();
  }
  FreeHook(aPtr, malloc_log_delete);
}

void
__log_addref(void* p, int oldrc, int newrc)
{
  if (gFirstTime) {
    Init();
  }
  if (LIBMALLOC_LOG_RC & gFlags) {
    Log(malloc_log_addref, p, size_t(oldrc), (void*)newrc);
  }
}

void
__log_release(void* p, int oldrc, int newrc)
{
  if (gFirstTime) {
    Init();
  }
  if (LIBMALLOC_LOG_RC & gFlags) {
    Log(malloc_log_release, p, size_t(oldrc), (void*)newrc);
  }
}
