diff --git a/ChangeLog b/ChangeLog index d432a48d7..fb56e4f59 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,24 @@ +2005-02-22 David Turner + + * src/base/ftdbgmem.c: adding the ability to list all allocation sites + in the memory debugger. Also a new function FT_DumpMemory() was added. + It is only available in builds with FT_DEBUG_MEMORY defined, and you + must declare it in your own code to use it, i.e. with something + like: + + extern void FT_DumpMemory( FT_Memory ); + + ... + + FT_DumpMemory( memory ); + + * include/freetype/config/ftoptions.h: disabling TrueType bytecode + interpreter ! + + * include/freetype/internal/ftmemory.h: adding FT_ARRAY_ZERO, as a + convenience macro. + + 2005-02-20 Werner Lemberg * builds/unix/ltmain.sh: Regenerated with `libtoolize --force diff --git a/include/freetype/config/ftoption.h b/include/freetype/config/ftoption.h index 319c412ff..1d9efe4c5 100644 --- a/include/freetype/config/ftoption.h +++ b/include/freetype/config/ftoption.h @@ -436,7 +436,7 @@ FT_BEGIN_HEADER /* Do not #undef this macro here, since the build system might */ /* define it for certain configurations only. */ /* */ -#define TT_CONFIG_OPTION_BYTECODE_INTERPRETER +/* #define TT_CONFIG_OPTION_BYTECODE_INTERPRETER */ /*************************************************************************/ diff --git a/include/freetype/internal/ftmemory.h b/include/freetype/internal/ftmemory.h index 417d1483e..e945f76d5 100644 --- a/include/freetype/internal/ftmemory.h +++ b/include/freetype/internal/ftmemory.h @@ -260,6 +260,9 @@ FT_BEGIN_HEADER #define FT_ZERO( p ) FT_MEM_ZERO( p, sizeof ( *(p) ) ) +#define FT_ARRAY_ZERO( dest, count ) \ + FT_MEM_ZERO( dest, (count)*sizeof( *(dest) ) ) + #define FT_ARRAY_COPY( dest, source, count ) \ FT_MEM_COPY( dest, source, (count) * sizeof( *(dest) ) ) diff --git a/src/base/ftdbgmem.c b/src/base/ftdbgmem.c index 61227b64a..97757851f 100644 --- a/src/base/ftdbgmem.c +++ b/src/base/ftdbgmem.c @@ -27,28 +27,63 @@ #ifdef FT_DEBUG_MEMORY +#define KEEPALIVE /* keep-alive means that free-d blocks aren't released + * to the heap. This is useful to detect double-frees + * or weird heap corruption, but it will use gobs of + * memory however. + */ #include #include + extern void + FT_DumpMemory( FT_Memory memory ); - typedef struct FT_MemNodeRec_* FT_MemNode; - typedef struct FT_MemTableRec_* FT_MemTable; + typedef struct FT_MemSourceRec_* FT_MemSource; + typedef struct FT_MemNodeRec_* FT_MemNode; + typedef struct FT_MemTableRec_* FT_MemTable; #define FT_MEM_VAL( addr ) ((FT_ULong)(FT_Pointer)( addr )) + typedef struct FT_MemSourceRec_ + { + const char* file_name; + long line_no; + + FT_Long cur_blocks; /* current number of allocated blocks */ + FT_Long max_blocks; /* max. number of allocated blocks */ + FT_Long all_blocks; /* total number of blocks allocated */ + + FT_Long cur_size; /* current cumulative allocated size */ + FT_Long max_size; /* maximum cumulative allocated size */ + FT_Long all_size; /* total cumulative allocated size */ + + FT_Long cur_max; /* current maximum allocated size */ + + FT_UInt32 hash; + FT_MemSource link; + + } FT_MemSourceRec; + +/* we don't need a resizable array for the memory sources, because + * their number is pretty limited within FreeType. + */ +#define FT_MEM_SOURCE_BUCKETS 128 + + typedef struct FT_MemNodeRec_ { - FT_Byte* address; - FT_Long size; /* < 0 if the block was freed */ + FT_Byte* address; + FT_Long size; /* < 0 if the block was freed */ - const char* alloc_file_name; - FT_Long alloc_line_no; + FT_MemSource source; - const char* free_file_name; - FT_Long free_line_no; +#ifdef KEEPALIVE + const char* free_file_name; + FT_Long free_line_no; +#endif - FT_MemNode link; + FT_MemNode link; } FT_MemNodeRec; @@ -64,15 +99,19 @@ FT_ULong alloc_max; FT_ULong alloc_count; - FT_Bool bound_total; + FT_Bool bound_total; FT_ULong alloc_total_max; - + FT_Bool bound_count; FT_ULong alloc_count_max; + FT_MemSource sources[ FT_MEM_SOURCE_BUCKETS ]; + const char* file_name; FT_Long line_no; + FT_Bool keep_alive; + FT_Memory memory; FT_Pointer memory_user; FT_Alloc_Func alloc; @@ -88,6 +127,10 @@ #define FT_FILENAME( x ) ((x) ? (x) : "unknown file") + /* I hate these prime numbers. I'd better implement L-Hashing + * which is 10% faster and doesn't require divisions, but + * I'm too lazy at the moment. + */ static const FT_UInt ft_mem_primes[] = { 7, @@ -128,6 +171,21 @@ }; + static FT_ULong + ft_mem_closest_prime( FT_ULong num ) + { + FT_UInt i; + + + for ( i = 0; + i < sizeof ( ft_mem_primes ) / sizeof ( ft_mem_primes[0] ); i++ ) + if ( ft_mem_primes[i] > num ) + return ft_mem_primes[i]; + + return FT_MEM_SIZE_MAX; + } + + extern void ft_mem_debug_panic( const char* fmt, ... ) @@ -146,21 +204,6 @@ } - static FT_ULong - ft_mem_closest_prime( FT_ULong num ) - { - FT_UInt i; - - - for ( i = 0; - i < sizeof ( ft_mem_primes ) / sizeof ( ft_mem_primes[0] ); i++ ) - if ( ft_mem_primes[i] > num ) - return ft_mem_primes[i]; - - return FT_MEM_SIZE_MAX; - } - - static FT_Pointer ft_mem_table_alloc( FT_MemTable table, FT_Long size ) @@ -209,7 +252,7 @@ if ( new_buckets == NULL ) return; - FT_MEM_ZERO( new_buckets, sizeof ( FT_MemNode ) * new_size ); + FT_ARRAY_ZERO( new_buckets, new_size ); for ( i = 0; i < table->size; i++ ) { @@ -250,7 +293,7 @@ if ( table == NULL ) goto Exit; - FT_MEM_ZERO( table, sizeof ( *table ) ); + FT_ZERO( table ); table->size = FT_MEM_SIZE_MIN; table->nodes = 0; @@ -267,7 +310,7 @@ memory->alloc( memory, table->size * sizeof ( FT_MemNode ) ); if ( table->buckets ) - FT_MEM_ZERO( table->buckets, sizeof ( FT_MemNode ) * table->size ); + FT_ARRAY_ZERO( table->buckets, table->size ); else { memory->free( memory, table ); @@ -284,13 +327,15 @@ { FT_ULong i; + FT_DumpMemory( table->memory ); if ( table ) { FT_Long leak_count = 0; FT_ULong leaks = 0; - + /* remove all blocks from the table, revealing leaked ones + */ for ( i = 0; i < table->size; i++ ) { FT_MemNode *pnode = table->buckets + i, next, node = *pnode; @@ -306,8 +351,8 @@ printf( "leaked memory block at address %p, size %8ld in (%s:%ld)\n", node->address, node->size, - FT_FILENAME( node->alloc_file_name ), - node->alloc_line_no ); + FT_FILENAME( node->source->file_name ), + node->source->line_no ); leak_count++; leaks += node->size; @@ -318,7 +363,7 @@ node->address = NULL; node->size = 0; - free( node ); + ft_mem_table_free( table, node ); node = next; } table->buckets[i] = 0; @@ -329,17 +374,33 @@ table->size = 0; table->nodes = 0; + /* remove all sources + */ + for ( i = 0; i < FT_MEM_SOURCE_BUCKETS; i++ ) + { + FT_MemSource source, next; + + for ( source = table->sources[i]; source != NULL; source = next ) + { + next = source->link; + ft_mem_table_free( table, source ); + } + + table->sources[i] = NULL; + } + printf( "FreeType: total memory allocations = %ld\n", table->alloc_total ); printf( "FreeType: maximum memory footprint = %ld\n", table->alloc_max ); - free( table ); + ft_mem_table_free( table, table ); if ( leak_count > 0 ) ft_mem_debug_panic( "FreeType: %ld bytes of memory leaked in %ld blocks\n", leaks, leak_count ); + printf( "FreeType: No memory leaks detected!\n" ); } } @@ -371,6 +432,55 @@ } + static FT_MemSource + ft_mem_table_get_source( FT_MemTable table ) + { + FT_UInt32 hash; + FT_MemSource node, *pnode; + + hash = (FT_UInt32)(void*)table->file_name + (FT_UInt32)(5*table->line_no); + pnode = &table->sources[ hash % FT_MEM_SOURCE_BUCKETS ]; + for ( ;; ) + { + node = *pnode; + if ( node == NULL ) + break; + + if ( node->file_name == table->file_name && + node->line_no == table->line_no ) + goto Exit; + + pnode = &node->link; + } + + node = ft_mem_table_alloc( table, sizeof(*node) ); + if ( node == NULL ) + ft_mem_debug_panic( + "not enough memory to perform memory debugging\n" ); + + + node->file_name = table->file_name; + node->line_no = table->line_no; + + node->cur_blocks = 0; + node->max_blocks = 0; + node->all_blocks = 0; + + node->cur_size = 0; + node->max_size = 0; + node->all_size = 0; + + node->cur_max = 0; + + node->link = NULL; + node->hash = hash; + *pnode = node; + + Exit: + return node; + } + + static void ft_mem_table_set( FT_MemTable table, FT_Byte* address, @@ -381,14 +491,19 @@ if ( table ) { + FT_MemSource source; + pnode = ft_mem_table_get_nodep( table, address ); node = *pnode; if ( node ) { + FT_MemSource source; + if ( node->size < 0 ) { /* this block was already freed. This means that our memory is */ /* now completely corrupted! */ + /* this can only happen in keep-alive mode */ ft_mem_debug_panic( "memory heap corrupted (allocating freed block)" ); } @@ -397,7 +512,12 @@ /* this block was already allocated. This means that our memory */ /* is also corrupted! */ ft_mem_debug_panic( - "memory heap corrupted (re-allocating allocated block)" ); + "memory heap corrupted (re-allocating allocated block at" + " %p, of size %ld)\n" + "org=%s:%d new=%s:%d\n", + node->address, node->size, + FT_FILENAME( node->source->file_name ), node->source->line_no, + FT_FILENAME( table->file_name ), table->line_no ); } } @@ -409,8 +529,20 @@ node->address = address; node->size = size; - node->alloc_file_name = table->file_name; - node->alloc_line_no = table->line_no; + node->source = source = ft_mem_table_get_source( table ); + + source->all_blocks++; + source->cur_blocks++; + if ( source->cur_blocks > source->max_blocks ) + source->max_blocks = source->cur_blocks; + + if ( size > source->cur_max ) + source->cur_max = size; + + source->all_size += size; + source->cur_size += size; + if ( source->cur_size > source->max_size ) + source->max_size = source->cur_size; node->free_file_name = NULL; node->free_line_no = 0; @@ -445,23 +577,49 @@ node = *pnode; if ( node ) { + FT_MemSource source; + if ( node->size < 0 ) ft_mem_debug_panic( "freeing memory block at %p more than once at (%s:%ld)\n" "block allocated at (%s:%ld) and released at (%s:%ld)", address, FT_FILENAME( table->file_name ), table->line_no, - FT_FILENAME( node->alloc_file_name ), node->alloc_line_no, + FT_FILENAME( node->source->file_name ), node->source->line_no, FT_FILENAME( node->free_file_name ), node->free_line_no ); - /* we simply invert the node's size to indicate that the node */ - /* was freed. We also change its contents. */ + /* scramble the node's content for additionnals safety */ FT_MEM_SET( address, 0xF3, node->size ); - table->alloc_current -= node->size; - node->size = -node->size; - node->free_file_name = table->file_name; - node->free_line_no = table->line_no; + + source = node->source; + + source->cur_blocks--; + source->cur_size -= node->size; + + if ( table->keep_alive ) + { + /* we simply invert the node's size to indicate that the node */ + /* was freed. */ + node->size = -node->size; + node->free_file_name = table->file_name; + node->free_line_no = table->line_no; + } + else + { + table->nodes --; + + *pnode = node->link; + + node->size = 0; + node->source = NULL; + + ft_mem_table_free( table, node ); + + if ( table->nodes * 3 < table->size || + table->size * 3 < table->nodes ) + ft_mem_table_resize( table ); + } } else ft_mem_debug_panic( @@ -489,9 +647,9 @@ return NULL; /* return NULL if this allocation would overflow the maximum heap size */ - if ( table->bound_total && + if ( table->bound_total && table->alloc_current + (FT_ULong)size > table->alloc_total_max ) - return NULL; + return NULL; block = (FT_Byte *)ft_mem_table_alloc( table, size ); if ( block ) @@ -520,7 +678,11 @@ ft_mem_table_remove( table, (FT_Byte*)block ); - /* we never really free the block */ + if ( !table->keep_alive ) + ft_mem_table_free( table, block ); + + table->alloc_count--; + table->file_name = NULL; table->line_no = 0; } @@ -548,7 +710,7 @@ #endif /* while the following is allowed in ANSI C also, we abort since */ - /* such code shouldn't be in FreeType... */ + /* such case should be handled by FreeType. */ if ( new_size <= 0 ) ft_mem_debug_panic( "trying to reallocate %p to size 0 (current is %ld) in (%s:%ld)", @@ -600,29 +762,29 @@ if ( table ) { const char* p; - + memory->user = table; memory->alloc = ft_mem_debug_alloc; memory->realloc = ft_mem_debug_realloc; memory->free = ft_mem_debug_free; - + p = getenv( "FT2_ALLOC_TOTAL_MAX" ); if ( p != NULL ) { FT_Long total_max = ft_atol(p); - + if ( total_max > 0 ) { table->bound_total = 1; table->alloc_total_max = (FT_ULong) total_max; } } - + p = getenv( "FT2_ALLOC_COUNT_MAX" ); if ( p != NULL ) { FT_Long total_count = ft_atol(p); - + if ( total_count > 0 ) { table->bound_count = 1; @@ -630,6 +792,15 @@ } } + p = getenv( "FT2_KEEP_ALIVE" ); + if ( p != NULL ) + { + FT_Long keep_alive = ft_atol(p); + + if ( keep_alive > 0 ) + table->keep_alive = 1; + } + result = 1; } } @@ -733,7 +904,7 @@ return FT_QRealloc( memory, current, size, P ); } - + FT_BASE_DEF( void ) FT_Free_Debug( FT_Memory memory, FT_Pointer block, @@ -752,6 +923,42 @@ } + extern void + FT_DumpMemory( FT_Memory memory ) + { + FT_MemTable table = (FT_MemTable)memory->user; + + + if ( table ) + { + FT_MemSource* bucket = table->sources; + FT_MemSource* limit = bucket + FT_MEM_SOURCE_BUCKETS; + const char* fmt; + + printf( "FreeType Memory Dump: current=%ld max=%ld total=%ld count=%ld\n", + table->alloc_current, table->alloc_max, table->alloc_total, table->alloc_count ); + printf( " block block sizes sizes sizes source\n" ); + printf( " count high sum highsum max location\n" ); + printf( "-------------------------------------------------\n" ); + fmt = "%6ld %6ld %10ld %10ld %10ld %s:%d\n"; + + for ( ; bucket < limit; bucket++ ) + { + FT_MemSource source = *bucket; + + for ( ; source; source = source->link ) + { + printf( fmt, + source->cur_blocks, source->max_blocks, + source->cur_size, source->max_size, source->cur_max, + FT_FILENAME( source->file_name ), + source->line_no ); + } + } + printf( "------------------------------------------------\n" ); + } + } + #else /* !FT_DEBUG_MEMORY */ /* ANSI C doesn't like empty source files */