/***************************************************************************/ /* */ /* ftdbgmem.c */ /* */ /* Memory debugger (body). */ /* */ /* Copyright 2001-2018 by */ /* David Turner, Robert Wilhelm, and Werner Lemberg. */ /* */ /* This file is part of the FreeType project, and may only be used, */ /* modified, and distributed under the terms of the FreeType project */ /* license, LICENSE.TXT. By continuing to use, modify, or distribute */ /* this file you indicate that you have read the license and */ /* understand and accept it fully. */ /* */ /***************************************************************************/ #include #include FT_CONFIG_CONFIG_H #include FT_INTERNAL_DEBUG_H #include FT_INTERNAL_MEMORY_H #include FT_SYSTEM_H #include FT_ERRORS_H #include FT_TYPES_H #ifdef FT_DEBUG_MEMORY #define KEEPALIVE /* `Keep alive' means that freed blocks aren't released * to the heap. This is useful to detect double-frees * or weird heap corruption, but it uses large amounts of * memory, however. */ #include FT_CONFIG_STANDARD_LIBRARY_H FT_BASE_DEF( const char* ) _ft_debug_file = NULL; FT_BASE_DEF( long ) _ft_debug_lineno = 0; extern void FT_DumpMemory( FT_Memory memory ); typedef struct FT_MemSourceRec_* FT_MemSource; typedef struct FT_MemNodeRec_* FT_MemNode; typedef struct FT_MemTableRec_* FT_MemTable; #define FT_MEM_VAL( addr ) ( (FT_PtrDist)(FT_Pointer)( addr ) ) /* * This structure holds statistics for a single allocation/release * site. This is useful to know where memory operations happen the * most. */ 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 /* * This structure holds information related to a single allocated * memory block. If KEEPALIVE is defined, blocks that are freed by * FreeType are never released to the system. Instead, their `size' * field is set to `-size'. This is mainly useful to detect double * frees, at the price of a large memory footprint during execution. */ typedef struct FT_MemNodeRec_ { FT_Byte* address; FT_Long size; /* < 0 if the block was freed */ FT_MemSource source; #ifdef KEEPALIVE const char* free_file_name; FT_Long free_line_no; #endif FT_MemNode link; } FT_MemNodeRec; /* * The global structure, containing compound statistics and all hash * tables. */ typedef struct FT_MemTableRec_ { FT_Long size; FT_Long nodes; FT_MemNode* buckets; FT_Long alloc_total; FT_Long alloc_current; FT_Long alloc_max; FT_Long alloc_count; FT_Bool bound_total; FT_Long alloc_total_max; FT_Bool bound_count; FT_Long alloc_count_max; FT_MemSource sources[FT_MEM_SOURCE_BUCKETS]; FT_Bool keep_alive; FT_Memory memory; FT_Pointer memory_user; FT_Alloc_Func alloc; FT_Free_Func free; FT_Realloc_Func realloc; } FT_MemTableRec; #define FT_MEM_SIZE_MIN 7 #define FT_MEM_SIZE_MAX 13845163 #define FT_FILENAME( x ) ( (x) ? (x) : "unknown file" ) /* * Prime numbers are ugly to handle. It would be better to implement * L-Hashing, which is 10% faster and doesn't require divisions. */ static const FT_Int ft_mem_primes[] = { 7, 11, 19, 37, 73, 109, 163, 251, 367, 557, 823, 1237, 1861, 2777, 4177, 6247, 9371, 14057, 21089, 31627, 47431, 71143, 106721, 160073, 240101, 360163, 540217, 810343, 1215497, 1823231, 2734867, 4102283, 6153409, 9230113, 13845163, }; static FT_Long ft_mem_closest_prime( FT_Long num ) { size_t 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 void ft_mem_debug_panic( const char* fmt, ... ) { va_list ap; printf( "FreeType.Debug: " ); va_start( ap, fmt ); vprintf( fmt, ap ); va_end( ap ); printf( "\n" ); exit( EXIT_FAILURE ); } static FT_Pointer ft_mem_table_alloc( FT_MemTable table, FT_Long size ) { FT_Memory memory = table->memory; FT_Pointer block; memory->user = table->memory_user; block = table->alloc( memory, size ); memory->user = table; return block; } static void ft_mem_table_free( FT_MemTable table, FT_Pointer block ) { FT_Memory memory = table->memory; memory->user = table->memory_user; table->free( memory, block ); memory->user = table; } static void ft_mem_table_resize( FT_MemTable table ) { FT_Long new_size; new_size = ft_mem_closest_prime( table->nodes ); if ( new_size != table->size ) { FT_MemNode* new_buckets; FT_Long i; new_buckets = (FT_MemNode *) ft_mem_table_alloc( table, new_size * (FT_Long)sizeof ( FT_MemNode ) ); if ( !new_buckets ) return; FT_ARRAY_ZERO( new_buckets, new_size ); for ( i = 0; i < table->size; i++ ) { FT_MemNode node, next, *pnode; FT_PtrDist hash; node = table->buckets[i]; while ( node ) { next = node->link; hash = FT_MEM_VAL( node->address ) % (FT_PtrDist)new_size; pnode = new_buckets + hash; node->link = pnode[0]; pnode[0] = node; node = next; } } if ( table->buckets ) ft_mem_table_free( table, table->buckets ); table->buckets = new_buckets; table->size = new_size; } } static FT_MemTable ft_mem_table_new( FT_Memory memory ) { FT_MemTable table; table = (FT_MemTable)memory->alloc( memory, sizeof ( *table ) ); if ( !table ) goto Exit; FT_ZERO( table ); table->size = FT_MEM_SIZE_MIN; table->nodes = 0; table->memory = memory; table->memory_user = memory->user; table->alloc = memory->alloc; table->realloc = memory->realloc; table->free = memory->free; table->buckets = (FT_MemNode *) memory->alloc( memory, table->size * (FT_Long)sizeof ( FT_MemNode ) ); if ( table->buckets ) FT_ARRAY_ZERO( table->buckets, table->size ); else { memory->free( memory, table ); table = NULL; } Exit: return table; } static void ft_mem_table_destroy( FT_MemTable table ) { FT_Long i; FT_Long leak_count = 0; FT_Long leaks = 0; FT_DumpMemory( table->memory ); /* 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; while ( node ) { next = node->link; node->link = NULL; if ( node->size > 0 ) { printf( "leaked memory block at address %p, size %8ld in (%s:%ld)\n", (void*)node->address, node->size, FT_FILENAME( node->source->file_name ), node->source->line_no ); leak_count++; leaks += node->size; ft_mem_table_free( table, node->address ); } node->address = NULL; node->size = 0; ft_mem_table_free( table, node ); node = next; } table->buckets[i] = NULL; } ft_mem_table_free( table, table->buckets ); table->buckets = NULL; 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 ); 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" ); } static FT_MemNode* ft_mem_table_get_nodep( FT_MemTable table, FT_Byte* address ) { FT_PtrDist hash; FT_MemNode *pnode, node; hash = FT_MEM_VAL( address ); pnode = table->buckets + ( hash % (FT_PtrDist)table->size ); for (;;) { node = pnode[0]; if ( !node ) break; if ( node->address == address ) break; pnode = &node->link; } return pnode; } static FT_MemSource ft_mem_table_get_source( FT_MemTable table ) { FT_UInt32 hash; FT_MemSource node, *pnode; /* cast to FT_PtrDist first since void* can be larger */ /* than FT_UInt32 and GCC 4.1.1 emits a warning */ hash = (FT_UInt32)(FT_PtrDist)(void*)_ft_debug_file + (FT_UInt32)( 5 * _ft_debug_lineno ); pnode = &table->sources[hash % FT_MEM_SOURCE_BUCKETS]; for (;;) { node = *pnode; if ( !node ) break; if ( node->file_name == _ft_debug_file && node->line_no == _ft_debug_lineno ) goto Exit; pnode = &node->link; } node = (FT_MemSource)ft_mem_table_alloc( table, sizeof ( *node ) ); if ( !node ) ft_mem_debug_panic( "not enough memory to perform memory debugging\n" ); node->file_name = _ft_debug_file; node->line_no = _ft_debug_lineno; 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, FT_Long size, FT_Long delta ) { FT_MemNode *pnode, node; if ( table ) { FT_MemSource source; pnode = ft_mem_table_get_nodep( table, address ); node = *pnode; if ( node ) { if ( node->size < 0 ) { /* This block was already freed. Our memory is now completely */ /* corrupted! */ /* This can only happen in keep-alive mode. */ ft_mem_debug_panic( "memory heap corrupted (allocating freed block)" ); } else { /* This block was already allocated. This means that our memory */ /* is also corrupted! */ ft_mem_debug_panic( "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( _ft_debug_file ), _ft_debug_lineno ); } } /* we need to create a new node in this table */ node = (FT_MemNode)ft_mem_table_alloc( table, sizeof ( *node ) ); if ( !node ) ft_mem_debug_panic( "not enough memory to run memory tests" ); node->address = address; node->size = size; node->source = source = ft_mem_table_get_source( table ); if ( delta == 0 ) { /* this is an allocation */ 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; if ( delta != 0 ) { /* we are growing or shrinking a reallocated block */ source->cur_size += delta; table->alloc_current += delta; } else { /* we are allocating a new block */ source->cur_size += size; table->alloc_current += size; } source->all_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; node->link = pnode[0]; pnode[0] = node; table->nodes++; table->alloc_total += size; if ( table->alloc_current > table->alloc_max ) table->alloc_max = table->alloc_current; if ( table->nodes * 3 < table->size || table->size * 3 < table->nodes ) ft_mem_table_resize( table ); } } static void ft_mem_table_remove( FT_MemTable table, FT_Byte* address, FT_Long delta ) { if ( table ) { FT_MemNode *pnode, node; pnode = ft_mem_table_get_nodep( table, address ); 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( _ft_debug_file ), _ft_debug_lineno, FT_FILENAME( node->source->file_name ), node->source->line_no, FT_FILENAME( node->free_file_name ), node->free_line_no ); /* scramble the node's content for additional safety */ FT_MEM_SET( address, 0xF3, node->size ); if ( delta == 0 ) { source = node->source; source->cur_blocks--; source->cur_size -= node->size; table->alloc_current -= 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 = _ft_debug_file; node->free_line_no = _ft_debug_lineno; } 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( "trying to free unknown block at %p in (%s:%ld)\n", address, FT_FILENAME( _ft_debug_file ), _ft_debug_lineno ); } } static FT_Pointer ft_mem_debug_alloc( FT_Memory memory, FT_Long size ) { FT_MemTable table = (FT_MemTable)memory->user; FT_Byte* block; if ( size <= 0 ) ft_mem_debug_panic( "negative block size allocation (%ld)", size ); /* return NULL if the maximum number of allocations was reached */ if ( table->bound_count && table->alloc_count >= table->alloc_count_max ) return NULL; /* return NULL if this allocation would overflow the maximum heap size */ if ( table->bound_total && table->alloc_total_max - table->alloc_current > size ) return NULL; block = (FT_Byte *)ft_mem_table_alloc( table, size ); if ( block ) { ft_mem_table_set( table, block, size, 0 ); table->alloc_count++; } _ft_debug_file = ""; _ft_debug_lineno = 0; return (FT_Pointer)block; } static void ft_mem_debug_free( FT_Memory memory, FT_Pointer block ) { FT_MemTable table = (FT_MemTable)memory->user; if ( !block ) ft_mem_debug_panic( "trying to free NULL in (%s:%ld)", FT_FILENAME( _ft_debug_file ), _ft_debug_lineno ); ft_mem_table_remove( table, (FT_Byte*)block, 0 ); if ( !table->keep_alive ) ft_mem_table_free( table, block ); table->alloc_count--; _ft_debug_file = ""; _ft_debug_lineno = 0; } static FT_Pointer ft_mem_debug_realloc( FT_Memory memory, FT_Long cur_size, FT_Long new_size, FT_Pointer block ) { FT_MemTable table = (FT_MemTable)memory->user; FT_MemNode node, *pnode; FT_Pointer new_block; FT_Long delta; const char* file_name = FT_FILENAME( _ft_debug_file ); FT_Long line_no = _ft_debug_lineno; /* unlikely, but possible */ if ( new_size == cur_size ) return block; /* the following is valid according to ANSI C */ #if 0 if ( !block || !cur_size ) ft_mem_debug_panic( "trying to reallocate NULL in (%s:%ld)", file_name, line_no ); #endif /* while the following is allowed in ANSI C also, we abort since */ /* 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)", block, cur_size, file_name, line_no ); /* check `cur_size' value */ pnode = ft_mem_table_get_nodep( table, (FT_Byte*)block ); node = *pnode; if ( !node ) ft_mem_debug_panic( "trying to reallocate unknown block at %p in (%s:%ld)", block, file_name, line_no ); if ( node->size <= 0 ) ft_mem_debug_panic( "trying to reallocate freed block at %p in (%s:%ld)", block, file_name, line_no ); if ( node->size != cur_size ) ft_mem_debug_panic( "invalid ft_realloc request for %p. cur_size is " "%ld instead of %ld in (%s:%ld)", block, cur_size, node->size, file_name, line_no ); /* return NULL if the maximum number of allocations was reached */ if ( table->bound_count && table->alloc_count >= table->alloc_count_max ) return NULL; delta = new_size - cur_size; /* return NULL if this allocation would overflow the maximum heap size */ if ( delta > 0 && table->bound_total && table->alloc_current + delta > table->alloc_total_max ) return NULL; new_block = (FT_Pointer)ft_mem_table_alloc( table, new_size ); if ( !new_block ) return NULL; ft_mem_table_set( table, (FT_Byte*)new_block, new_size, delta ); ft_memcpy( new_block, block, cur_size < new_size ? (size_t)cur_size : (size_t)new_size ); ft_mem_table_remove( table, (FT_Byte*)block, delta ); _ft_debug_file = ""; _ft_debug_lineno = 0; if ( !table->keep_alive ) ft_mem_table_free( table, block ); return new_block; } extern FT_Int ft_mem_debug_init( FT_Memory memory ) { FT_MemTable table; FT_Int result = 0; if ( ft_getenv( "FT2_DEBUG_MEMORY" ) ) { table = ft_mem_table_new( memory ); 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 = ft_getenv( "FT2_ALLOC_TOTAL_MAX" ); if ( p ) { FT_Long total_max = ft_strtol( p, NULL, 10 ); if ( total_max > 0 ) { table->bound_total = 1; table->alloc_total_max = total_max; } } p = ft_getenv( "FT2_ALLOC_COUNT_MAX" ); if ( p ) { FT_Long total_count = ft_strtol( p, NULL, 10 ); if ( total_count > 0 ) { table->bound_count = 1; table->alloc_count_max = total_count; } } p = ft_getenv( "FT2_KEEP_ALIVE" ); if ( p ) { FT_Long keep_alive = ft_strtol( p, NULL, 10 ); if ( keep_alive > 0 ) table->keep_alive = 1; } result = 1; } } return result; } extern void ft_mem_debug_done( FT_Memory memory ) { FT_MemTable table = (FT_MemTable)memory->user; if ( table ) { memory->free = table->free; memory->realloc = table->realloc; memory->alloc = table->alloc; ft_mem_table_destroy( table ); memory->user = NULL; } } static int ft_mem_source_compare( const void* p1, const void* p2 ) { FT_MemSource s1 = *(FT_MemSource*)p1; FT_MemSource s2 = *(FT_MemSource*)p2; if ( s2->max_size > s1->max_size ) return 1; else if ( s2->max_size < s1->max_size ) return -1; else return 0; } 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; FT_MemSource* sources; FT_Int nn, count; const char* fmt; count = 0; for ( ; bucket < limit; bucket++ ) { FT_MemSource source = *bucket; for ( ; source; source = source->link ) count++; } sources = (FT_MemSource*) ft_mem_table_alloc( table, count * (FT_Long)sizeof ( *sources ) ); count = 0; for ( bucket = table->sources; bucket < limit; bucket++ ) { FT_MemSource source = *bucket; for ( ; source; source = source->link ) sources[count++] = source; } ft_qsort( sources, (size_t)count, sizeof ( *sources ), ft_mem_source_compare ); 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 %8ld %8ld %8ld %s:%d\n"; for ( nn = 0; nn < count; nn++ ) { FT_MemSource source = sources[nn]; 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" ); ft_mem_table_free( table, sources ); } } #else /* !FT_DEBUG_MEMORY */ /* ANSI C doesn't like empty source files */ typedef int _debug_mem_dummy; #endif /* !FT_DEBUG_MEMORY */ /* END */