/** @file vmalloc.C
@brief Validating malloc routines
@author Patrick Audley <paudley@blackcat.ca>
@license_gpl
*/
/*
Copyright 1995-2003 Patrick C. Audley, Geoff J. Barton, Robert B. Russel
This program 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 of the License, or
(at your option) any later version.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <string.h>
#include <stdarg.h>
#include <map>
#include <vector>
#include <string>
#include "vmalloc.h"
#ifdef VMALLOC_ALWAYS
#error "You can't use Vmalloc on Vmalloc... bad things will happen."
#endif
unsigned int Vmalloc_verbose = 0;
//! Bookkeeping structure for _Vmalloc() and _Vfree()
struct mem_region {
void* ptr; //!< address of our region
unsigned long size; //!< size requested
char file[64]; //!< file name of the caller
char note[128]; //!< note for this region
long line; //!< line number of the caller
long pass; //!< This is the n-th allocation from this location
bool freed; //!< Whether we've been free'd yet
bool realloced; //!< Whether we've been realloc'ed (and have info in Vmem_reallocs)
void* replaced; //!< If we've been realloc'ed and realloc replaced us, what did it replace us with?
};
/** Null mem_region for use in _Vmemget()
@internal */
mem_region vmem_null;
//! Bookeeping map of allocations for _Vmalloc() and _Vfree()
typedef std::map<void*,mem_region> Vmem_acct_t;
//! Vector of allocations for _Vrealloc()
typedef std::vector<mem_region> Vmem_realloc_t;
//! Our map for use in the functions below.
Vmem_acct_t Vmem_acct;
//! Our map of previous frees
Vmem_acct_t Vmem_frees;
//! map of hashed file and line positions and number allocations seen from there
std::map<unsigned long,unsigned long> Vmem_seen;
//! map of vectors of reallocation information
std::map<void *,Vmem_realloc_t> Vmem_reallocs;
//! if we are validating then we need to setup account once. This is whether we have or not.
static bool Vmem_accounting_setup = false;
//! Check bytes for head of allocations
const long Vchk_head = 0xABBACACC;
//! Check bytes for tail of allocations
const long Vchk_tail = 0xDEADBEEF;
//! Check bytes for head of reallocations
const long Vchk_realloc = 0xBEA110CA;
//! should we process the accounting lists?
static bool Vchk_accounting = true;
/** Whether we've built the crc_table or not. */
static bool crc_init = false;
/** CRC32 table */
static unsigned long crc_table[256];
/** Build the crc_table */
static void gen_table(void) {
unsigned long crc, poly = 0xEDB88320L; for (long i = 0; i < 256; i++) { crc = i; for (long j = 8; j > 0; j--) { if (crc & 1) crc = (crc >> 1) ^ poly; else crc >>= 1; } crc_table[i] = crc; }
}
/** @brief calculate crc32 of a memory region.
@param buf pointer to memory to hash
@param len size of memory to hash
@return the crc32 of the memory region
Used to index the Vmem_seen map.
*/
static unsigned long get_crc( char* buf, size_t len ) {
register unsigned long crc = 0xFFFFFFFF;
if( !crc_init ) gen_table();
for( long i = 0; i < len; i++ )
crc = ((crc>>8) & 0x00FFFFFF) ^ crc_table[ (crc^buf[i]) & 0xFF ];
return( crc^0xFFFFFFFF );
}
//! buffer for Vmemprint()
static char *memprint_buf;
//! size to allocate for memprint_buf
#define MEMPRINT_BUFSIZE 8192
/** given a mem_rgion, return a string suitable for printing
@param M region to display
@return a pointer to memprint_buf
@internal
*/
static char *Vmemprint( mem_region &M ) {
snprintf( memprint_buf, MEMPRINT_BUFSIZE, "%s:%ld %s%s%spass %lu [%ld bytes]",
M.file, M.line,
M.note[0] != 0x00 ? "(" : "",
M.note[0] != 0x00 ? M.note : "",
M.note[0] != 0x00 ? ") " : "",
M.pass,
M.size
);
if( M.replaced ) {
Vmem_realloc_t &V = Vmem_reallocs[ M.ptr ];
unsigned long last_size = M.size;
char* temp_buf = (char*)malloc( MEMPRINT_BUFSIZE );
if( !temp_buf ) {
fprintf( stderr, "internal malloc failed in Vmemprint!\n" );
exit(EXIT_FAILURE);
}
for( Vmem_realloc_t::iterator i = V.begin();
i != V.end();
i++ ) {
snprintf( temp_buf, MEMPRINT_BUFSIZE, "%s\n -> realloced at %s:%d %s%s%sfrom %ld to %ld bytes, old address was %p",
memprint_buf, (*i).file, (*i).line,
(*i).note[0] != 0x00 ? "(" : "",
(*i).note[0] != 0x00 ? (*i).note : "",
(*i).note[0] != 0x00 ? ") " : "",
(*i).size, M.size, (*i).replaced );
strncpy( memprint_buf, temp_buf, MEMPRINT_BUFSIZE );
}
free( temp_buf );
}
return memprint_buf;
}
/** fetch refernce to mem_region for ptr
@param ptr pointer to region to search for
@param func string of caller's name
@param file string of caller's caller's file
@param line number of caller's caller's line
@param peek if true then exit the program if we don't find the region
@return mem_region reference
@internal
*/
static mem_region &Vmemget( void* ptr, const char* func, const char* file, const long line, bool peek = false ) {
if( Vmem_acct.find( ptr ) == Vmem_acct.end() ) {
if( peek ) {
memset( &vmem_null, 0x00, sizeof(mem_region) );
return vmem_null;
}
// This means we don't have a record of this allocation...
fprintf( stderr, "%s called at %s:%ld to free a chunk of memory at %p that wasn't allocated with Vmalloc!\n",
func, file, line, ptr );
VhexdumpPoint( (char*)ptr-16, 32, ptr );
Vchk_accounting = false;
exit(EXIT_FAILURE);
}
return Vmem_acct[ ptr ];
}
/** given a point, return a strdup'ed suitable for printing.
@param ptr region to print information about
@param file string of caller's caller's file
@param line number of caller's caller's line
@return strdup'ed string containing information suitable for printing or @c NULL if the region isn't managed by Vmalloc.
@note
This function calles Vstrdup() internally and it's return value must be Vfree()'d
*/
char *_Vmemprint( void* ptr, const char* file, const long line ) {
mem_region M = Vmemget( ptr, "_Vmemprint", file, line, true ); // Just peek, don't abort.
if( strlen( M.file ) == 0 ) // Region wasn't allocated via Vmalloc()
return NULL;
return _Vstrdup( Vmemprint( M ), file, line );
}
/** @brief atexit hook function to check the blc memory accounting if we are validating.
This function is registered with atexit() and called on program exit.
*/
void Vmem_accounting_check( void ) {
if( !Vchk_accounting ) return;
if( Vmalloc_verbose ) printf( "checking memory...\n" );
// Walk the account map and find any memory that wasn't freed.
for( Vmem_acct_t::iterator i = Vmem_acct.begin();
i != Vmem_acct.end();
i++ )
{
if( !i->second.freed && !i->second.realloced ) {
fprintf( stderr, "found unfreed allocation from %s at %p\n",
Vmemprint( i->second ), i->first );
}
}
free( memprint_buf );
}
/** @brief internal strdup routing
@param str string to copy
@param file we are called from
@param line we are called from
@return malloc'ed pointer to shiny new memory containing your string
@sa Vstrdup()
@attention
This routine should be called via the Vstrdup() macro only.
This strdup wrapper uses _Vmalloc() if we are in validating mode.
*/
char* _Vstrdup( const char* str, const char* file, const long line ) {
// If we are validating then use _Vmalloc() instead of calling strdup.
char* ret = (char*)_Vmalloc( strlen( str ) + 1, file, line );
strcpy( ret, str );
return ret;
}
/** @brief internal realloc routine
@param ptr region of memory to resize
@param size new size of the region
@param file we are called from
@param line we are called from
@return malloc'ed pointer to shiny new resized memory
@sa _Vmalloc() Vrealloc()
@attention
This routine should be called via the Vrealloc() macro only.
@note
Passing zero or less as @c size will result in the memory being freed.
This realloc wrapper changes the size of memory that has been
allocated previously by malloc. Reallocs are tracked for a given
region and can be displayed via Vmemprint.
*/
void* _Vrealloc( void * ptr, size_t size, const char* file, const long line ) {
// verify the args.
if( size < 1 ) {
if( Vmalloc_verbose ) printf( "Vrealloc called with a zero size, calling Vfree from %s:%ld\n", file, line );
_Vfree( ptr, file, line ); // Emulate realloc(ptr,0) behaviour of calling free
return NULL;
}
// Check the region
_Vmemcheck( ptr, file, line, "checking during realloc" );
// Look up the existing region
mem_region &M = Vmemget( ptr, "Vfree", file, line );
void *orig_ptr = (char*)ptr - sizeof(long);
unsigned long orig_size = M.size;
// Ignore request to change size to the current size
if( size == M.size ) {
if( Vmalloc_verbose ) printf( "Vrealloc called with size == current region size of %ld, doing nothing on %s:%ld\n", size, file, line );
return ptr;
}
/* Ok.. this is sneaky. Here we add a new checkbyte at the beginning. Before realloc it should look like:
* 0xABBACACC .... 0xDEADBEEF
* After Vrealloc it will be:
* 0xBEA110CA 0xABBACACC ... 0xDEADBEEF
* This allows us to detect attempt the following error:
* void* ptr = malloc( 10 );
* realloc( ptr, 20 );
* free( ptr );
* realloc(3) reserves the right to move the memory to somewhere else and return a ptr that different then it's
* argument. Programmers should update use:
* ptr = realloc(ptr,size)
* and we catch this even when the memory region isn't moved by realloc.
*/
void *new_ptr = realloc( orig_ptr, size + sizeof(long)*3 );
if( !new_ptr ) {
fprintf( stderr, "Can't re-allocate memory on %s:%ld.\n", file, line );
Vchk_accounting = false;
exit(EXIT_FAILURE);
}
// Move the memory up one byte
memmove( (long*)new_ptr+1, (long*)new_ptr, M.size+sizeof(long)*2 );
// Install the realloc check byte
memcpy( (char*)new_ptr, &Vchk_realloc, sizeof(long) );
// clear the new memory
if( size > M.size )
memset( (char*)new_ptr + M.size + sizeof(long)*2, 0x00, size - M.size + sizeof(long));
// Update the tail bytes
memcpy( (char*)new_ptr + size + sizeof(long)*2, &Vchk_tail, sizeof(long) );
// Fudge new_ptr to skip the head bytes
new_ptr = (char*)new_ptr + sizeof(long)*2;
void* rep_ptr = NULL;
// update the accounting
mem_region R;
mem_region N = M;
memset( &R, 0x00, sizeof(mem_region));
R.size = N.size;
R.line = line;
strncpy( R.file, file, 64 );
if( strlen( N.note ) == 0 )
snprintf( R.note, 128, "reallocated at %s:%ld from %ld to %ld bytes, old ptr was %p, new one is %p", file, line, M.size, R.size, ptr, new_ptr );
else
strncpy( R.note, N.note, 128 );
if( N.replaced )
R.replaced = N.replaced;
else
R.replaced = orig_ptr;
// Change the mem_region.
N.size = size;
R.realloced = true;
N.realloced = false;
N.replaced = orig_ptr;
if( orig_ptr == new_ptr )
// We didn't get a new pointer.
// Copy the old record, remove it, add it back with the new ptr
Vmem_acct.erase( Vmem_acct.find( orig_ptr ) );
// Store the reallocation
Vmem_reallocs[ R.replaced ].push_back( R );
// Store the new region
Vmem_acct[ new_ptr ] = N;
M.size = size;
M.realloced = true;
if( Vmalloc_verbose ) printf( "Vmalloc reallocated %p from %lu to %lu bytes for %s:%ld\n",
new_ptr, orig_size, size, file, line );
// replace the tail check bytes
return new_ptr;
}
/** @brief internal malloc routine
@param size number of bytes to allocate
@param file we are called from
@param line we are called from
@return malloc'ed pointer to shiny new memory
@sa _Vfree() Vmalloc()
@attention
This routine should be called via the Vmalloc() macro only.
This malloc wrapper clears it's memory afterward, checks for
errors, and does extra validation if we are in validating mode.
*/
void* _Vmalloc( size_t size, const char* file, const long line ) {
if( Vmem_accounting_setup == false ) {
// setup accounting
memprint_buf = (char*)malloc(MEMPRINT_BUFSIZE);
atexit( &Vmem_accounting_check );
Vmem_accounting_setup = true;
}
// Check for invalid sizes
if( size < 1 ) {
fprintf( stderr, "Invalid size in memory allocation on %s:%ld.\n", file, line );
Vchk_accounting = false;
exit(EXIT_FAILURE);
}
// Add room for the head+tail bytes and called malloc.
void* ptr = malloc( size + sizeof(long)*2 );
if( !ptr ) {
fprintf( stderr, "Can't allocate memory on %s:%ld.\n", file, line );
Vchk_accounting = false;
exit(EXIT_FAILURE);
}
// Clear the memory
memset( ptr, 0x00, size + sizeof(long)*2 );
// Fudge ptr to skip the head bytes
(char*)ptr += sizeof(long);
// Do some bookkeeping.
struct mem_region M;
memset( &M, 0x00, sizeof( mem_region ) );
M.ptr = (char*)ptr - sizeof(long);
M.size = size;
strncpy( M.file, file, 64 );
M.line = line;
snprintf( memprint_buf, MEMPRINT_BUFSIZE, "%s:%lu", file, line );
M.pass = Vmem_seen[ get_crc(memprint_buf,strlen(memprint_buf)) ]++;
M.freed = false;
// Add the head byte
memcpy( (char*)ptr - sizeof(long), &Vchk_head, sizeof(long) );
// Add the tail byte
memcpy( (char*)ptr + M.size, &Vchk_tail, sizeof(long) );
// Record the allocation
Vmem_acct[ ptr ] = M;
if( Vmalloc_verbose ) printf( "Vmalloc allocated %lu bytes at %p for %s:%ld\n",
size, ptr, file, line );
return ptr;
}
/** @brief internal memory freeing routine
@param ptr pointer to memory to be free'd
@param file we are called from
@param line we are called from
@sa _Vmalloc() Vfree()
@attention
This routine should be called via the Vmalloc() macro only.
This free wrapper checks for errors and does extra validation.
*/
void _Vfree( void * ptr, const char* file, const long line ) {
if( ptr == NULL ) return;
// Check the books for this allocations.
mem_region &M = Vmemget( ptr, "Vfree", file, line );
// Check for attempts to free a realloced region.
if( M.realloced ) {
fprintf(stderr, "Vfree was called at %s:%ld with a pointer that had previously been realloced, %p\n -> %s\n",
file, line, ptr, Vmemprint( M ) );
Vchk_accounting = false;
exit(EXIT_FAILURE);
}
// Check for attempts to free a freed region.
if( M.freed ) {
mem_region F = Vmem_frees[ ptr ];
fprintf(stderr, "Vfree was called at %s:%ld with a pointer that had previously been freed, %p\n -> freed at %s:%ld\n -> %s\n",
file, line, ptr, F.file, F.line, Vmemprint( M ) );
Vchk_accounting = false;
exit(EXIT_FAILURE);
}
// Check the region first
_Vmemcheck( ptr, file, line, "checking during free" );
// Adjust the ptr back to being what malloc originally gave us.
void* full_ptr = (char*)ptr - sizeof(long) * (M.replaced ? 2 : 1 );
// Ok, mark us as done.
M.freed = true;
// Clear the memory so that if the caller tries to access it later they get NULL's
memset( full_ptr, 0x00, M.size + sizeof(long) * (M.replaced ? 2 : 1 ) );
// Add it to the free list
mem_region F = M;
strncpy( F.file, file, 64 );
F.line = line;
Vmem_frees[ ptr ] = F;
if( Vmalloc_verbose ) printf( "Vfree reclaimed %lu bytes at %p for %s:%ld\n",
M.size, ptr, file, line );
// Actually free the memory.
free( full_ptr );
}
/** @brief Add a note to an internal memory allocation for tracking down memory problems.
@param ptr pointer to memory to be annotated
@param file we are called from
@param line we are called from
@param fmt note to attach
@sa Vmemnote()
@attention
This routine should be called via the Vmemnote() macro only.
This routing add a note to a region of memory allocated via
Vmalloc(). This note will be displayed in any error messages
associated with this region.
*/
void _Vmemnote( void *ptr, const char* file, const long line, const char* fmt, ... ) {
mem_region &M = Vmemget( ptr, "Vmemnote", file, line );
va_list va;
va_start( va, fmt );
vsnprintf( memprint_buf, MEMPRINT_BUFSIZE, fmt, va );
va_end( va );
strncpy( M.note, memprint_buf, 128 );
}
/** @brief internal memory checking routine
@param ptr pointer to memory to be checked
@param file we are called from
@param line we are called from
@param fmt note to attach
@sa Vmemcheck()
@attention
This routine should be called via the Vmemcheck() macro only.
This routing checks the head and tail check bytes on a given
region of memory. It will exit if errors are found.
*/
void _Vmemcheck( void *ptr, const char* file, const long line, const char* const fmt, ... ) {
bool error = false;
bool error_in_tail = false;
char *buf;
mem_region &M = Vmemget( ptr, "Vmemcheck", file, line );
// Allocate our buffer
buf = (char*)malloc(MEMPRINT_BUFSIZE);
// Process the args
va_list va;
va_start( va, fmt );
vsnprintf( buf, 2048, fmt, va );
va_end( va );
// Check the head bytes
if( memcmp( (char*)ptr - sizeof(long), &Vchk_head, sizeof(long) ) != 0 ) {
error_in_tail = false;
error = true;
}
// Check the tail bytes
if( memcmp( (char*)ptr + M.size, &Vchk_tail, sizeof(long) ) != 0 ) {
error_in_tail = true;
error = true;
}
if( error ) {
fprintf( stderr, "\nVmemcheck called from %s:%ld detected an %s on memory at %p:\n -> allocated at %s\n",
file, line, error_in_tail ? "overrun" : "underrun", ptr, Vmemprint(M) );
fprintf( stderr, "%s\n", VhexdumpPointS( (char*)(error_in_tail ? (char*)ptr + M.size : ptr ) - 16, 32, (error_in_tail ? (char*)ptr + M.size : ptr ) ).c_str() );
fprintf( stderr, "%s check bytes were 0x%08X when they should have been 0x%08X.\n",
error_in_tail ? "Tail" : "Head",
error_in_tail ? *(long*)((char*)ptr+M.size) : *((long*)((char*)ptr-sizeof(long))),
error_in_tail ? Vchk_tail : Vchk_head
);
fprintf( stderr, "Message: %s\n", buf );
Vchk_accounting = false;
free( buf );
exit(EXIT_FAILURE);
}
free( buf );
}
/** dump a region of memory to stdout
@param ptr pointer to start display at
@param size size of region to display
*/
void Vhexdump( const void* ptr, const long size ) { VhexdumpPoint( ptr, size, NULL ); }
/** dump a region of memory to stdout, point out a specific address
@param ptr pointer to start display at
@param size size of region to display
@param point_out pointer to display as a marker, NULL if you don't want a marker.
*/
void VhexdumpPoint( const void* ptr, const long size, void* point_out ) { printf("%s\n",VhexdumpPointS( ptr, size, point_out ).c_str() ); }
/** @brief return a string containing a hexdump of a memory region.
@param ptr pointer to start display at
@param size size of region to display
@param point_out pointer to display as a marker, NULL if you don't want a marker.
@return string object containing the hexdump suitable for printing
@attention
This function is available from C++ only.
*/
std::string VhexdumpPointS( const void* ptr, const long size, void* point_out ) {
std::string hex;
if( ptr == NULL ) {
hex = " 0x00000000 - cowardly refusing to dump a NULL pointer.\n";
return hex;
}
char *buf = (char*)ptr;
char cbuf[64];
char pbuf[9]; memset(pbuf,0x00,9);
short pos = 0;
short loc = 0;
bool seen_point = false;
for( unsigned int i=0; i<size; i++ ) {
if( loc > 7 ) {
loc = 0;
hex += " - ";
snprintf(cbuf,64,"0x%08lx",*(long*)(buf+pos-8));
hex += cbuf;
hex += " ";
snprintf(cbuf,64,"0x%08lx",*(long*)(buf+pos-4));
hex += cbuf;
hex += " - ";
hex += pbuf;
hex += "\n";
if( point_out
&& (long)((char*)point_out - (char*)(buf+pos-8)) < 8
&& (long)((char*)point_out - (char*)(buf+pos-8)) >= 0
) {
seen_point = true;
long ptr_pos = (long)((char*)point_out - (char*)(buf+pos-8));
snprintf(cbuf,16,"*%p",point_out);
hex += cbuf;
hex += " -> ";
for( long n = 0; n < 8; n++ ) {
if( n == 4 )
hex += " ";
if( n == ptr_pos )
hex += "^";
hex += " ";
}
hex += "\n";
}
}
if( loc == 4 )
hex += " ";
if( loc == 0 ) {
snprintf(cbuf,16," %p",&buf[pos]);
hex += cbuf;
hex += " ->";
}
unsigned char pbyte = (unsigned char)buf[pos];
snprintf(cbuf,16," %02x",pbyte); hex += cbuf;
if( pbyte > 32 && pbyte < 127 )
pbuf[loc] = pbyte;
else
pbuf[loc] = '.';
loc++;
pos++;
}
if( loc < 9 ) {
for( int i = loc; i < 8; i++ ) {
pbuf[i] = ' ';
if( i == 4 )
hex += " ";
hex += " --";
}
hex += " ";
printf( "loc = %ld\n", loc );
if( loc >= 4 ) {
snprintf(cbuf,64,"0x%08lx",*(long*)(buf+pos-loc));
hex += cbuf;
} else
hex += "0xNA ";
hex += " ";
if( loc >= 8 ) {
snprintf(cbuf,64,"0x%08lx",*(long*)(buf+pos-loc+4));
hex += cbuf;
} else
hex += "0xNA ";
hex += " - ";
hex += pbuf;
if( point_out
&& (long)((char*)point_out - (char*)(buf+pos-8)) < 8
&& (long)((char*)point_out - (char*)(buf+pos-8)) >= 0
) {
seen_point = true;
long ptr_pos = (long)((char*)point_out - (char*)(buf+pos-8));
snprintf(cbuf,16,"\n*%p",point_out);
hex += cbuf;
hex += " -> ";
for( long n = 0; n < 8; n++ ) {
if( n == 4 )
hex += " ";
if( n == ptr_pos )
hex += "^";
hex += " ";
}
}
}
if( point_out && !seen_point ) {
snprintf(cbuf,16,"\n*%p",point_out);
hex += cbuf;
hex += " -> NOT FOUND!\n";
}
return hex;
}
/** @example vmalloc1.C Example of malloc'ing a C++ class and failing to free it. */
/** @example vmalloc2.c Example of failing to free a memory region that also illustrates the use of Vmemnote(). */
/** @example vmalloc3.c Example of underrun detection. */
/** @example vmalloc4.c Example of overrun detection. */
/** @example vmalloc5.c Example of the Vrealloc() and Vhexdump() */
/** @example vmalloc6.c Example of double freeing a Vmalloc()'ed pointer */
syntax highlighted by Code2HTML, v. 0.9.1