|
1 /***************************************************************************/ |
|
2 /* */ |
|
3 /* ftdbgmem.c */ |
|
4 /* */ |
|
5 /* Memory debugger (body). */ |
|
6 /* */ |
|
7 /* Copyright 2001, 2002, 2003, 2004, 2005, 2006, 2009 by */ |
|
8 /* David Turner, Robert Wilhelm, and Werner Lemberg. */ |
|
9 /* */ |
|
10 /* This file is part of the FreeType project, and may only be used, */ |
|
11 /* modified, and distributed under the terms of the FreeType project */ |
|
12 /* license, LICENSE.TXT. By continuing to use, modify, or distribute */ |
|
13 /* this file you indicate that you have read the license and */ |
|
14 /* understand and accept it fully. */ |
|
15 /* */ |
|
16 /***************************************************************************/ |
|
17 |
|
18 |
|
19 #include <ft2build.h> |
|
20 #include FT_CONFIG_CONFIG_H |
|
21 #include FT_INTERNAL_DEBUG_H |
|
22 #include FT_INTERNAL_MEMORY_H |
|
23 #include FT_SYSTEM_H |
|
24 #include FT_ERRORS_H |
|
25 #include FT_TYPES_H |
|
26 |
|
27 |
|
28 #ifdef FT_DEBUG_MEMORY |
|
29 |
|
30 #define KEEPALIVE /* `Keep alive' means that freed blocks aren't released |
|
31 * to the heap. This is useful to detect double-frees |
|
32 * or weird heap corruption, but it uses large amounts of |
|
33 * memory, however. |
|
34 */ |
|
35 |
|
36 #include FT_CONFIG_STANDARD_LIBRARY_H |
|
37 |
|
38 FT_BASE_DEF( const char* ) _ft_debug_file = 0; |
|
39 FT_BASE_DEF( long ) _ft_debug_lineno = 0; |
|
40 |
|
41 extern void |
|
42 FT_DumpMemory( FT_Memory memory ); |
|
43 |
|
44 |
|
45 typedef struct FT_MemSourceRec_* FT_MemSource; |
|
46 typedef struct FT_MemNodeRec_* FT_MemNode; |
|
47 typedef struct FT_MemTableRec_* FT_MemTable; |
|
48 |
|
49 |
|
50 #define FT_MEM_VAL( addr ) ((FT_PtrDist)(FT_Pointer)( addr )) |
|
51 |
|
52 /* |
|
53 * This structure holds statistics for a single allocation/release |
|
54 * site. This is useful to know where memory operations happen the |
|
55 * most. |
|
56 */ |
|
57 typedef struct FT_MemSourceRec_ |
|
58 { |
|
59 const char* file_name; |
|
60 long line_no; |
|
61 |
|
62 FT_Long cur_blocks; /* current number of allocated blocks */ |
|
63 FT_Long max_blocks; /* max. number of allocated blocks */ |
|
64 FT_Long all_blocks; /* total number of blocks allocated */ |
|
65 |
|
66 FT_Long cur_size; /* current cumulative allocated size */ |
|
67 FT_Long max_size; /* maximum cumulative allocated size */ |
|
68 FT_Long all_size; /* total cumulative allocated size */ |
|
69 |
|
70 FT_Long cur_max; /* current maximum allocated size */ |
|
71 |
|
72 FT_UInt32 hash; |
|
73 FT_MemSource link; |
|
74 |
|
75 } FT_MemSourceRec; |
|
76 |
|
77 |
|
78 /* |
|
79 * We don't need a resizable array for the memory sources, because |
|
80 * their number is pretty limited within FreeType. |
|
81 */ |
|
82 #define FT_MEM_SOURCE_BUCKETS 128 |
|
83 |
|
84 /* |
|
85 * This structure holds information related to a single allocated |
|
86 * memory block. If KEEPALIVE is defined, blocks that are freed by |
|
87 * FreeType are never released to the system. Instead, their `size' |
|
88 * field is set to -size. This is mainly useful to detect double frees, |
|
89 * at the price of large memory footprint during execution. |
|
90 */ |
|
91 typedef struct FT_MemNodeRec_ |
|
92 { |
|
93 FT_Byte* address; |
|
94 FT_Long size; /* < 0 if the block was freed */ |
|
95 |
|
96 FT_MemSource source; |
|
97 |
|
98 #ifdef KEEPALIVE |
|
99 const char* free_file_name; |
|
100 FT_Long free_line_no; |
|
101 #endif |
|
102 |
|
103 FT_MemNode link; |
|
104 |
|
105 } FT_MemNodeRec; |
|
106 |
|
107 |
|
108 /* |
|
109 * The global structure, containing compound statistics and all hash |
|
110 * tables. |
|
111 */ |
|
112 typedef struct FT_MemTableRec_ |
|
113 { |
|
114 FT_ULong size; |
|
115 FT_ULong nodes; |
|
116 FT_MemNode* buckets; |
|
117 |
|
118 FT_ULong alloc_total; |
|
119 FT_ULong alloc_current; |
|
120 FT_ULong alloc_max; |
|
121 FT_ULong alloc_count; |
|
122 |
|
123 FT_Bool bound_total; |
|
124 FT_ULong alloc_total_max; |
|
125 |
|
126 FT_Bool bound_count; |
|
127 FT_ULong alloc_count_max; |
|
128 |
|
129 FT_MemSource sources[FT_MEM_SOURCE_BUCKETS]; |
|
130 |
|
131 FT_Bool keep_alive; |
|
132 |
|
133 FT_Memory memory; |
|
134 FT_Pointer memory_user; |
|
135 FT_Alloc_Func alloc; |
|
136 FT_Free_Func free; |
|
137 FT_Realloc_Func realloc; |
|
138 |
|
139 } FT_MemTableRec; |
|
140 |
|
141 |
|
142 #define FT_MEM_SIZE_MIN 7 |
|
143 #define FT_MEM_SIZE_MAX 13845163 |
|
144 |
|
145 #define FT_FILENAME( x ) ((x) ? (x) : "unknown file") |
|
146 |
|
147 |
|
148 /* |
|
149 * Prime numbers are ugly to handle. It would be better to implement |
|
150 * L-Hashing, which is 10% faster and doesn't require divisions. |
|
151 */ |
|
152 static const FT_UInt ft_mem_primes[] = |
|
153 { |
|
154 7, |
|
155 11, |
|
156 19, |
|
157 37, |
|
158 73, |
|
159 109, |
|
160 163, |
|
161 251, |
|
162 367, |
|
163 557, |
|
164 823, |
|
165 1237, |
|
166 1861, |
|
167 2777, |
|
168 4177, |
|
169 6247, |
|
170 9371, |
|
171 14057, |
|
172 21089, |
|
173 31627, |
|
174 47431, |
|
175 71143, |
|
176 106721, |
|
177 160073, |
|
178 240101, |
|
179 360163, |
|
180 540217, |
|
181 810343, |
|
182 1215497, |
|
183 1823231, |
|
184 2734867, |
|
185 4102283, |
|
186 6153409, |
|
187 9230113, |
|
188 13845163, |
|
189 }; |
|
190 |
|
191 |
|
192 static FT_ULong |
|
193 ft_mem_closest_prime( FT_ULong num ) |
|
194 { |
|
195 FT_UInt i; |
|
196 |
|
197 |
|
198 for ( i = 0; |
|
199 i < sizeof ( ft_mem_primes ) / sizeof ( ft_mem_primes[0] ); i++ ) |
|
200 if ( ft_mem_primes[i] > num ) |
|
201 return ft_mem_primes[i]; |
|
202 |
|
203 return FT_MEM_SIZE_MAX; |
|
204 } |
|
205 |
|
206 |
|
207 extern void |
|
208 ft_mem_debug_panic( const char* fmt, |
|
209 ... ) |
|
210 { |
|
211 va_list ap; |
|
212 |
|
213 |
|
214 printf( "FreeType.Debug: " ); |
|
215 |
|
216 va_start( ap, fmt ); |
|
217 vprintf( fmt, ap ); |
|
218 va_end( ap ); |
|
219 |
|
220 printf( "\n" ); |
|
221 exit( EXIT_FAILURE ); |
|
222 } |
|
223 |
|
224 |
|
225 static FT_Pointer |
|
226 ft_mem_table_alloc( FT_MemTable table, |
|
227 FT_Long size ) |
|
228 { |
|
229 FT_Memory memory = table->memory; |
|
230 FT_Pointer block; |
|
231 |
|
232 |
|
233 memory->user = table->memory_user; |
|
234 block = table->alloc( memory, size ); |
|
235 memory->user = table; |
|
236 |
|
237 return block; |
|
238 } |
|
239 |
|
240 |
|
241 static void |
|
242 ft_mem_table_free( FT_MemTable table, |
|
243 FT_Pointer block ) |
|
244 { |
|
245 FT_Memory memory = table->memory; |
|
246 |
|
247 |
|
248 memory->user = table->memory_user; |
|
249 table->free( memory, block ); |
|
250 memory->user = table; |
|
251 } |
|
252 |
|
253 |
|
254 static void |
|
255 ft_mem_table_resize( FT_MemTable table ) |
|
256 { |
|
257 FT_ULong new_size; |
|
258 |
|
259 |
|
260 new_size = ft_mem_closest_prime( table->nodes ); |
|
261 if ( new_size != table->size ) |
|
262 { |
|
263 FT_MemNode* new_buckets; |
|
264 FT_ULong i; |
|
265 |
|
266 |
|
267 new_buckets = (FT_MemNode *) |
|
268 ft_mem_table_alloc( table, |
|
269 new_size * sizeof ( FT_MemNode ) ); |
|
270 if ( new_buckets == NULL ) |
|
271 return; |
|
272 |
|
273 FT_ARRAY_ZERO( new_buckets, new_size ); |
|
274 |
|
275 for ( i = 0; i < table->size; i++ ) |
|
276 { |
|
277 FT_MemNode node, next, *pnode; |
|
278 FT_PtrDist hash; |
|
279 |
|
280 |
|
281 node = table->buckets[i]; |
|
282 while ( node ) |
|
283 { |
|
284 next = node->link; |
|
285 hash = FT_MEM_VAL( node->address ) % new_size; |
|
286 pnode = new_buckets + hash; |
|
287 |
|
288 node->link = pnode[0]; |
|
289 pnode[0] = node; |
|
290 |
|
291 node = next; |
|
292 } |
|
293 } |
|
294 |
|
295 if ( table->buckets ) |
|
296 ft_mem_table_free( table, table->buckets ); |
|
297 |
|
298 table->buckets = new_buckets; |
|
299 table->size = new_size; |
|
300 } |
|
301 } |
|
302 |
|
303 |
|
304 static FT_MemTable |
|
305 ft_mem_table_new( FT_Memory memory ) |
|
306 { |
|
307 FT_MemTable table; |
|
308 |
|
309 |
|
310 table = (FT_MemTable)memory->alloc( memory, sizeof ( *table ) ); |
|
311 if ( table == NULL ) |
|
312 goto Exit; |
|
313 |
|
314 FT_ZERO( table ); |
|
315 |
|
316 table->size = FT_MEM_SIZE_MIN; |
|
317 table->nodes = 0; |
|
318 |
|
319 table->memory = memory; |
|
320 |
|
321 table->memory_user = memory->user; |
|
322 |
|
323 table->alloc = memory->alloc; |
|
324 table->realloc = memory->realloc; |
|
325 table->free = memory->free; |
|
326 |
|
327 table->buckets = (FT_MemNode *) |
|
328 memory->alloc( memory, |
|
329 table->size * sizeof ( FT_MemNode ) ); |
|
330 if ( table->buckets ) |
|
331 FT_ARRAY_ZERO( table->buckets, table->size ); |
|
332 else |
|
333 { |
|
334 memory->free( memory, table ); |
|
335 table = NULL; |
|
336 } |
|
337 |
|
338 Exit: |
|
339 return table; |
|
340 } |
|
341 |
|
342 |
|
343 static void |
|
344 ft_mem_table_destroy( FT_MemTable table ) |
|
345 { |
|
346 FT_ULong i; |
|
347 |
|
348 |
|
349 FT_DumpMemory( table->memory ); |
|
350 |
|
351 if ( table ) |
|
352 { |
|
353 FT_Long leak_count = 0; |
|
354 FT_ULong leaks = 0; |
|
355 |
|
356 |
|
357 /* remove all blocks from the table, revealing leaked ones */ |
|
358 for ( i = 0; i < table->size; i++ ) |
|
359 { |
|
360 FT_MemNode *pnode = table->buckets + i, next, node = *pnode; |
|
361 |
|
362 |
|
363 while ( node ) |
|
364 { |
|
365 next = node->link; |
|
366 node->link = 0; |
|
367 |
|
368 if ( node->size > 0 ) |
|
369 { |
|
370 printf( |
|
371 "leaked memory block at address %p, size %8ld in (%s:%ld)\n", |
|
372 node->address, node->size, |
|
373 FT_FILENAME( node->source->file_name ), |
|
374 node->source->line_no ); |
|
375 |
|
376 leak_count++; |
|
377 leaks += node->size; |
|
378 |
|
379 ft_mem_table_free( table, node->address ); |
|
380 } |
|
381 |
|
382 node->address = NULL; |
|
383 node->size = 0; |
|
384 |
|
385 ft_mem_table_free( table, node ); |
|
386 node = next; |
|
387 } |
|
388 table->buckets[i] = 0; |
|
389 } |
|
390 |
|
391 ft_mem_table_free( table, table->buckets ); |
|
392 table->buckets = NULL; |
|
393 |
|
394 table->size = 0; |
|
395 table->nodes = 0; |
|
396 |
|
397 /* remove all sources */ |
|
398 for ( i = 0; i < FT_MEM_SOURCE_BUCKETS; i++ ) |
|
399 { |
|
400 FT_MemSource source, next; |
|
401 |
|
402 |
|
403 for ( source = table->sources[i]; source != NULL; source = next ) |
|
404 { |
|
405 next = source->link; |
|
406 ft_mem_table_free( table, source ); |
|
407 } |
|
408 |
|
409 table->sources[i] = NULL; |
|
410 } |
|
411 |
|
412 printf( |
|
413 "FreeType: total memory allocations = %ld\n", table->alloc_total ); |
|
414 printf( |
|
415 "FreeType: maximum memory footprint = %ld\n", table->alloc_max ); |
|
416 |
|
417 ft_mem_table_free( table, table ); |
|
418 |
|
419 if ( leak_count > 0 ) |
|
420 ft_mem_debug_panic( |
|
421 "FreeType: %ld bytes of memory leaked in %ld blocks\n", |
|
422 leaks, leak_count ); |
|
423 |
|
424 printf( "FreeType: no memory leaks detected\n" ); |
|
425 } |
|
426 } |
|
427 |
|
428 |
|
429 static FT_MemNode* |
|
430 ft_mem_table_get_nodep( FT_MemTable table, |
|
431 FT_Byte* address ) |
|
432 { |
|
433 FT_PtrDist hash; |
|
434 FT_MemNode *pnode, node; |
|
435 |
|
436 |
|
437 hash = FT_MEM_VAL( address ); |
|
438 pnode = table->buckets + ( hash % table->size ); |
|
439 |
|
440 for (;;) |
|
441 { |
|
442 node = pnode[0]; |
|
443 if ( !node ) |
|
444 break; |
|
445 |
|
446 if ( node->address == address ) |
|
447 break; |
|
448 |
|
449 pnode = &node->link; |
|
450 } |
|
451 return pnode; |
|
452 } |
|
453 |
|
454 |
|
455 static FT_MemSource |
|
456 ft_mem_table_get_source( FT_MemTable table ) |
|
457 { |
|
458 FT_UInt32 hash; |
|
459 FT_MemSource node, *pnode; |
|
460 |
|
461 |
|
462 /* cast to FT_PtrDist first since void* can be larger */ |
|
463 /* than FT_UInt32 and GCC 4.1.1 emits a warning */ |
|
464 hash = (FT_UInt32)(FT_PtrDist)(void*)_ft_debug_file + |
|
465 (FT_UInt32)( 5 * _ft_debug_lineno ); |
|
466 pnode = &table->sources[hash % FT_MEM_SOURCE_BUCKETS]; |
|
467 |
|
468 for ( ;; ) |
|
469 { |
|
470 node = *pnode; |
|
471 if ( node == NULL ) |
|
472 break; |
|
473 |
|
474 if ( node->file_name == _ft_debug_file && |
|
475 node->line_no == _ft_debug_lineno ) |
|
476 goto Exit; |
|
477 |
|
478 pnode = &node->link; |
|
479 } |
|
480 |
|
481 node = (FT_MemSource)ft_mem_table_alloc( table, sizeof ( *node ) ); |
|
482 if ( node == NULL ) |
|
483 ft_mem_debug_panic( |
|
484 "not enough memory to perform memory debugging\n" ); |
|
485 |
|
486 node->file_name = _ft_debug_file; |
|
487 node->line_no = _ft_debug_lineno; |
|
488 |
|
489 node->cur_blocks = 0; |
|
490 node->max_blocks = 0; |
|
491 node->all_blocks = 0; |
|
492 |
|
493 node->cur_size = 0; |
|
494 node->max_size = 0; |
|
495 node->all_size = 0; |
|
496 |
|
497 node->cur_max = 0; |
|
498 |
|
499 node->link = NULL; |
|
500 node->hash = hash; |
|
501 *pnode = node; |
|
502 |
|
503 Exit: |
|
504 return node; |
|
505 } |
|
506 |
|
507 |
|
508 static void |
|
509 ft_mem_table_set( FT_MemTable table, |
|
510 FT_Byte* address, |
|
511 FT_ULong size, |
|
512 FT_Long delta ) |
|
513 { |
|
514 FT_MemNode *pnode, node; |
|
515 |
|
516 |
|
517 if ( table ) |
|
518 { |
|
519 FT_MemSource source; |
|
520 |
|
521 |
|
522 pnode = ft_mem_table_get_nodep( table, address ); |
|
523 node = *pnode; |
|
524 if ( node ) |
|
525 { |
|
526 if ( node->size < 0 ) |
|
527 { |
|
528 /* This block was already freed. Our memory is now completely */ |
|
529 /* corrupted! */ |
|
530 /* This can only happen in keep-alive mode. */ |
|
531 ft_mem_debug_panic( |
|
532 "memory heap corrupted (allocating freed block)" ); |
|
533 } |
|
534 else |
|
535 { |
|
536 /* This block was already allocated. This means that our memory */ |
|
537 /* is also corrupted! */ |
|
538 ft_mem_debug_panic( |
|
539 "memory heap corrupted (re-allocating allocated block at" |
|
540 " %p, of size %ld)\n" |
|
541 "org=%s:%d new=%s:%d\n", |
|
542 node->address, node->size, |
|
543 FT_FILENAME( node->source->file_name ), node->source->line_no, |
|
544 FT_FILENAME( _ft_debug_file ), _ft_debug_lineno ); |
|
545 } |
|
546 } |
|
547 |
|
548 /* we need to create a new node in this table */ |
|
549 node = (FT_MemNode)ft_mem_table_alloc( table, sizeof ( *node ) ); |
|
550 if ( node == NULL ) |
|
551 ft_mem_debug_panic( "not enough memory to run memory tests" ); |
|
552 |
|
553 node->address = address; |
|
554 node->size = size; |
|
555 node->source = source = ft_mem_table_get_source( table ); |
|
556 |
|
557 if ( delta == 0 ) |
|
558 { |
|
559 /* this is an allocation */ |
|
560 source->all_blocks++; |
|
561 source->cur_blocks++; |
|
562 if ( source->cur_blocks > source->max_blocks ) |
|
563 source->max_blocks = source->cur_blocks; |
|
564 } |
|
565 |
|
566 if ( size > (FT_ULong)source->cur_max ) |
|
567 source->cur_max = size; |
|
568 |
|
569 if ( delta != 0 ) |
|
570 { |
|
571 /* we are growing or shrinking a reallocated block */ |
|
572 source->cur_size += delta; |
|
573 table->alloc_current += delta; |
|
574 } |
|
575 else |
|
576 { |
|
577 /* we are allocating a new block */ |
|
578 source->cur_size += size; |
|
579 table->alloc_current += size; |
|
580 } |
|
581 |
|
582 source->all_size += size; |
|
583 |
|
584 if ( source->cur_size > source->max_size ) |
|
585 source->max_size = source->cur_size; |
|
586 |
|
587 node->free_file_name = NULL; |
|
588 node->free_line_no = 0; |
|
589 |
|
590 node->link = pnode[0]; |
|
591 |
|
592 pnode[0] = node; |
|
593 table->nodes++; |
|
594 |
|
595 table->alloc_total += size; |
|
596 |
|
597 if ( table->alloc_current > table->alloc_max ) |
|
598 table->alloc_max = table->alloc_current; |
|
599 |
|
600 if ( table->nodes * 3 < table->size || |
|
601 table->size * 3 < table->nodes ) |
|
602 ft_mem_table_resize( table ); |
|
603 } |
|
604 } |
|
605 |
|
606 |
|
607 static void |
|
608 ft_mem_table_remove( FT_MemTable table, |
|
609 FT_Byte* address, |
|
610 FT_Long delta ) |
|
611 { |
|
612 if ( table ) |
|
613 { |
|
614 FT_MemNode *pnode, node; |
|
615 |
|
616 |
|
617 pnode = ft_mem_table_get_nodep( table, address ); |
|
618 node = *pnode; |
|
619 if ( node ) |
|
620 { |
|
621 FT_MemSource source; |
|
622 |
|
623 |
|
624 if ( node->size < 0 ) |
|
625 ft_mem_debug_panic( |
|
626 "freeing memory block at %p more than once at (%s:%ld)\n" |
|
627 "block allocated at (%s:%ld) and released at (%s:%ld)", |
|
628 address, |
|
629 FT_FILENAME( _ft_debug_file ), _ft_debug_lineno, |
|
630 FT_FILENAME( node->source->file_name ), node->source->line_no, |
|
631 FT_FILENAME( node->free_file_name ), node->free_line_no ); |
|
632 |
|
633 /* scramble the node's content for additional safety */ |
|
634 FT_MEM_SET( address, 0xF3, node->size ); |
|
635 |
|
636 if ( delta == 0 ) |
|
637 { |
|
638 source = node->source; |
|
639 |
|
640 source->cur_blocks--; |
|
641 source->cur_size -= node->size; |
|
642 |
|
643 table->alloc_current -= node->size; |
|
644 } |
|
645 |
|
646 if ( table->keep_alive ) |
|
647 { |
|
648 /* we simply invert the node's size to indicate that the node */ |
|
649 /* was freed. */ |
|
650 node->size = -node->size; |
|
651 node->free_file_name = _ft_debug_file; |
|
652 node->free_line_no = _ft_debug_lineno; |
|
653 } |
|
654 else |
|
655 { |
|
656 table->nodes--; |
|
657 |
|
658 *pnode = node->link; |
|
659 |
|
660 node->size = 0; |
|
661 node->source = NULL; |
|
662 |
|
663 ft_mem_table_free( table, node ); |
|
664 |
|
665 if ( table->nodes * 3 < table->size || |
|
666 table->size * 3 < table->nodes ) |
|
667 ft_mem_table_resize( table ); |
|
668 } |
|
669 } |
|
670 else |
|
671 ft_mem_debug_panic( |
|
672 "trying to free unknown block at %p in (%s:%ld)\n", |
|
673 address, |
|
674 FT_FILENAME( _ft_debug_file ), _ft_debug_lineno ); |
|
675 } |
|
676 } |
|
677 |
|
678 |
|
679 extern FT_Pointer |
|
680 ft_mem_debug_alloc( FT_Memory memory, |
|
681 FT_Long size ) |
|
682 { |
|
683 FT_MemTable table = (FT_MemTable)memory->user; |
|
684 FT_Byte* block; |
|
685 |
|
686 |
|
687 if ( size <= 0 ) |
|
688 ft_mem_debug_panic( "negative block size allocation (%ld)", size ); |
|
689 |
|
690 /* return NULL if the maximum number of allocations was reached */ |
|
691 if ( table->bound_count && |
|
692 table->alloc_count >= table->alloc_count_max ) |
|
693 return NULL; |
|
694 |
|
695 /* return NULL if this allocation would overflow the maximum heap size */ |
|
696 if ( table->bound_total && |
|
697 table->alloc_total_max - table->alloc_current > (FT_ULong)size ) |
|
698 return NULL; |
|
699 |
|
700 block = (FT_Byte *)ft_mem_table_alloc( table, size ); |
|
701 if ( block ) |
|
702 { |
|
703 ft_mem_table_set( table, block, (FT_ULong)size, 0 ); |
|
704 |
|
705 table->alloc_count++; |
|
706 } |
|
707 |
|
708 _ft_debug_file = "<unknown>"; |
|
709 _ft_debug_lineno = 0; |
|
710 |
|
711 return (FT_Pointer)block; |
|
712 } |
|
713 |
|
714 |
|
715 extern void |
|
716 ft_mem_debug_free( FT_Memory memory, |
|
717 FT_Pointer block ) |
|
718 { |
|
719 FT_MemTable table = (FT_MemTable)memory->user; |
|
720 |
|
721 |
|
722 if ( block == NULL ) |
|
723 ft_mem_debug_panic( "trying to free NULL in (%s:%ld)", |
|
724 FT_FILENAME( _ft_debug_file ), |
|
725 _ft_debug_lineno ); |
|
726 |
|
727 ft_mem_table_remove( table, (FT_Byte*)block, 0 ); |
|
728 |
|
729 if ( !table->keep_alive ) |
|
730 ft_mem_table_free( table, block ); |
|
731 |
|
732 table->alloc_count--; |
|
733 |
|
734 _ft_debug_file = "<unknown>"; |
|
735 _ft_debug_lineno = 0; |
|
736 } |
|
737 |
|
738 |
|
739 extern FT_Pointer |
|
740 ft_mem_debug_realloc( FT_Memory memory, |
|
741 FT_Long cur_size, |
|
742 FT_Long new_size, |
|
743 FT_Pointer block ) |
|
744 { |
|
745 FT_MemTable table = (FT_MemTable)memory->user; |
|
746 FT_MemNode node, *pnode; |
|
747 FT_Pointer new_block; |
|
748 FT_Long delta; |
|
749 |
|
750 const char* file_name = FT_FILENAME( _ft_debug_file ); |
|
751 FT_Long line_no = _ft_debug_lineno; |
|
752 |
|
753 |
|
754 /* unlikely, but possible */ |
|
755 if ( new_size == cur_size ) |
|
756 return block; |
|
757 |
|
758 /* the following is valid according to ANSI C */ |
|
759 #if 0 |
|
760 if ( block == NULL || cur_size == 0 ) |
|
761 ft_mem_debug_panic( "trying to reallocate NULL in (%s:%ld)", |
|
762 file_name, line_no ); |
|
763 #endif |
|
764 |
|
765 /* while the following is allowed in ANSI C also, we abort since */ |
|
766 /* such case should be handled by FreeType. */ |
|
767 if ( new_size <= 0 ) |
|
768 ft_mem_debug_panic( |
|
769 "trying to reallocate %p to size 0 (current is %ld) in (%s:%ld)", |
|
770 block, cur_size, file_name, line_no ); |
|
771 |
|
772 /* check `cur_size' value */ |
|
773 pnode = ft_mem_table_get_nodep( table, (FT_Byte*)block ); |
|
774 node = *pnode; |
|
775 if ( !node ) |
|
776 ft_mem_debug_panic( |
|
777 "trying to reallocate unknown block at %p in (%s:%ld)", |
|
778 block, file_name, line_no ); |
|
779 |
|
780 if ( node->size <= 0 ) |
|
781 ft_mem_debug_panic( |
|
782 "trying to reallocate freed block at %p in (%s:%ld)", |
|
783 block, file_name, line_no ); |
|
784 |
|
785 if ( node->size != cur_size ) |
|
786 ft_mem_debug_panic( "invalid ft_realloc request for %p. cur_size is " |
|
787 "%ld instead of %ld in (%s:%ld)", |
|
788 block, cur_size, node->size, file_name, line_no ); |
|
789 |
|
790 /* return NULL if the maximum number of allocations was reached */ |
|
791 if ( table->bound_count && |
|
792 table->alloc_count >= table->alloc_count_max ) |
|
793 return NULL; |
|
794 |
|
795 delta = (FT_Long)( new_size - cur_size ); |
|
796 |
|
797 /* return NULL if this allocation would overflow the maximum heap size */ |
|
798 if ( delta > 0 && |
|
799 table->bound_total && |
|
800 table->alloc_current + (FT_ULong)delta > table->alloc_total_max ) |
|
801 return NULL; |
|
802 |
|
803 new_block = (FT_Byte *)ft_mem_table_alloc( table, new_size ); |
|
804 if ( new_block == NULL ) |
|
805 return NULL; |
|
806 |
|
807 ft_mem_table_set( table, (FT_Byte*)new_block, new_size, delta ); |
|
808 |
|
809 ft_memcpy( new_block, block, cur_size < new_size ? cur_size : new_size ); |
|
810 |
|
811 ft_mem_table_remove( table, (FT_Byte*)block, delta ); |
|
812 |
|
813 _ft_debug_file = "<unknown>"; |
|
814 _ft_debug_lineno = 0; |
|
815 |
|
816 if ( !table->keep_alive ) |
|
817 ft_mem_table_free( table, block ); |
|
818 |
|
819 return new_block; |
|
820 } |
|
821 |
|
822 |
|
823 extern FT_Int |
|
824 ft_mem_debug_init( FT_Memory memory ) |
|
825 { |
|
826 FT_MemTable table; |
|
827 FT_Int result = 0; |
|
828 |
|
829 |
|
830 if ( getenv( "FT2_DEBUG_MEMORY" ) ) |
|
831 { |
|
832 table = ft_mem_table_new( memory ); |
|
833 if ( table ) |
|
834 { |
|
835 const char* p; |
|
836 |
|
837 |
|
838 memory->user = table; |
|
839 memory->alloc = ft_mem_debug_alloc; |
|
840 memory->realloc = ft_mem_debug_realloc; |
|
841 memory->free = ft_mem_debug_free; |
|
842 |
|
843 p = getenv( "FT2_ALLOC_TOTAL_MAX" ); |
|
844 if ( p != NULL ) |
|
845 { |
|
846 FT_Long total_max = ft_atol( p ); |
|
847 |
|
848 |
|
849 if ( total_max > 0 ) |
|
850 { |
|
851 table->bound_total = 1; |
|
852 table->alloc_total_max = (FT_ULong)total_max; |
|
853 } |
|
854 } |
|
855 |
|
856 p = getenv( "FT2_ALLOC_COUNT_MAX" ); |
|
857 if ( p != NULL ) |
|
858 { |
|
859 FT_Long total_count = ft_atol( p ); |
|
860 |
|
861 |
|
862 if ( total_count > 0 ) |
|
863 { |
|
864 table->bound_count = 1; |
|
865 table->alloc_count_max = (FT_ULong)total_count; |
|
866 } |
|
867 } |
|
868 |
|
869 p = getenv( "FT2_KEEP_ALIVE" ); |
|
870 if ( p != NULL ) |
|
871 { |
|
872 FT_Long keep_alive = ft_atol( p ); |
|
873 |
|
874 |
|
875 if ( keep_alive > 0 ) |
|
876 table->keep_alive = 1; |
|
877 } |
|
878 |
|
879 result = 1; |
|
880 } |
|
881 } |
|
882 return result; |
|
883 } |
|
884 |
|
885 |
|
886 extern void |
|
887 ft_mem_debug_done( FT_Memory memory ) |
|
888 { |
|
889 FT_MemTable table = (FT_MemTable)memory->user; |
|
890 |
|
891 |
|
892 if ( table ) |
|
893 { |
|
894 memory->free = table->free; |
|
895 memory->realloc = table->realloc; |
|
896 memory->alloc = table->alloc; |
|
897 |
|
898 ft_mem_table_destroy( table ); |
|
899 memory->user = NULL; |
|
900 } |
|
901 } |
|
902 |
|
903 |
|
904 |
|
905 static int |
|
906 ft_mem_source_compare( const void* p1, |
|
907 const void* p2 ) |
|
908 { |
|
909 FT_MemSource s1 = *(FT_MemSource*)p1; |
|
910 FT_MemSource s2 = *(FT_MemSource*)p2; |
|
911 |
|
912 |
|
913 if ( s2->max_size > s1->max_size ) |
|
914 return 1; |
|
915 else if ( s2->max_size < s1->max_size ) |
|
916 return -1; |
|
917 else |
|
918 return 0; |
|
919 } |
|
920 |
|
921 |
|
922 extern void |
|
923 FT_DumpMemory( FT_Memory memory ) |
|
924 { |
|
925 FT_MemTable table = (FT_MemTable)memory->user; |
|
926 |
|
927 |
|
928 if ( table ) |
|
929 { |
|
930 FT_MemSource* bucket = table->sources; |
|
931 FT_MemSource* limit = bucket + FT_MEM_SOURCE_BUCKETS; |
|
932 FT_MemSource* sources; |
|
933 FT_UInt nn, count; |
|
934 const char* fmt; |
|
935 |
|
936 |
|
937 count = 0; |
|
938 for ( ; bucket < limit; bucket++ ) |
|
939 { |
|
940 FT_MemSource source = *bucket; |
|
941 |
|
942 |
|
943 for ( ; source; source = source->link ) |
|
944 count++; |
|
945 } |
|
946 |
|
947 sources = (FT_MemSource*)ft_mem_table_alloc( |
|
948 table, sizeof ( *sources ) * count ); |
|
949 |
|
950 count = 0; |
|
951 for ( bucket = table->sources; bucket < limit; bucket++ ) |
|
952 { |
|
953 FT_MemSource source = *bucket; |
|
954 |
|
955 |
|
956 for ( ; source; source = source->link ) |
|
957 sources[count++] = source; |
|
958 } |
|
959 |
|
960 ft_qsort( sources, count, sizeof ( *sources ), ft_mem_source_compare ); |
|
961 |
|
962 printf( "FreeType Memory Dump: " |
|
963 "current=%ld max=%ld total=%ld count=%ld\n", |
|
964 table->alloc_current, table->alloc_max, |
|
965 table->alloc_total, table->alloc_count ); |
|
966 printf( " block block sizes sizes sizes source\n" ); |
|
967 printf( " count high sum highsum max location\n" ); |
|
968 printf( "-------------------------------------------------\n" ); |
|
969 |
|
970 fmt = "%6ld %6ld %8ld %8ld %8ld %s:%d\n"; |
|
971 |
|
972 for ( nn = 0; nn < count; nn++ ) |
|
973 { |
|
974 FT_MemSource source = sources[nn]; |
|
975 |
|
976 |
|
977 printf( fmt, |
|
978 source->cur_blocks, source->max_blocks, |
|
979 source->cur_size, source->max_size, source->cur_max, |
|
980 FT_FILENAME( source->file_name ), |
|
981 source->line_no ); |
|
982 } |
|
983 printf( "------------------------------------------------\n" ); |
|
984 |
|
985 ft_mem_table_free( table, sources ); |
|
986 } |
|
987 } |
|
988 |
|
989 #else /* !FT_DEBUG_MEMORY */ |
|
990 |
|
991 /* ANSI C doesn't like empty source files */ |
|
992 typedef int _debug_mem_dummy; |
|
993 |
|
994 #endif /* !FT_DEBUG_MEMORY */ |
|
995 |
|
996 |
|
997 /* END */ |