/* * $Id: ram-smalloc.c,v 0.41 2003/08/23 16:38:14 ceder Exp $ * Copyright (C) 1991-1996, 1998-1999, 2001-2003 Lysator Academic Computer Association. * * This file is part of the LysKOM server. * * LysKOM 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 1, or (at your option) * any later version. * * LysKOM 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 LysKOM; see the file COPYING. If not, write to * Lysator, c/o ISY, Linkoping University, S-581 83 Linkoping, SWEDEN, * or the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, * MA 02139, USA. * * Please report bugs at http://bugzilla.lysator.liu.se/. */ /* * smalloc.c * * Contains memory allocator routines * * TEST VERSION by ceder */ #ifdef HAVE_CONFIG_H # include #endif /* * Finding memory leaks * ==================== * * This code contains some stubs that can be useful when hunting for * memory leaks. To use it, you must configure with * --with-traced-allocations and recompile. You actually only need to * recompile ram-smalloc.c, ramkomd.c and the programs in testsuite * after changing it, so "make -t; rm src/server/ram-smalloc.o \ * src/server/ramkomd.o src/server/testsuite/test-*.o; make" is the * fastest way to change between tracing and a normal compile. * * Run the resulting lyskomd under gdb, like this: * bash$ gdb lyskomd * (gdb) source trace-mem.gdb * (gdb) run * Where does the trace want to go today? [stderr] RET * gdb will print a backtrace and continue whenever memory is * allocated, reallocated or released. The program itself will also * print out some information. The result is a long log file. * * Run M-x resolve-trace (from handle-malloc-dump.el on the resulting * output. Look in the *Result* buffer for the result. * * Using the test suite to find memory leaks * ========================================= * * This code can also be used together with the test suite, even * though that is slightly more complicated. Begin by starting a gdb * whose output is saved, for example by running it in a shell buffer * in emacs (note: do not use M-x gdb -- that will slow down the * process and it is slow enough as it is): * bash$ gdb lyskomd * (gdb) source trace-mem.gdb * (gdb) shell tty * /dev/ttypd * Pass ATTACH=yes and MEMTRACE to runtest. MEMTRACE should be set * to the tty where gdb is running. Start the test suite: * bash$ runtest --tool lyskomd leaks.0/99.exp ATTACH=yes MEMTRACE=/dev/ttypd * [...] * Please attach to lyskomd pid 4711 and hit RETURN * Attach to the process: * (gdb) attach 4711 * (gdb) continue * Press enter to resume the test suite. Lots of output should appear * from gdb. * * You can do all this automatically for test cases that succeed in * starting the server and only start it once. * * emacs -batch -l handle-malloc-dump.el --tool leaks --test 99.exp * * This will do the above in an Emacs, then run the trace analysis * and print the results on standard output. Use --usage to get a * summary of available options (--help was already taken by Emacs.) * * This does not work with all Emacs versions. * */ #ifdef HAVE_STDLIB_H # include #endif #include #include #include #include /* The order between inttypes.h and stdint.h is mandated by autoconf-2.57. */ #if HAVE_INTTYPES_H # include #else # if HAVE_STDINT_H # include # endif #endif #include "exp.h" #include "s-string.h" #include "kom-types.h" #include "lyskomd.h" #include "server/smalloc.h" #ifdef TRACED_ALLOCATIONS # include "trace-alloc.h" #endif #include "eintr.h" static int no_of_allocated_blocks = 0; /* When using our own malloc guard areas, the memory layout looks like this, where U is sizeof(union overhead), and S is the size of the requested block. offset what ====== ==== 0: magic cookie (stored in an union overhead) (see below) U: the size S (an union overhead) 2*U: S bytes of data returned to the user. S+2*U: guard byte: 0x89 1+S+2*U: guard byte: 0xA7 2+S+2*U: one past the end of the block allocated with malloc(). The total overhead is thus 2*U + 2. The macro OVERHEAD adds the specified overead to S and returns the size we request from the system malloc. The magic cookie reflects the status of the block and is used for defensive checks. The constants are defined below: SMALLOC_MAGIC_ALLOC: an allocated block. SMALLOC_MAGIC_FREE: a block that was previously allocated. */ #ifdef USE_MALLOC_GUARDS /* A union of "all types", used to get the maximum alignment needed for any type. */ union overhead { /* We prefer to store stuf in a size_t, since we are storing a size. Use an unsigned int if size_t isn't available. */ # ifdef HAVE_SIZE_T size_t val; unsigned int a; # else unsigned int val; # endif void *b; long c; long *d; long long e; long long *f; float g; double h; void (*i)(void); long double j; # ifdef HAVE_INTPTR_T intptr_t k; # endif # ifdef HAVE_INTMAX_T intmax_t l; # endif }; # define OVERHEAD(s) ((s) + (2*sizeof(union overhead) + 2)) # define SMALLOC_MAGIC_ALLOC 0x12FE56A0u # define SMALLOC_MAGIC_FREE 0xCA348E63u #else /* When malloc() or realloc() is asked to allocate a 0-sized area, they may return NULL to indicate success. We are not prepared for that behaviour, so we always allocate at least 1 byte. */ # define OVERHEAD(s) (((s) == 0) ? 1 : s) #endif #ifdef TRACED_ALLOCATIONS static FILE *malloc_fp = NULL; void trace_alloc_file(const char *loc) { if (loc[0] == '\0') malloc_fp = stderr; else malloc_fp = i_fopen(loc, "w"); if (malloc_fp == NULL) restart_kom("init_malloc_fp failed: %s, %d\n", loc, errno); fprintf(malloc_fp, "A new trace from lyskomd starts here\n"); } static void trace_smalloc(size_t size, void *result) { assert(malloc_fp != NULL); fprintf(malloc_fp, "smalloc:\nArg: 0x%lx\nRes: 0x%lx\n", (long)size, (long)result); fprintf(malloc_fp, "==== end ====\n"); fflush(malloc_fp); } #endif /* * "safe" malloc. Handles the case when malloc returns NULL. * smalloc cannot fail. */ EXPORT void * smalloc(size_t size) { union overhead *p; p = malloc(OVERHEAD(size)); if (p == NULL) restart_kom("Can't allocate %lu bytes.\n", (unsigned long)size); ++no_of_allocated_blocks; #ifdef USE_MALLOC_GUARDS p->val = SMALLOC_MAGIC_ALLOC; p++; p->val = size; p++; ((unsigned char *) p)[size] = 0x89; ((unsigned char *) p)[size+1] = 0xA7; #endif #ifdef TRACED_ALLOCATIONS trace_smalloc(size, p); #endif return p; } #ifdef TRACED_ALLOCATIONS static void trace_free(void *block) { assert(malloc_fp != NULL); fprintf(malloc_fp, "sfree:\nArg: 0x%lx\n", (long)block); fprintf(malloc_fp, "==== end ====\n"); fflush(malloc_fp); } #endif EXPORT void sfree(void * ptr) /* it is legal to sfree a NULL pointer */ { #ifdef USE_MALLOC_GUARDS union overhead *ip; #endif if ( ptr != NULL ) { #ifdef TRACED_ALLOCATIONS trace_free(ptr); #endif #ifndef USE_MALLOC_GUARDS free(ptr); --no_of_allocated_blocks; #else ip = (union overhead *)ptr; ip -= 2; switch (ip->val) { case SMALLOC_MAGIC_ALLOC: if (((unsigned char *) (ip+2))[ip[1].val] != 0x89 || ((unsigned char *) (ip+2))[ip[1].val+1] != 0xA7) restart_kom("SFREE: Buffer overflow, bsize = %ul\n", ip[1].val); --no_of_allocated_blocks; ip->val = SMALLOC_MAGIC_FREE; free(ip); break; case SMALLOC_MAGIC_FREE: restart_kom("SFREE: Trying to free already freed block\n"); default: restart_kom("SFREE: Illegal magic number\n"); } #endif } } #ifdef TRACED_ALLOCATIONS static void trace_srealloc(size_t size, void *arg, void *result) { assert(malloc_fp != NULL); fprintf(malloc_fp, "srealloc:\nSize: 0x%lx\nArg: 0x%lx\nRes: 0x%lx\n", (long)size, (long)arg, (long)result); fprintf(malloc_fp, "==== end ====\n"); fflush(malloc_fp); } #endif EXPORT void * srealloc(void * ptr, size_t size) /* Never fails. It is legal to */ { /* realloc the NULL ptr. */ union overhead * ip; union overhead * new_ptr; if ( ptr == NULL ) return smalloc(size); ip = (union overhead *)ptr; #ifdef USE_MALLOC_GUARDS ip -= 2; switch (ip->val) { case SMALLOC_MAGIC_ALLOC: break; case SMALLOC_MAGIC_FREE: restart_kom("SREALLOC: Trying to realloc freed block\n"); default: restart_kom("SREALLOC: Illegal magic number\n"); } if (((unsigned char *) (ip+2))[ip[1].val] != 0x89 || ((unsigned char *) (ip+2))[ip[1].val+1] != 0xA7) restart_kom("SREALLOC: Buffer overflow, osize = %ul, nsize = %lu.\n", ip[1].val, (unsigned long)size); ip->val = SMALLOC_MAGIC_FREE; #endif if ((new_ptr = realloc(ip, OVERHEAD(size))) == NULL) { restart_kom("Out of memory - can't realloc. ptr = %lu size = %lu.\n", (unsigned long)ptr, (unsigned long)size); } #ifdef USE_MALLOC_GUARDS new_ptr->val = SMALLOC_MAGIC_ALLOC; new_ptr++; new_ptr->val = size; new_ptr++; ((unsigned char *) new_ptr)[size] = 0x89; ((unsigned char *) new_ptr)[size+1] = 0xA7; #endif #ifdef TRACED_ALLOCATIONS trace_srealloc(size, ptr, new_ptr); #endif return (void *) new_ptr; } /* * Allocate temporary memory, which is automatically freed after this * atomic call. */ static void **tmp_alloc_table = NULL; static int tmp_alloc_table_size = 0; /* Size */ static int tmp_alloc_table_use = 0; /* Used size */ EXPORT void * tmp_alloc(unsigned long size) { if ( tmp_alloc_table_size <= tmp_alloc_table_use ) { /* Need to increas table. */ tmp_alloc_table = srealloc (tmp_alloc_table, ((++tmp_alloc_table_size) * sizeof (void *))); } return (tmp_alloc_table[ tmp_alloc_table_use++ ] = smalloc (size)); } /* * Free all core which is allocated with tmp_alloc(). This is called from * end_of_atomic(). */ EXPORT void free_tmp(void) { int i; for ( i = 0; i < tmp_alloc_table_use; i++ ) { sfree ( tmp_alloc_table[ i ] ); tmp_alloc_table[ i ] = NULL; } tmp_alloc_table_use = 0; } EXPORT void free_all_tmp(void) { free_tmp(); sfree( tmp_alloc_table ); tmp_alloc_table = NULL; tmp_alloc_table_size = 0; } EXPORT void dump_smalloc_counts(FILE *stat_file) { fprintf(stat_file, "---ram-smalloc.c:\n%s%d\n", "\tAllocated blocks (grand total): ", no_of_allocated_blocks); }