diff src/free-hook.c @ 428:3ecd8885ac67 r21-2-22

Import from CVS: tag r21-2-22
author cvs
date Mon, 13 Aug 2007 11:28:15 +0200
parents
children abe6d1db359e
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/free-hook.c	Mon Aug 13 11:28:15 2007 +0200
@@ -0,0 +1,596 @@
+/* This file is part of XEmacs.
+
+XEmacs is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2, or (at your option) any
+later version.
+
+XEmacs 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 General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with XEmacs; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.  */
+
+/* Synched up with: Not in FSF. */
+
+/* Debugging hooks for malloc. */
+
+/* These hooks work with gmalloc to catch allocation errors.
+   In particular, the following is trapped:
+
+   * Freeing the same pointer twice.
+   * Trying to free a pointer not returned by malloc.
+   * Trying to realloc a pointer not returned by malloc.
+
+   In addition, every word of every block freed is set to
+   0xdeadbeef.  This causes many uses of freed storage to be
+   trapped or recognized.
+
+   When you use this, the storage used by the last FREE_QUEUE_LIMIT
+   calls to free() is not recycled.  When you call free for the Nth
+   time, the (N - FREE_QUEUE_LIMIT)'th block is actually recycled.
+
+   For these last FREE_QUEUE_LIMIT calls to free() a backtrace is
+   saved showing where it was called from.  The function
+   find_backtrace() is provided here to be called from GDB with a
+   pointer (such as would be passed to free()) as argument, e.g.
+   (gdb) p/a *find_backtrace (0x234000).  If SAVE_ARGS is defined,
+   the first three arguments to each function are saved as well as the
+   return addresses.
+
+   If UNMAPPED_FREE is defined, instead of setting every word of freed
+   storage to 0xdeadbeef, every call to malloc goes on its own page(s).
+   When free() is called, the block is read and write protected.  This
+   is very useful when debugging, since it usually generates a bus error
+   when the deadbeef hack might only cause some garbage to be printed.
+   However, this is too slow for everyday use, since it takes an enormous
+   number of pages.
+
+
+   Some other features that would be useful are:
+
+   * Checking for storage leaks.
+     This could be done by a GC-like facility that would scan the data
+     segment looking for pointers to allocated storage and tell you
+     about those that are no longer referenced.  This could be invoked
+     at any time.  Another possibility is to report on what allocated
+     storage is still in use when the process is exited.  Typically
+     there will be a large amount, so this might not be very useful.
+*/
+
+#ifdef emacs
+#include <config.h>
+#include "lisp.h"
+#else
+void *malloc (size_t);
+#endif
+
+#if !defined(HAVE_LIBMCHECK)
+#include <stdio.h>
+
+#include "hash.h"
+
+#ifdef UNMAPPED_FREE
+#include <sys/mman.h>
+#include <sys/param.h>
+#define ROUND_UP_TO_PAGE(i) (((i) + PAGEOFFSET) & PAGEMASK)
+#endif
+
+#include <sys/types.h>
+
+/* System function prototypes don't belong in C source files */
+/* extern void free (void *); */
+
+static struct hash_table *pointer_table;
+
+extern void (*__free_hook) (void *);
+extern void *(*__malloc_hook) (size_t);
+
+static void *check_malloc (size_t);
+
+typedef void (*fun_ptr) (void);
+
+/* free_queue is not too useful without backtrace logging */
+#define FREE_QUEUE_LIMIT 1
+#define TRACE_LIMIT 20
+
+typedef struct {
+  fun_ptr return_pc;
+#ifdef SAVE_ARGS
+  void *arg[3];
+#endif
+} fun_entry;
+
+typedef struct {
+  void *address;
+  unsigned long length;
+} free_queue_entry;
+
+static free_queue_entry free_queue[FREE_QUEUE_LIMIT];
+
+static int current_free;
+
+static int strict_free_check;
+
+static void
+check_free (void *ptr)
+{
+  __free_hook = 0;
+  __malloc_hook = 0;
+  if (!pointer_table)
+    pointer_table = make_hash_table (max (100, FREE_QUEUE_LIMIT * 2));
+  if (ptr != 0)
+    {
+      long size;
+#ifdef UNMAPPED_FREE
+      unsigned long rounded_up_size;
+#endif
+
+      EMACS_INT present = (EMACS_INT) gethash (ptr, pointer_table,
+					       (CONST void **) &size);
+
+      if (!present)
+	{
+	/* This can only happen if you try to free something that didn't
+	   come from malloc */
+#if !defined(__linux__)
+	  /* I originally wrote:  "There's really no need to drop core."
+	     I have seen the error of my ways. -slb */
+	  if (strict_free_check)
+	    abort ();
+#endif
+	  printf("Freeing unmalloc'ed memory at %p\n", ptr);
+	  __free_hook = check_free;
+	  __malloc_hook = check_malloc;
+	  goto end;
+	}
+
+      if (size < 0)
+	{
+	  /* This happens when you free twice */
+#if !defined(__linux__)
+	  /* See above comment. */
+	  if (strict_free_check)
+	    abort ();
+#endif
+	  printf("Freeing %p twice\n", ptr);
+	  __free_hook = check_free;
+	  __malloc_hook = check_malloc;
+	  goto end;
+	}
+
+      puthash (ptr, (void *)-size, pointer_table);
+#ifdef UNMAPPED_FREE
+      /* Round up size to an even number of pages. */
+      rounded_up_size = ROUND_UP_TO_PAGE (size);
+      /* Protect the pages freed from all access */
+      if (strict_free_check)
+	mprotect (ptr, rounded_up_size, PROT_NONE);
+#else
+      /* Set every word in the block to 0xdeadbeef */
+      if (strict_free_check)
+	{
+	  unsigned long long_length = (size + (sizeof (long) - 1))
+	    / sizeof (long);
+	  unsigned long i;
+
+	  for (i = 0; i < long_length; i++)
+	    ((unsigned long *) ptr)[i] = 0xdeadbeef;
+	}
+#endif
+      free_queue[current_free].address = ptr;
+      free_queue[current_free].length = size;
+
+      current_free++;
+      if (current_free >= FREE_QUEUE_LIMIT)
+	current_free = 0;
+      /* Really free this if there's something there */
+      {
+	void *old = free_queue[current_free].address;
+
+	if (old)
+	  {
+#ifdef UNMAPPED_FREE
+	    unsigned long old_len = free_queue[current_free].length;
+
+	    mprotect (old, old_len,  PROT_READ | PROT_WRITE | PROT_EXEC);
+#endif
+	    free (old);
+	    remhash (old, pointer_table);
+	  }
+      }
+    }
+  __free_hook = check_free;
+  __malloc_hook = check_malloc;
+
+ end:
+  return;
+}
+
+static void *
+check_malloc (size_t size)
+{
+  size_t rounded_up_size;
+  void *result;
+
+  __free_hook = 0;
+  __malloc_hook = 0;
+  if (size == 0)
+    {
+      result = 0;
+      goto end;
+    }
+#ifdef UNMAPPED_FREE
+  /* Round up to an even number of pages. */
+  rounded_up_size = ROUND_UP_TO_PAGE (size);
+#else
+  rounded_up_size = size;
+#endif
+  result = malloc (rounded_up_size);
+  if (!pointer_table)
+    pointer_table = make_hash_table (FREE_QUEUE_LIMIT * 2);
+  puthash (result, (void *)size, pointer_table);
+  __free_hook = check_free;
+  __malloc_hook = check_malloc;
+ end:
+  return result;
+}
+
+extern void *(*__realloc_hook) (void *, size_t);
+
+#ifdef MIN
+#undef MIN
+#endif
+#define MIN(A, B) ((A) < (B) ? (A) : (B))
+
+/* Don't optimize realloc */
+
+static void *
+check_realloc (void * ptr, size_t size)
+{
+  EMACS_INT present;
+  size_t old_size;
+  void *result = malloc (size);
+
+  if (!ptr) return result;
+  present = (EMACS_INT) gethash (ptr, pointer_table, (CONST void **) &old_size);
+  if (!present)
+    {
+    /* This can only happen by reallocing a pointer that didn't
+       come from malloc. */
+#if !defined(__linux__)
+      /* see comment in check_free(). */
+      abort ();
+#endif
+      printf("Realloc'ing unmalloc'ed pointer at %p\n", ptr);
+    }
+
+  if (result == 0)
+    goto end;
+  memcpy (result, ptr, MIN (size, old_size));
+  free (ptr);
+ end:
+  return result;
+}
+
+void enable_strict_free_check (void);
+void
+enable_strict_free_check (void)
+{
+  strict_free_check = 1;
+}
+
+void disable_strict_free_check (void);
+void
+disable_strict_free_check (void)
+{
+  strict_free_check = 0;
+}
+
+/* Note: All BLOCK_INPUT stuff removed from this file because it's
+   completely gone in XEmacs */
+
+static void *
+block_input_malloc (size_t size);
+
+static void
+block_input_free (void* ptr)
+{
+  __free_hook = 0;
+  __malloc_hook = 0;
+  free (ptr);
+  __free_hook = block_input_free;
+  __malloc_hook = block_input_malloc;
+}
+
+static void *
+block_input_malloc (size_t size)
+{
+  void* result;
+  __free_hook = 0;
+  __malloc_hook = 0;
+  result = malloc (size);
+  __free_hook = block_input_free;
+  __malloc_hook = block_input_malloc;
+  return result;
+}
+
+
+static void *
+block_input_realloc (void* ptr, size_t size)
+{
+  void* result;
+  __free_hook = 0;
+  __malloc_hook = 0;
+  __realloc_hook = 0;
+  result = realloc (ptr, size);
+  __free_hook = block_input_free;
+  __malloc_hook = block_input_malloc;
+  __realloc_hook = block_input_realloc;
+  return result;
+}
+
+#ifdef emacs
+
+void disable_free_hook (void);
+void
+disable_free_hook (void)
+{
+  __free_hook = block_input_free;
+  __malloc_hook = block_input_malloc;
+  __realloc_hook = block_input_realloc;
+}
+
+void
+init_free_hook (void)
+{
+  __free_hook = check_free;
+  __malloc_hook = check_malloc;
+  __realloc_hook = check_realloc;
+  current_free = 0;
+  strict_free_check = 1;
+}
+
+void really_free_one_entry (void *, int, int *);
+
+DEFUN ("really-free", Freally_free, 0, 1, "P", /*
+Actually free the storage held by the free() debug hook.
+A no-op if the free hook is disabled.
+*/
+       (arg))
+{
+  int count[2];
+  Lisp_Object lisp_count[2];
+
+  if ((__free_hook != 0) && pointer_table)
+    {
+      count[0] = 0;
+      count[1] = 0;
+      __free_hook = 0;
+      maphash ((maphash_function)really_free_one_entry,
+               pointer_table, (void *)&count);
+      memset (free_queue, 0, sizeof (free_queue_entry) * FREE_QUEUE_LIMIT);
+      current_free = 0;
+      __free_hook = check_free;
+      XSETINT (lisp_count[0], count[0]);
+      XSETINT (lisp_count[1], count[1]);
+      return Fcons (lisp_count[0], lisp_count[1]);
+    }
+  else
+    return Fcons (make_int (0), make_int (0));
+}
+
+void
+really_free_one_entry (void *key, int contents, int *countp)
+{
+  if (contents < 0)
+    {
+      free (key);
+#ifdef UNMAPPED_FREE
+      mprotect (key, -contents, PROT_READ | PROT_WRITE | PROT_EXEC);
+#endif
+      remhash (key, pointer_table);
+      countp[0]++;
+      countp[1] += -contents;
+    }
+}
+
+void
+syms_of_free_hook (void)
+{
+  DEFSUBR (Freally_free);
+}
+
+#else
+void (*__free_hook)(void *) = check_free;
+void *(*__malloc_hook)(size_t) = check_malloc;
+void *(*__realloc_hook)(void *, size_t) = check_realloc;
+#endif
+
+#endif /* !defined(HAVE_LIBMCHECK) */
+
+#if defined(DEBUG_INPUT_BLOCKING) || defined (DEBUG_GCPRO)
+
+/* Note: There is no more input blocking in XEmacs */
+typedef enum {
+  block_type, unblock_type, totally_type,
+  gcpro1_type, gcpro2_type, gcpro3_type, gcpro4_type, gcpro5_type,
+  ungcpro_type
+} blocktype;
+
+struct block_input_history_struct
+{
+  char *file;
+  int line;
+  blocktype type;
+  int value;
+};
+
+typedef struct block_input_history_struct block_input_history;
+
+#endif /* DEBUG_INPUT_BLOCKING || DEBUG_GCPRO */
+
+#ifdef DEBUG_INPUT_BLOCKING
+
+int blhistptr;
+
+#define BLHISTLIMIT 1000
+
+block_input_history blhist[BLHISTLIMIT];
+
+note_block_input (char *file, int line)
+{
+  note_block (file, line, block_type);
+  if (interrupt_input_blocked > 2) abort();
+}
+
+note_unblock_input (char* file, int line)
+{
+  note_block (file, line, unblock_type);
+}
+
+note_totally_unblocked (char* file, int line)
+{
+  note_block (file, line, totally_type);
+}
+
+note_block (char *file, int line, blocktype type)
+{
+  blhist[blhistptr].file = file;
+  blhist[blhistptr].line = line;
+  blhist[blhistptr].type = type;
+  blhist[blhistptr].value = interrupt_input_blocked;
+
+  blhistptr++;
+  if (blhistptr >= BLHISTLIMIT)
+    blhistptr = 0;
+}
+
+#endif /* DEBUG_INPUT_BLOCKING */
+
+
+#ifdef DEBUG_GCPRO
+
+int gcprohistptr;
+#define GCPROHISTLIMIT 1000
+block_input_history gcprohist[GCPROHISTLIMIT];
+
+static void
+log_gcpro (char *file, int line, struct gcpro *value, blocktype type)
+{
+  if (type == ungcpro_type)
+    {
+      if (value == gcprolist) goto OK;
+      if (! gcprolist) abort ();
+      if (value == gcprolist->next) goto OK;
+      if (! gcprolist->next) abort ();
+      if (value == gcprolist->next->next) goto OK;
+      if (! gcprolist->next->next) abort ();
+      if (value == gcprolist->next->next->next) goto OK;
+      abort ();
+    OK:;
+    }
+  gcprohist[gcprohistptr].file = file;
+  gcprohist[gcprohistptr].line = line;
+  gcprohist[gcprohistptr].type = type;
+  gcprohist[gcprohistptr].value = (int) value;
+  gcprohistptr++;
+  if (gcprohistptr >= GCPROHISTLIMIT)
+    gcprohistptr = 0;
+}
+
+void
+debug_gcpro1 (char *file, int line, struct gcpro *gcpro1, Lisp_Object *var)
+{
+  gcpro1->next = gcprolist; gcpro1->var = var; gcpro1->nvars = 1;
+  gcprolist = gcpro1;
+  log_gcpro (file, line, gcpro1, gcpro1_type);
+}
+
+void
+debug_gcpro2 (char *file, int line, struct gcpro *gcpro1, struct gcpro *gcpro2,
+	      Lisp_Object *var1, Lisp_Object *var2)
+{
+  gcpro1->next = gcprolist; gcpro1->var = var1; gcpro1->nvars = 1;
+  gcpro2->next = gcpro1; gcpro2->var = var2; gcpro2->nvars = 1;
+  gcprolist = gcpro2;
+  log_gcpro (file, line, gcpro2, gcpro2_type);
+}
+
+void
+debug_gcpro3 (char *file, int line, struct gcpro *gcpro1, struct gcpro *gcpro2,
+	      struct gcpro *gcpro3, Lisp_Object *var1, Lisp_Object *var2,
+	      Lisp_Object *var3)
+{
+  gcpro1->next = gcprolist; gcpro1->var = var1; gcpro1->nvars = 1;
+  gcpro2->next = gcpro1; gcpro2->var = var2; gcpro2->nvars = 1;
+  gcpro3->next = gcpro2; gcpro3->var = var3; gcpro3->nvars = 1;
+  gcprolist = gcpro3;
+  log_gcpro (file, line, gcpro3, gcpro3_type);
+}
+
+void
+debug_gcpro4 (char *file, int line, struct gcpro *gcpro1, struct gcpro *gcpro2,
+	      struct gcpro *gcpro3, struct gcpro *gcpro4, Lisp_Object *var1,
+	      Lisp_Object *var2, Lisp_Object *var3, Lisp_Object *var4)
+{
+  log_gcpro (file, line, gcpro4, gcpro4_type);
+  gcpro1->next = gcprolist; gcpro1->var = var1; gcpro1->nvars = 1;
+  gcpro2->next = gcpro1; gcpro2->var = var2; gcpro2->nvars = 1;
+  gcpro3->next = gcpro2; gcpro3->var = var3; gcpro3->nvars = 1;
+  gcpro4->next = gcpro3; gcpro4->var = var4; gcpro4->nvars = 1;
+  gcprolist = gcpro4;
+}
+
+void
+debug_gcpro5 (char *file, int line, struct gcpro *gcpro1, struct gcpro *gcpro2,
+	      struct gcpro *gcpro3, struct gcpro *gcpro4, struct gcpro *gcpro5,
+	      Lisp_Object *var1, Lisp_Object *var2, Lisp_Object *var3,
+	      Lisp_Object *var4, Lisp_Object *var5)
+{
+  log_gcpro (file, line, gcpro5, gcpro5_type);
+  gcpro1->next = gcprolist; gcpro1->var = var1; gcpro1->nvars = 1;
+  gcpro2->next = gcpro1; gcpro2->var = var2; gcpro2->nvars = 1;
+  gcpro3->next = gcpro2; gcpro3->var = var3; gcpro3->nvars = 1;
+  gcpro4->next = gcpro3; gcpro4->var = var4; gcpro4->nvars = 1;
+  gcpro5->next = gcpro4; gcpro5->var = var5; gcpro5->nvars = 1;
+  gcprolist = gcpro5;
+}
+
+void
+debug_ungcpro (char *file, int line, struct gcpro *gcpro1)
+{
+  log_gcpro (file, line, gcpro1, ungcpro_type);
+  gcprolist = gcpro1->next;
+}
+
+
+/* To be called from the debugger */
+void show_gcprohist (void);
+void
+show_gcprohist (void)
+{
+  int i, j;
+  for (i = 0, j = gcprohistptr;
+       i < GCPROHISTLIMIT;
+       i++, j++)
+    {
+      if (j >= GCPROHISTLIMIT)
+	j = 0;
+      printf ("%3d  %s		%d	%s	0x%x\n",
+	      j, gcprohist[j].file, gcprohist[j].line,
+	      (gcprohist[j].type == gcpro1_type ? "GCPRO1" :
+	       gcprohist[j].type == gcpro2_type ? "GCPRO2" :
+	       gcprohist[j].type == gcpro3_type ? "GCPRO3" :
+	       gcprohist[j].type == gcpro4_type ? "GCPRO4" :
+	       gcprohist[j].type == ungcpro_type ? "UNGCPRO" : "???"),
+	      gcprohist[j].value);
+    }
+  fflush (stdout);
+}
+
+#endif /* DEBUG_GCPRO */