428
+ − 1 /* This file is part of XEmacs.
+ − 2
+ − 3 XEmacs is free software; you can redistribute it and/or modify it
+ − 4 under the terms of the GNU General Public License as published by the
+ − 5 Free Software Foundation; either version 2, or (at your option) any
+ − 6 later version.
+ − 7
+ − 8 XEmacs is distributed in the hope that it will be useful, but WITHOUT
+ − 9 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ − 10 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ − 11 for more details.
+ − 12
+ − 13 You should have received a copy of the GNU General Public License
+ − 14 along with XEmacs; see the file COPYING. If not, write to
+ − 15 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ − 16 Boston, MA 02111-1307, USA. */
+ − 17
+ − 18 /* Synched up with: Not in FSF. */
+ − 19
+ − 20 /* Debugging hooks for malloc. */
+ − 21
+ − 22 /* These hooks work with gmalloc to catch allocation errors.
+ − 23 In particular, the following is trapped:
+ − 24
+ − 25 * Freeing the same pointer twice.
+ − 26 * Trying to free a pointer not returned by malloc.
+ − 27 * Trying to realloc a pointer not returned by malloc.
+ − 28
1204
+ − 29 In addition, every word of every block freed is set to 0xdeadbeef
+ − 30 (-559038737). This causes many uses of freed storage to be trapped or
+ − 31 recognized.
428
+ − 32
+ − 33 When you use this, the storage used by the last FREE_QUEUE_LIMIT
+ − 34 calls to free() is not recycled. When you call free for the Nth
+ − 35 time, the (N - FREE_QUEUE_LIMIT)'th block is actually recycled.
+ − 36
+ − 37 For these last FREE_QUEUE_LIMIT calls to free() a backtrace is
+ − 38 saved showing where it was called from. The function
+ − 39 find_backtrace() is provided here to be called from GDB with a
+ − 40 pointer (such as would be passed to free()) as argument, e.g.
+ − 41 (gdb) p/a *find_backtrace (0x234000). If SAVE_ARGS is defined,
+ − 42 the first three arguments to each function are saved as well as the
+ − 43 return addresses.
+ − 44
+ − 45 If UNMAPPED_FREE is defined, instead of setting every word of freed
+ − 46 storage to 0xdeadbeef, every call to malloc goes on its own page(s).
+ − 47 When free() is called, the block is read and write protected. This
+ − 48 is very useful when debugging, since it usually generates a bus error
+ − 49 when the deadbeef hack might only cause some garbage to be printed.
+ − 50 However, this is too slow for everyday use, since it takes an enormous
+ − 51 number of pages.
+ − 52
+ − 53
+ − 54 Some other features that would be useful are:
+ − 55
+ − 56 * Checking for storage leaks.
+ − 57 This could be done by a GC-like facility that would scan the data
+ − 58 segment looking for pointers to allocated storage and tell you
+ − 59 about those that are no longer referenced. This could be invoked
+ − 60 at any time. Another possibility is to report on what allocated
+ − 61 storage is still in use when the process is exited. Typically
+ − 62 there will be a large amount, so this might not be very useful.
+ − 63 */
+ − 64
+ − 65 #ifdef emacs
+ − 66 #include <config.h>
+ − 67 #include "lisp.h"
+ − 68 #else
+ − 69 void *malloc (size_t);
+ − 70 #endif
+ − 71
+ − 72 #if !defined(HAVE_LIBMCHECK)
+ − 73 #include <stdio.h>
+ − 74
+ − 75 #include "hash.h"
+ − 76
+ − 77 #ifdef UNMAPPED_FREE
+ − 78 #include <sys/mman.h>
+ − 79 #include <sys/param.h>
+ − 80 #define ROUND_UP_TO_PAGE(i) (((i) + PAGEOFFSET) & PAGEMASK)
+ − 81 #endif
+ − 82
+ − 83 #include <sys/types.h>
+ − 84
+ − 85 /* System function prototypes don't belong in C source files */
+ − 86 /* extern void free (void *); */
+ − 87
+ − 88 static struct hash_table *pointer_table;
+ − 89
+ − 90 extern void (*__free_hook) (void *);
+ − 91 extern void *(*__malloc_hook) (size_t);
+ − 92
+ − 93 static void *check_malloc (size_t);
+ − 94
+ − 95 typedef void (*fun_ptr) (void);
+ − 96
+ − 97 /* free_queue is not too useful without backtrace logging */
+ − 98 #define FREE_QUEUE_LIMIT 1
+ − 99 #define TRACE_LIMIT 20
+ − 100
+ − 101 typedef struct {
+ − 102 fun_ptr return_pc;
+ − 103 #ifdef SAVE_ARGS
+ − 104 void *arg[3];
+ − 105 #endif
+ − 106 } fun_entry;
+ − 107
+ − 108 typedef struct {
+ − 109 void *address;
+ − 110 unsigned long length;
+ − 111 } free_queue_entry;
+ − 112
+ − 113 static free_queue_entry free_queue[FREE_QUEUE_LIMIT];
+ − 114
+ − 115 static int current_free;
+ − 116
+ − 117 static int strict_free_check;
+ − 118
+ − 119 static void
+ − 120 check_free (void *ptr)
+ − 121 {
+ − 122 __free_hook = 0;
+ − 123 __malloc_hook = 0;
+ − 124 if (!pointer_table)
+ − 125 pointer_table = make_hash_table (max (100, FREE_QUEUE_LIMIT * 2));
+ − 126 if (ptr != 0)
+ − 127 {
+ − 128 long size;
+ − 129 #ifdef UNMAPPED_FREE
+ − 130 unsigned long rounded_up_size;
+ − 131 #endif
+ − 132
+ − 133 EMACS_INT present = (EMACS_INT) gethash (ptr, pointer_table,
2519
+ − 134 (const void **)
+ − 135 (void *) &size);
428
+ − 136
+ − 137 if (!present)
+ − 138 {
+ − 139 /* This can only happen if you try to free something that didn't
+ − 140 come from malloc */
+ − 141 #if !defined(__linux__)
+ − 142 /* I originally wrote: "There's really no need to drop core."
+ − 143 I have seen the error of my ways. -slb */
+ − 144 if (strict_free_check)
2500
+ − 145 ABORT ();
428
+ − 146 #endif
+ − 147 printf("Freeing unmalloc'ed memory at %p\n", ptr);
+ − 148 __free_hook = check_free;
+ − 149 __malloc_hook = check_malloc;
+ − 150 goto end;
+ − 151 }
+ − 152
+ − 153 if (size < 0)
+ − 154 {
+ − 155 /* This happens when you free twice */
+ − 156 #if !defined(__linux__)
+ − 157 /* See above comment. */
+ − 158 if (strict_free_check)
2500
+ − 159 ABORT ();
428
+ − 160 #endif
+ − 161 printf("Freeing %p twice\n", ptr);
+ − 162 __free_hook = check_free;
+ − 163 __malloc_hook = check_malloc;
+ − 164 goto end;
+ − 165 }
+ − 166
+ − 167 puthash (ptr, (void *)-size, pointer_table);
+ − 168 #ifdef UNMAPPED_FREE
+ − 169 /* Round up size to an even number of pages. */
+ − 170 rounded_up_size = ROUND_UP_TO_PAGE (size);
+ − 171 /* Protect the pages freed from all access */
+ − 172 if (strict_free_check)
+ − 173 mprotect (ptr, rounded_up_size, PROT_NONE);
+ − 174 #else
+ − 175 /* Set every word in the block to 0xdeadbeef */
+ − 176 if (strict_free_check)
+ − 177 {
+ − 178 unsigned long long_length = (size + (sizeof (long) - 1))
+ − 179 / sizeof (long);
+ − 180 unsigned long i;
+ − 181
3988
+ − 182 /* Not using the DEADBEEF_CONSTANT #define, since we don't know
+ − 183 * that allocation sizes will be multiples of eight. */
428
+ − 184 for (i = 0; i < long_length; i++)
+ − 185 ((unsigned long *) ptr)[i] = 0xdeadbeef;
+ − 186 }
+ − 187 #endif
+ − 188 free_queue[current_free].address = ptr;
+ − 189 free_queue[current_free].length = size;
+ − 190
+ − 191 current_free++;
+ − 192 if (current_free >= FREE_QUEUE_LIMIT)
+ − 193 current_free = 0;
+ − 194 /* Really free this if there's something there */
+ − 195 {
+ − 196 void *old = free_queue[current_free].address;
+ − 197
+ − 198 if (old)
+ − 199 {
+ − 200 #ifdef UNMAPPED_FREE
+ − 201 unsigned long old_len = free_queue[current_free].length;
+ − 202
+ − 203 mprotect (old, old_len, PROT_READ | PROT_WRITE | PROT_EXEC);
+ − 204 #endif
+ − 205 free (old);
+ − 206 remhash (old, pointer_table);
+ − 207 }
+ − 208 }
+ − 209 }
+ − 210 __free_hook = check_free;
+ − 211 __malloc_hook = check_malloc;
+ − 212
+ − 213 end:
+ − 214 return;
+ − 215 }
+ − 216
+ − 217 static void *
+ − 218 check_malloc (size_t size)
+ − 219 {
+ − 220 size_t rounded_up_size;
+ − 221 void *result;
+ − 222
+ − 223 __free_hook = 0;
+ − 224 __malloc_hook = 0;
+ − 225 if (size == 0)
+ − 226 {
+ − 227 result = 0;
+ − 228 goto end;
+ − 229 }
+ − 230 #ifdef UNMAPPED_FREE
+ − 231 /* Round up to an even number of pages. */
+ − 232 rounded_up_size = ROUND_UP_TO_PAGE (size);
+ − 233 #else
+ − 234 rounded_up_size = size;
+ − 235 #endif
+ − 236 result = malloc (rounded_up_size);
+ − 237 if (!pointer_table)
+ − 238 pointer_table = make_hash_table (FREE_QUEUE_LIMIT * 2);
+ − 239 puthash (result, (void *)size, pointer_table);
+ − 240 __free_hook = check_free;
+ − 241 __malloc_hook = check_malloc;
+ − 242 end:
+ − 243 return result;
+ − 244 }
+ − 245
+ − 246 extern void *(*__realloc_hook) (void *, size_t);
+ − 247
+ − 248 #ifdef MIN
+ − 249 #undef MIN
+ − 250 #endif
+ − 251 #define MIN(A, B) ((A) < (B) ? (A) : (B))
+ − 252
+ − 253 /* Don't optimize realloc */
+ − 254
+ − 255 static void *
+ − 256 check_realloc (void * ptr, size_t size)
+ − 257 {
+ − 258 EMACS_INT present;
+ − 259 size_t old_size;
+ − 260 void *result = malloc (size);
+ − 261
+ − 262 if (!ptr) return result;
442
+ − 263 present = (EMACS_INT) gethash (ptr, pointer_table, (const void **) &old_size);
428
+ − 264 if (!present)
+ − 265 {
+ − 266 /* This can only happen by reallocing a pointer that didn't
+ − 267 come from malloc. */
+ − 268 #if !defined(__linux__)
+ − 269 /* see comment in check_free(). */
2500
+ − 270 ABORT ();
428
+ − 271 #endif
+ − 272 printf("Realloc'ing unmalloc'ed pointer at %p\n", ptr);
+ − 273 }
+ − 274
+ − 275 if (result == 0)
+ − 276 goto end;
+ − 277 memcpy (result, ptr, MIN (size, old_size));
+ − 278 free (ptr);
+ − 279 end:
+ − 280 return result;
+ − 281 }
+ − 282
+ − 283 void enable_strict_free_check (void);
+ − 284 void
+ − 285 enable_strict_free_check (void)
+ − 286 {
+ − 287 strict_free_check = 1;
+ − 288 }
+ − 289
+ − 290 void disable_strict_free_check (void);
+ − 291 void
+ − 292 disable_strict_free_check (void)
+ − 293 {
+ − 294 strict_free_check = 0;
+ − 295 }
+ − 296
+ − 297 /* Note: All BLOCK_INPUT stuff removed from this file because it's
+ − 298 completely gone in XEmacs */
+ − 299
+ − 300 static void *
+ − 301 block_input_malloc (size_t size);
+ − 302
+ − 303 static void
+ − 304 block_input_free (void* ptr)
+ − 305 {
+ − 306 __free_hook = 0;
+ − 307 __malloc_hook = 0;
+ − 308 free (ptr);
+ − 309 __free_hook = block_input_free;
+ − 310 __malloc_hook = block_input_malloc;
+ − 311 }
+ − 312
+ − 313 static void *
+ − 314 block_input_malloc (size_t size)
+ − 315 {
+ − 316 void* result;
+ − 317 __free_hook = 0;
+ − 318 __malloc_hook = 0;
+ − 319 result = malloc (size);
+ − 320 __free_hook = block_input_free;
+ − 321 __malloc_hook = block_input_malloc;
+ − 322 return result;
+ − 323 }
+ − 324
+ − 325
+ − 326 static void *
+ − 327 block_input_realloc (void* ptr, size_t size)
+ − 328 {
+ − 329 void* result;
+ − 330 __free_hook = 0;
+ − 331 __malloc_hook = 0;
+ − 332 __realloc_hook = 0;
+ − 333 result = realloc (ptr, size);
+ − 334 __free_hook = block_input_free;
+ − 335 __malloc_hook = block_input_malloc;
+ − 336 __realloc_hook = block_input_realloc;
+ − 337 return result;
+ − 338 }
+ − 339
+ − 340 #ifdef emacs
+ − 341
+ − 342 void disable_free_hook (void);
+ − 343 void
+ − 344 disable_free_hook (void)
+ − 345 {
+ − 346 __free_hook = block_input_free;
+ − 347 __malloc_hook = block_input_malloc;
+ − 348 __realloc_hook = block_input_realloc;
+ − 349 }
+ − 350
+ − 351 void
+ − 352 init_free_hook (void)
+ − 353 {
+ − 354 __free_hook = check_free;
+ − 355 __malloc_hook = check_malloc;
+ − 356 __realloc_hook = check_realloc;
+ − 357 current_free = 0;
+ − 358 strict_free_check = 1;
+ − 359 }
+ − 360
+ − 361 void really_free_one_entry (void *, int, int *);
+ − 362
+ − 363 DEFUN ("really-free", Freally_free, 0, 1, "P", /*
+ − 364 Actually free the storage held by the free() debug hook.
+ − 365 A no-op if the free hook is disabled.
+ − 366 */
2286
+ − 367 (UNUSED (arg)))
428
+ − 368 {
+ − 369 int count[2];
+ − 370 Lisp_Object lisp_count[2];
+ − 371
+ − 372 if ((__free_hook != 0) && pointer_table)
+ − 373 {
+ − 374 count[0] = 0;
+ − 375 count[1] = 0;
+ − 376 __free_hook = 0;
+ − 377 maphash ((maphash_function)really_free_one_entry,
+ − 378 pointer_table, (void *)&count);
+ − 379 memset (free_queue, 0, sizeof (free_queue_entry) * FREE_QUEUE_LIMIT);
+ − 380 current_free = 0;
+ − 381 __free_hook = check_free;
793
+ − 382 lisp_count[0] = make_int (count[0]);
+ − 383 lisp_count[1] = make_int (count[1]);
428
+ − 384 return Fcons (lisp_count[0], lisp_count[1]);
+ − 385 }
+ − 386 else
+ − 387 return Fcons (make_int (0), make_int (0));
+ − 388 }
+ − 389
+ − 390 void
+ − 391 really_free_one_entry (void *key, int contents, int *countp)
+ − 392 {
+ − 393 if (contents < 0)
+ − 394 {
+ − 395 free (key);
+ − 396 #ifdef UNMAPPED_FREE
+ − 397 mprotect (key, -contents, PROT_READ | PROT_WRITE | PROT_EXEC);
+ − 398 #endif
+ − 399 remhash (key, pointer_table);
+ − 400 countp[0]++;
+ − 401 countp[1] += -contents;
+ − 402 }
+ − 403 }
+ − 404
+ − 405 void
+ − 406 syms_of_free_hook (void)
+ − 407 {
+ − 408 DEFSUBR (Freally_free);
+ − 409 }
+ − 410
+ − 411 #else
+ − 412 void (*__free_hook)(void *) = check_free;
+ − 413 void *(*__malloc_hook)(size_t) = check_malloc;
+ − 414 void *(*__realloc_hook)(void *, size_t) = check_realloc;
+ − 415 #endif
+ − 416
+ − 417 #endif /* !defined(HAVE_LIBMCHECK) */
+ − 418
+ − 419 #if defined(DEBUG_INPUT_BLOCKING) || defined (DEBUG_GCPRO)
+ − 420
+ − 421 /* Note: There is no more input blocking in XEmacs */
+ − 422 typedef enum {
+ − 423 block_type, unblock_type, totally_type,
+ − 424 gcpro1_type, gcpro2_type, gcpro3_type, gcpro4_type, gcpro5_type,
+ − 425 ungcpro_type
+ − 426 } blocktype;
+ − 427
+ − 428 struct block_input_history_struct
+ − 429 {
+ − 430 char *file;
+ − 431 int line;
+ − 432 blocktype type;
+ − 433 int value;
+ − 434 };
+ − 435
+ − 436 typedef struct block_input_history_struct block_input_history;
+ − 437
+ − 438 #endif /* DEBUG_INPUT_BLOCKING || DEBUG_GCPRO */
+ − 439
+ − 440 #ifdef DEBUG_INPUT_BLOCKING
+ − 441
+ − 442 int blhistptr;
+ − 443
+ − 444 #define BLHISTLIMIT 1000
+ − 445
+ − 446 block_input_history blhist[BLHISTLIMIT];
+ − 447
+ − 448 note_block_input (char *file, int line)
+ − 449 {
+ − 450 note_block (file, line, block_type);
2500
+ − 451 if (interrupt_input_blocked > 2) ABORT();
428
+ − 452 }
+ − 453
+ − 454 note_unblock_input (char* file, int line)
+ − 455 {
+ − 456 note_block (file, line, unblock_type);
+ − 457 }
+ − 458
+ − 459 note_totally_unblocked (char* file, int line)
+ − 460 {
+ − 461 note_block (file, line, totally_type);
+ − 462 }
+ − 463
+ − 464 note_block (char *file, int line, blocktype type)
+ − 465 {
+ − 466 blhist[blhistptr].file = file;
+ − 467 blhist[blhistptr].line = line;
+ − 468 blhist[blhistptr].type = type;
+ − 469 blhist[blhistptr].value = interrupt_input_blocked;
+ − 470
+ − 471 blhistptr++;
+ − 472 if (blhistptr >= BLHISTLIMIT)
+ − 473 blhistptr = 0;
+ − 474 }
+ − 475
+ − 476 #endif /* DEBUG_INPUT_BLOCKING */
+ − 477
+ − 478
+ − 479 #ifdef DEBUG_GCPRO
+ − 480
+ − 481 int gcprohistptr;
+ − 482 #define GCPROHISTLIMIT 1000
+ − 483 block_input_history gcprohist[GCPROHISTLIMIT];
+ − 484
+ − 485 static void
+ − 486 log_gcpro (char *file, int line, struct gcpro *value, blocktype type)
+ − 487 {
+ − 488 if (type == ungcpro_type)
+ − 489 {
+ − 490 if (value == gcprolist) goto OK;
2500
+ − 491 if (! gcprolist) ABORT ();
428
+ − 492 if (value == gcprolist->next) goto OK;
2500
+ − 493 if (! gcprolist->next) ABORT ();
428
+ − 494 if (value == gcprolist->next->next) goto OK;
2500
+ − 495 if (! gcprolist->next->next) ABORT ();
428
+ − 496 if (value == gcprolist->next->next->next) goto OK;
2500
+ − 497 if (! gcprolist->next->next->next) ABORT ();
446
+ − 498 if (value == gcprolist->next->next->next->next) goto OK;
2500
+ − 499 ABORT ();
428
+ − 500 OK:;
+ − 501 }
+ − 502 gcprohist[gcprohistptr].file = file;
+ − 503 gcprohist[gcprohistptr].line = line;
+ − 504 gcprohist[gcprohistptr].type = type;
+ − 505 gcprohist[gcprohistptr].value = (int) value;
+ − 506 gcprohistptr++;
+ − 507 if (gcprohistptr >= GCPROHISTLIMIT)
+ − 508 gcprohistptr = 0;
+ − 509 }
+ − 510
+ − 511 void
+ − 512 debug_gcpro1 (char *file, int line, struct gcpro *gcpro1, Lisp_Object *var)
+ − 513 {
+ − 514 gcpro1->next = gcprolist; gcpro1->var = var; gcpro1->nvars = 1;
+ − 515 gcprolist = gcpro1;
+ − 516 log_gcpro (file, line, gcpro1, gcpro1_type);
+ − 517 }
+ − 518
+ − 519 void
+ − 520 debug_gcpro2 (char *file, int line, struct gcpro *gcpro1, struct gcpro *gcpro2,
+ − 521 Lisp_Object *var1, Lisp_Object *var2)
+ − 522 {
+ − 523 gcpro1->next = gcprolist; gcpro1->var = var1; gcpro1->nvars = 1;
+ − 524 gcpro2->next = gcpro1; gcpro2->var = var2; gcpro2->nvars = 1;
+ − 525 gcprolist = gcpro2;
+ − 526 log_gcpro (file, line, gcpro2, gcpro2_type);
+ − 527 }
+ − 528
+ − 529 void
+ − 530 debug_gcpro3 (char *file, int line, struct gcpro *gcpro1, struct gcpro *gcpro2,
+ − 531 struct gcpro *gcpro3, Lisp_Object *var1, Lisp_Object *var2,
+ − 532 Lisp_Object *var3)
+ − 533 {
+ − 534 gcpro1->next = gcprolist; gcpro1->var = var1; gcpro1->nvars = 1;
+ − 535 gcpro2->next = gcpro1; gcpro2->var = var2; gcpro2->nvars = 1;
+ − 536 gcpro3->next = gcpro2; gcpro3->var = var3; gcpro3->nvars = 1;
+ − 537 gcprolist = gcpro3;
+ − 538 log_gcpro (file, line, gcpro3, gcpro3_type);
+ − 539 }
+ − 540
+ − 541 void
+ − 542 debug_gcpro4 (char *file, int line, struct gcpro *gcpro1, struct gcpro *gcpro2,
+ − 543 struct gcpro *gcpro3, struct gcpro *gcpro4, Lisp_Object *var1,
+ − 544 Lisp_Object *var2, Lisp_Object *var3, Lisp_Object *var4)
+ − 545 {
+ − 546 log_gcpro (file, line, gcpro4, gcpro4_type);
+ − 547 gcpro1->next = gcprolist; gcpro1->var = var1; gcpro1->nvars = 1;
+ − 548 gcpro2->next = gcpro1; gcpro2->var = var2; gcpro2->nvars = 1;
+ − 549 gcpro3->next = gcpro2; gcpro3->var = var3; gcpro3->nvars = 1;
+ − 550 gcpro4->next = gcpro3; gcpro4->var = var4; gcpro4->nvars = 1;
+ − 551 gcprolist = gcpro4;
+ − 552 }
+ − 553
+ − 554 void
+ − 555 debug_gcpro5 (char *file, int line, struct gcpro *gcpro1, struct gcpro *gcpro2,
+ − 556 struct gcpro *gcpro3, struct gcpro *gcpro4, struct gcpro *gcpro5,
+ − 557 Lisp_Object *var1, Lisp_Object *var2, Lisp_Object *var3,
+ − 558 Lisp_Object *var4, Lisp_Object *var5)
+ − 559 {
+ − 560 log_gcpro (file, line, gcpro5, gcpro5_type);
+ − 561 gcpro1->next = gcprolist; gcpro1->var = var1; gcpro1->nvars = 1;
+ − 562 gcpro2->next = gcpro1; gcpro2->var = var2; gcpro2->nvars = 1;
+ − 563 gcpro3->next = gcpro2; gcpro3->var = var3; gcpro3->nvars = 1;
+ − 564 gcpro4->next = gcpro3; gcpro4->var = var4; gcpro4->nvars = 1;
+ − 565 gcpro5->next = gcpro4; gcpro5->var = var5; gcpro5->nvars = 1;
+ − 566 gcprolist = gcpro5;
+ − 567 }
+ − 568
+ − 569 void
+ − 570 debug_ungcpro (char *file, int line, struct gcpro *gcpro1)
+ − 571 {
+ − 572 log_gcpro (file, line, gcpro1, ungcpro_type);
+ − 573 gcprolist = gcpro1->next;
+ − 574 }
+ − 575
+ − 576
+ − 577 /* To be called from the debugger */
+ − 578 void show_gcprohist (void);
+ − 579 void
+ − 580 show_gcprohist (void)
+ − 581 {
+ − 582 int i, j;
+ − 583 for (i = 0, j = gcprohistptr;
+ − 584 i < GCPROHISTLIMIT;
+ − 585 i++, j++)
+ − 586 {
+ − 587 if (j >= GCPROHISTLIMIT)
+ − 588 j = 0;
+ − 589 printf ("%3d %s %d %s 0x%x\n",
+ − 590 j, gcprohist[j].file, gcprohist[j].line,
+ − 591 (gcprohist[j].type == gcpro1_type ? "GCPRO1" :
+ − 592 gcprohist[j].type == gcpro2_type ? "GCPRO2" :
+ − 593 gcprohist[j].type == gcpro3_type ? "GCPRO3" :
+ − 594 gcprohist[j].type == gcpro4_type ? "GCPRO4" :
446
+ − 595 gcprohist[j].type == gcpro5_type ? "GCPRO5" :
428
+ − 596 gcprohist[j].type == ungcpro_type ? "UNGCPRO" : "???"),
+ − 597 gcprohist[j].value);
+ − 598 }
+ − 599 fflush (stdout);
+ − 600 }
+ − 601
+ − 602 #endif /* DEBUG_GCPRO */