Mercurial > hg > xemacs-beta
diff src/free-hook.c @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children | 859a2309aef8 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/free-hook.c Mon Aug 13 08:45:50 2007 +0200 @@ -0,0 +1,687 @@ +/* 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. +*/ + +#if defined (EMACS_BTL) && defined (sun4) && !defined (__lucid) +/* currently only works in this configuration */ +# define SAVE_STACK +#endif + +#ifdef emacs +#ifdef SAVE_STACK +#include "cadillac-btl.h" +#endif +#include <config.h> +#include "lisp.h" +#else +void *malloc (unsigned long); +#endif + +#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 *); */ + +c_hashtable pointer_table; + +extern void (*__free_hook) (void *); +extern void *(*__malloc_hook) (unsigned long); + +static void *check_malloc (unsigned long); + +typedef void (*fun_ptr) (); + +#ifdef SAVE_STACK +#define FREE_QUEUE_LIMIT 1000 +#else +/* free_queue is not too useful without backtrace logging */ +#define FREE_QUEUE_LIMIT 1 +#endif +#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; +#ifdef SAVE_STACK + fun_entry backtrace[TRACE_LIMIT]; +#endif +} free_queue_entry; + +free_queue_entry free_queue[FREE_QUEUE_LIMIT]; + +int current_free; + +#ifdef SAVE_STACK +static void +init_frame (FRAME *fptr) +{ + FRAME tmp_frame; + +#ifdef sparc + /* Do the system trap ST_FLUSH_WINDOWS */ + asm ("ta 3"); + asm ("st %sp, [%i0+0]"); + asm ("st %fp, [%i0+4]"); +#endif + + fptr->pc = (char *) init_frame; + tmp_frame = *fptr; + + PREVIOUS_FRAME (tmp_frame); + + *fptr = tmp_frame; + return; +} + +#ifdef SAVE_ARGS +static void * +frame_arg (FRAME *fptr, int index) +{ + return ((void *) FRAME_ARG(*fptr, index)); +} +#endif + +static void +save_backtrace (FRAME *current_frame_ptr, fun_entry *table) +{ + int i = 0; +#ifdef SAVE_ARGS + int j; +#endif + FRAME current_frame = *current_frame_ptr; + + /* Get up and out of free() */ + PREVIOUS_FRAME (current_frame); + + /* now do the basic loop adding data until there is no more */ + while (PREVIOUS_FRAME (current_frame) && i < TRACE_LIMIT) + { + table[i].return_pc = (void (*)())FRAME_PC (current_frame); +#ifdef SAVE_ARGS + for (j = 0; j < 3; j++) + table[i].arg[j] = frame_arg (¤t_frame, j); +#endif + i++; + } + memset (&table[i], 0, sizeof (fun_entry) * (TRACE_LIMIT - i)); +} + +free_queue_entry * +find_backtrace (void *ptr) +{ + int i; + + for (i = 0; i < FREE_QUEUE_LIMIT; i++) + if (free_queue[i].address == ptr) + return &free_queue[i]; + + return 0; +} +#endif /* SAVE_STACK */ + +int strict_free_check; + +static void +check_free (void *ptr) +{ +#ifdef SAVE_STACK + FRAME start_frame; + + init_frame (&start_frame); +#endif + + __free_hook = 0; + __malloc_hook = 0; + if (!pointer_table) + pointer_table = make_hashtable (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, (void *) &size); + + if (!present) + /* This can only happen if you try to free something that didn't + come from malloc */ + if (strict_free_check) + abort (); + else + { + __free_hook = check_free; + __malloc_hook = check_malloc; + goto end; + } + + if (size < 0) + /* This happens when you free twice */ + if (strict_free_check) + abort (); + else + { + __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; +#ifdef SAVE_STACK + save_backtrace (&start_frame, + free_queue[current_free].backtrace); +#endif + 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 (unsigned long size) +{ + unsigned long 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_hashtable (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 *, unsigned long); + +#ifdef MIN +#undef MIN +#endif +#define MIN(A, B) ((A) < (B) ? (A) : (B)) + +/* Don't optimize realloc */ + +static void * +check_realloc (void * ptr, unsigned long size) +{ + EMACS_INT present; + unsigned long old_size; + void *result = malloc (size); + + present = (EMACS_INT) gethash (ptr, pointer_table, (void *) &old_size); + if (!present) + /* This can only happen by reallocing a pointer that didn't + come from malloc. */ + abort (); + 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 (unsigned long 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 (unsigned long 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, unsigned long 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, Sreally_free, 0, 1, "P" /* +Actually free the storage held by the free() debug hook. +A no-op if the free hook is disabled. +*/ ) + (arg) + Lisp_Object 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 (&Sreally_free); +} + +#else +void (*__free_hook)() = check_free; +void *(*__malloc_hook)() = check_malloc; +void *(*__realloc_hook)() = check_realloc; +#endif + + +#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, ungcpro_type +} blocktype; + +struct block_input_history_struct { + char *file; + int line; + blocktype type; + int value; +#ifdef SAVE_STACK + fun_entry backtrace[TRACE_LIMIT]; +#endif +}; + +typedef struct block_input_history_struct block_input_history; + +#endif + +#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) +{ +#ifdef SAVE_STACK + FRAME start_frame; + + init_frame (&start_frame); +#endif + + blhist[blhistptr].file = file; + blhist[blhistptr].line = line; + blhist[blhistptr].type = type; + blhist[blhistptr].value = interrupt_input_blocked; + +#ifdef SAVE_STACK + save_backtrace (&start_frame, + blhist[blhistptr].backtrace); +#endif + + blhistptr++; + if (blhistptr >= BLHISTLIMIT) + blhistptr = 0; +} + +#endif + + +#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) +{ + FRAME start_frame; + + 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:; + } +#ifdef SAVE_STACK + init_frame (&start_frame); +#endif + gcprohist[gcprohistptr].file = file; + gcprohist[gcprohistptr].line = line; + gcprohist[gcprohistptr].type = type; + gcprohist[gcprohistptr].value = (int) value; +#ifdef SAVE_STACK + save_backtrace (&start_frame, gcprohist[gcprohistptr].backtrace); +#endif + 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; +} + +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