memory_checker.cpp

Go to the documentation of this file.
00001 #ifndef MEMORY_CHECKER_IMPLEMENTATION_FILE
00002 #define MEMORY_CHECKER_IMPLEMENTATION_FILE
00003 
00004 /*****************************************************************************\
00005 *                                                                             *
00006 *  Name   : memory_checker                                                    *
00007 *  Author : Chris Koeritz                                                     *
00008 *                                                                             *
00009 *******************************************************************************
00010 * Copyright (c) 1998-$now By Author.  This program is free software; you can  *
00011 * redistribute it and/or modify it under the terms of the GNU General Public  *
00012 * License as published by the Free Software Foundation; either version 2 of   *
00013 * the License or (at your option) any later version.  This is online at:      *
00014 *     http://www.fsf.org/copyleft/gpl.html                                    *
00015 * Please send any updates to: fred@gruntose.com                               *
00016 \*****************************************************************************/
00017 
00018 // note: parts of this have been around since at least 1998, but this code was
00019 // newly revised for memory checking in february of 2007.  --cak
00020 
00021 #ifdef ENABLE_MEMORY_HOOK
00022 
00023 #include "definitions.h"
00024 #include "log_base.h"
00025 #include "memory_checker.h"
00026 #include "mutex.h"
00027 #include "utility.h"
00028 
00029 #include <stdio.h>
00030 #include <stdlib.h>
00031 #include <string.h>
00032 
00033 const int MAXIMUM_HASH_SLOTS = 256 * KILOBYTE;
00034   // that's a whole lot of slots.  this number is basically multiplied by
00035   // the sizeof(memory_bin) to get full memory footprint.
00036 
00037 const int SINGLE_LINE_SIZE_ESTIMATE = 200;
00038   // we are guessing that the average line of memory printout will take
00039   // this many characters.  that includes the size, the pointer value,
00040   // and the location and line number.
00041 
00042 const int RESERVED_AREA = 1000;
00043   // we reserve this much space in the string generated for the memory bin
00044   // dumps.  it will be used for adding more information to the string.
00045 
00046 #define CLEAR_ALLOCATED_MEMORY
00047   // uncomment this to ensure that new memory gets its contents set to zero.
00048   // neither new nor malloc does this, but it can help finding bugs from
00049   // people re-using deallocated memory.
00050 
00051 #define MEMORY_CHECKER_STATISTICS
00052   // uncomment to enable code that analyzes how many allocations were new and
00053   // so forth.  this will make the object run a bit slower.
00054 
00055 //#define DEBUG_MEMORY_CHECKER
00056   // uncomment for super noisy version.
00057 
00059 
00060 // define the replacement new and delete operators.
00061 
00062 #include <basis/trap_new.addin>
00063 void *operator new(size_t size, char *file, int line) throw (std::bad_alloc)
00064 { return program_wide_memories().provide_memory(size, file, line); }
00065 #include <basis/untrap_new.addin>
00066 
00067 void operator delete(void *ptr) throw ()
00068 { program_wide_memories().release_memory(ptr); }
00069 
00071 
00072 // memlink is one link in a chain of memories.  it's singly linked, so our
00073 // algorithms have to explicitly remember the parent.
00074 
00075 class memlink
00076 {
00077 public:
00078   void *_chunk;  
00079 
00082   memlink *_next;  
00083   int _size;  
00084   char *_where;  
00085   int _line;  
00086 #ifdef ENABLE_CALLSTACK_TRACKING
00087   char *_stack;  
00088 #endif
00089 
00090   void construct(void *ptr, int size, char *where, int line) {
00091     _next = NIL;
00092     _chunk = ptr;
00093     _size = size;
00094     _where = strdup(where);  // uses malloc, not new, so we're safe.
00095     if (strlen(_where) > SINGLE_LINE_SIZE_ESTIMATE - 40) {
00096       // if we will not have room for the full line, we crop it.
00097       _where[SINGLE_LINE_SIZE_ESTIMATE - 40] = '\0';
00098     }
00099     _line = line;
00100 #ifdef ENABLE_CALLSTACK_TRACKING
00101     _stack = program_wide_stack_trace().full_trace();
00103 #endif
00104   }
00105 
00106   void destruct() {
00107     free(_chunk); _chunk = NIL;
00108     free(_where); _where = NIL; 
00109     _next = NIL;
00110     _size = 0;
00111     _line = 0;
00112 #ifdef ENABLE_CALLSTACK_TRACKING
00113     free(_stack); _stack = NIL;
00114 #endif
00115   }
00116 };
00117 
00119 
00120 //pretty lame here so far.
00121 #ifdef MEMORY_CHECKER_STATISTICS
00122   // simple stats: the methods below will tweak these numbers if memory_checker
00123   // statistics are enabled.  ints won't do here, due to the number of
00124   // operations in a long-running program easily overflowing that size.
00125 
00126   // this bank of statistics counts the number of times memory was treated
00127   // a certain way.
00128   double _stat_new_allocations = 0;  // this many new allocations occurred.
00129   double _stat_freed_allocations = 0;  // number of freed blocks.
00130   // next bank of stats are the sizes of the memory that were stowed, etc.
00131   double _stat_new_allocations_size = 0;  // this many bytes got allocated.
00132   double _stat_freed_allocations_size = 0;  // this many bytes were freed.
00133 #endif
00134 
00136 
00138 
00139 class memory_bin
00140 {
00141 public:
00142   void construct() {
00143     _head = NIL;
00144     _count = 0;
00145     _lock = (mutex_base *)malloc(sizeof(mutex_base));
00146     _lock->construct();
00147   }
00148   void destruct() {
00149     _lock->destruct();
00150     free(_lock);
00151   }
00152 
00153   inline int count() const { return _count; }
00154 
00155   int record_memory(void *ptr, int size, char *where, int line) {
00156     memlink *new_guy = (memlink *)malloc(sizeof(memlink));
00157     new_guy->construct(ptr, size, where, line);
00158     _lock->lock();
00159     // this code has the effect of putting more recent allocations first.
00160     // if they happen to get cleaned up right away, that's nice and fast.
00161     new_guy->_next = _head;
00162     _head = new_guy;
00163     _count++;
00164     _lock->unlock();
00165     return common::OKAY;  // seems to have worked fine.
00166   }
00167 
00168   int release_memory(void *to_release) {
00169     _lock->lock();
00170     // search the bin to locate the item specified.
00171     memlink *current = _head;  // current will scoot through the list.
00172     memlink *previous = NIL;  // previous remembers the parent node, if any.
00173     while (current) {
00174       if (current->_chunk == to_release) {
00175 #ifdef MEMORY_CHECKER_STATISTICS
00176         // record that it went away.
00177         _stat_freed_allocations += 1.0;
00178         _stat_freed_allocations_size += current->_size;
00179 #endif
00180 #ifdef DEBUG_MEMORY_CHECKER
00181         printf("found %p listed, removing for %s[%d]\n", to_release,
00182             current->_where, current->_line);
00183 #endif
00184         // unlink this one and clean up; they don't want it now.
00185         if (!previous) {
00186           // this is the head we're modifying.
00187           _head = current->_next;
00188         } else {
00189           // not the head, so there was a valid previous element.
00190           previous->_next = current->_next;
00191         }
00192         // now trash that goner's house.
00193         current->destruct();
00194         free(current);
00195         _count--;
00196         _lock->unlock();
00197         return common::OKAY;
00198       }
00199       // the current node isn't it; jump to next node.
00200       previous = current;
00201       current = current->_next;
00202     }
00203 #ifdef DEBUG_MEMORY_CHECKER
00204     printf("failed to find %p listed.\n", to_release);
00205 #endif
00206     _lock->unlock();
00207     return common::NOT_FOUND;
00208   }
00209 
00210   void dump_list(char *add_to, int &curr_size, int max_size) {
00211     int size_alloc = 2 * SINGLE_LINE_SIZE_ESTIMATE;  // room for one line.
00212     char *temp_str = (char *)malloc(size_alloc);
00213     memlink *current = _head;  // current will scoot through the list.
00214     while (current) {
00215       temp_str[0] = '\0';
00216       sprintf(temp_str, "\n\"%s[%d]\", \"size %d\", \"addr %p\"\n",
00217           current->_where, current->_line, current->_size, current->_chunk);
00218       int len_add = strlen(temp_str);
00219       if (curr_size + len_add < max_size) {
00220         strcat(add_to, temp_str);
00221         curr_size += len_add;
00222       }
00223 #ifdef ENABLE_CALLSTACK_TRACKING
00224       len_add = strlen(current->_stack);
00225       if (curr_size + len_add < max_size) {
00226         strcat(add_to, current->_stack);
00227         curr_size += len_add;
00228       }
00229 #endif
00230       current = current->_next;
00231     }
00232     free(temp_str);
00233   }
00234 
00235 private:
00236   memlink *_head;  // our first, if any, item.
00237   mutex_base *_lock;  // protects our bin from concurrent access.
00238   int _count;  // current count of items held.
00239 };
00240 
00242 
00243 class allocation_memories
00244 {
00245 public:
00246   void construct(int num_slots) {
00247     _num_slots = num_slots;
00248     _bins = (memory_bin *)malloc(num_slots * sizeof(memory_bin));
00249     for (int i = 0; i < num_slots; i++)
00250       _bins[i].construct();
00251   }
00252 
00253   void destruct() {
00254     // destroy each bin in our list.
00255     for (int i = 0; i < _num_slots; i++) {
00256       _bins[i].destruct();
00257     }
00258     free(_bins);
00259     _bins = NIL;
00260   }
00261 
00262   int compute_slot(void *ptr) {
00263     return utility::hash_bytes(&ptr, sizeof(void *)) % _num_slots;
00264   }
00265 
00266   void *provide_memory(int size_needed, char *file, int line) {
00267     void *new_allocation = malloc(size_needed);
00268     // slice and dice pointer to get appropriate hash bin.
00269     int slot = compute_slot(new_allocation);
00270 #ifdef DEBUG_MEMORY_CHECKER
00271     printf("using slot %d for %p\n", slot, new_allocation);
00272 #endif
00273     _bins[slot].record_memory(new_allocation, size_needed, file, line);
00274 #ifdef MEMORY_CHECKER_STATISTICS
00275     _stat_new_allocations += 1.0;
00276     _stat_new_allocations_size += size_needed;
00277 #endif
00278     return new_allocation;
00279   }
00280 
00281   int release_memory(void *to_drop) {
00282     int slot = compute_slot(to_drop);  // slice and dice to get bin number.
00283 #ifdef DEBUG_MEMORY_CHECKER
00284     printf("removing mem %p from slot %d.\n", to_drop, slot);
00285 #endif
00286     return _bins[slot].release_memory(to_drop);
00287   }
00288 
00290 
00294   char *report_allocations() {
00295     // count how many allocations we have overall.
00296     int full_count = 0;
00297     for (int i = 0; i < _num_slots; i++) {
00299       full_count += _bins[i].count();
00300     }
00302     // calculate a guess for how much space we need to show all of those.
00303     int alloc_size = full_count * SINGLE_LINE_SIZE_ESTIMATE + RESERVED_AREA;
00304     char *to_return = (char *)malloc(alloc_size);
00305     to_return[0] = '\0';
00306     if (full_count) {
00307       strcat(to_return, "===================\n");
00308       strcat(to_return, "Unfreed Allocations\n");
00309       strcat(to_return, "===================\n");
00310     }
00311     int curr_size = strlen(to_return);  // how much in use so far.
00312     for (int i = 0; i < _num_slots; i++) {
00313       _bins[i].dump_list(to_return, curr_size, alloc_size - RESERVED_AREA);
00314     }
00315     return to_return;
00316   }
00317 
00318   // this is fairly resource intensive, so don't dump the state out that often.
00319   char *text_form(bool show_outstanding) {
00320     char *to_return = NIL;
00321     if (show_outstanding) {
00322       to_return = report_allocations();
00323     } else {
00324       to_return = (char *)malloc(RESERVED_AREA);
00325       to_return[0] = '\0';
00326     }
00327 #ifdef MEMORY_CHECKER_STATISTICS
00328     char *temp_str = (char *)malloc(4 * SINGLE_LINE_SIZE_ESTIMATE);
00329     
00330     sprintf(temp_str, "=================\n");
00331     strcat(to_return, temp_str);
00332     sprintf(temp_str, "Memory Statistics\n");
00333     strcat(to_return, temp_str);
00334     sprintf(temp_str, "=================\n");
00335     strcat(to_return, temp_str);
00336     sprintf(temp_str, "Measurements taken across entire program runtime:\n");
00337     strcat(to_return, temp_str);
00338     sprintf(temp_str, "  %.0f new allocations.\n", _stat_new_allocations);
00339     strcat(to_return, temp_str);
00340     sprintf(temp_str, "  %.4f new Mbytes.\n",
00341         _stat_new_allocations_size / MEGABYTE);
00342     strcat(to_return, temp_str);
00343     sprintf(temp_str, "  %.0f freed deallocations.\n",
00344         _stat_freed_allocations);
00345     strcat(to_return, temp_str);
00346     sprintf(temp_str, "  %.4f freed Mbytes.\n",
00347         _stat_freed_allocations_size / MEGABYTE);
00348     strcat(to_return, temp_str);
00349 
00350     free(temp_str);
00351 #endif
00352     return to_return;
00353   }
00354 
00355 private:
00356   memory_bin *_bins;  
00357   int _num_slots;  
00358 };
00359 
00361 
00362 void memory_checker::construct()
00363 {
00364   _mems = (allocation_memories *)malloc(sizeof(allocation_memories));
00365   _mems->construct(MAXIMUM_HASH_SLOTS);
00366   _unusable = false;
00367   _enabled = true;
00368 }
00369 
00370 void memory_checker::destruct()
00371 {
00372   if (_unusable) return;  // already gone.
00373 if (!_mems) printf("memory_checker::destruct being invoked twice!\n");
00374 
00375   // show some stats about memory allocation.
00376   char *mem_state = text_form(true);
00377   printf("%s", mem_state);
00379 //the above free seems to totally die if we allow it to happen.
00380 
00381   _unusable = true;
00382 
00383   _mems->destruct();
00384   free(_mems);
00385   _mems = NIL;
00386 }
00387 
00388 void *memory_checker::provide_memory(size_t size, char *file, int line)
00389 {
00390   if (_unusable || !_enabled) return malloc(size);
00391   return _mems->provide_memory(size, file, line);
00392 }
00393 
00394 int memory_checker::release_memory(void *ptr)
00395 {
00396   if (_unusable || !_enabled) {
00397     free(ptr);
00398     return common::OKAY;
00399   }
00400   return _mems->release_memory(ptr);
00401 }
00402 
00403 char *memory_checker::text_form(bool show_outstanding)
00404 {
00405   if (_unusable) return strdup("already destroyed memory_checker!\n");
00406   return _mems->text_form(show_outstanding);
00407 }
00408 
00410 
00411 #endif  // enable memory hook
00412 
00413 #endif //MEMORY_CHECKER_IMPLEMENTATION_FILE
00414 

Generated on Sat Oct 11 04:28:38 2008 for HOOPLE Libraries by  doxygen 1.5.1