1 /* 2 * Copyright (c) 1983, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. All advertising materials mentioning features or use of this software 14 * must display the following acknowledgement: 15 * This product includes software developed by the University of 16 * California, Berkeley and its contributors. 17 * 4. Neither the name of the University nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34 #ifndef lint 35 static char copyright[] = 36 "@(#) Copyright (c) 1983, 1993\n\ 37 The Regents of the University of California. All rights reserved.\n"; 38 #endif /* not lint */ 39 40 #ifndef lint 41 static char sccsid[] = "@(#)gprof.c 8.1 (Berkeley) 6/6/93"; 42 #endif /* not lint */ 43 44 #include "gprof.h" 45 46 char *whoami = "gprof"; 47 48 /* 49 * things which get -E excluded by default. 50 */ 51 char *defaultEs[] = { "mcount" , "__mcleanup" , 0 }; 52 53 static struct gmonhdr gmonhdr; 54 static bool uflag; 55 static int lflag; 56 static int Lflag; 57 58 main(argc, argv) 59 int argc; 60 char **argv; 61 { 62 char **sp; 63 nltype **timesortnlp; 64 65 --argc; 66 argv++; 67 debug = 0; 68 bflag = TRUE; 69 while ( *argv != 0 && **argv == '-' ) { 70 (*argv)++; 71 switch ( **argv ) { 72 case 'a': 73 aflag = TRUE; 74 break; 75 case 'b': 76 bflag = FALSE; 77 break; 78 case 'C': 79 Cflag = TRUE; 80 cyclethreshold = atoi( *++argv ); 81 break; 82 case 'c': 83 #if defined(vax) || defined(tahoe) 84 cflag = TRUE; 85 #else 86 fprintf(stderr, "gprof: -c isn't supported on this architecture yet\n"); 87 exit(1); 88 #endif 89 break; 90 case 'd': 91 dflag = TRUE; 92 setlinebuf(stdout); 93 debug |= atoi( *++argv ); 94 debug |= ANYDEBUG; 95 # ifdef DEBUG 96 printf("[main] debug = %d\n", debug); 97 # else not DEBUG 98 printf("%s: -d ignored\n", whoami); 99 # endif DEBUG 100 break; 101 case 'E': 102 ++argv; 103 addlist( Elist , *argv ); 104 Eflag = TRUE; 105 addlist( elist , *argv ); 106 eflag = TRUE; 107 break; 108 case 'e': 109 addlist( elist , *++argv ); 110 eflag = TRUE; 111 break; 112 case 'F': 113 ++argv; 114 addlist( Flist , *argv ); 115 Fflag = TRUE; 116 addlist( flist , *argv ); 117 fflag = TRUE; 118 break; 119 case 'f': 120 addlist( flist , *++argv ); 121 fflag = TRUE; 122 break; 123 case 'k': 124 addlist( kfromlist , *++argv ); 125 addlist( ktolist , *++argv ); 126 kflag = TRUE; 127 break; 128 case 'l': 129 lflag = 1; 130 Lflag = 0; 131 break; 132 case 'L': 133 Lflag = 1; 134 lflag = 0; 135 break; 136 case 's': 137 sflag = TRUE; 138 break; 139 case 'u': 140 uflag = TRUE; 141 break; 142 case 'z': 143 zflag = TRUE; 144 break; 145 } 146 argv++; 147 } 148 if ( *argv != 0 ) { 149 a_outname = *argv; 150 argv++; 151 } else { 152 a_outname = A_OUTNAME; 153 } 154 if ( *argv != 0 ) { 155 gmonname = *argv; 156 argv++; 157 } else { 158 gmonname = GMONNAME; 159 } 160 /* 161 * turn off default functions 162 */ 163 for ( sp = &defaultEs[0] ; *sp ; sp++ ) { 164 Eflag = TRUE; 165 addlist( Elist , *sp ); 166 eflag = TRUE; 167 addlist( elist , *sp ); 168 } 169 /* 170 * get information about a.out file. 171 */ 172 getnfile(); 173 /* 174 * get information about mon.out file(s). 175 */ 176 do { 177 getpfile( gmonname ); 178 if ( *argv != 0 ) { 179 gmonname = *argv; 180 } 181 } while ( *argv++ != 0 ); 182 /* 183 * how many ticks per second? 184 * if we can't tell, report time in ticks. 185 */ 186 if (hz == 0) { 187 hz = 1; 188 fprintf(stderr, "time is in ticks, not seconds\n"); 189 } 190 /* 191 * dump out a gmon.sum file if requested 192 */ 193 if ( sflag ) { 194 dumpsum( GMONSUM ); 195 } 196 /* 197 * assign samples to procedures 198 */ 199 asgnsamples(); 200 /* 201 * assemble the dynamic profile 202 */ 203 timesortnlp = doarcs(); 204 /* 205 * print the dynamic profile 206 */ 207 if(!lflag) { 208 printgprof( timesortnlp ); 209 } 210 /* 211 * print the flat profile 212 */ 213 if(!Lflag) { 214 printprof(); 215 } 216 /* 217 * print the index 218 */ 219 printindex(); 220 done(); 221 } 222 223 /* 224 * Set up string and symbol tables from a.out. 225 * and optionally the text space. 226 * On return symbol table is sorted by value. 227 */ 228 getnfile() 229 { 230 FILE *nfile; 231 int valcmp(); 232 233 nfile = fopen( a_outname ,"r"); 234 if (nfile == NULL) { 235 perror( a_outname ); 236 done(); 237 } 238 fread(&xbuf, 1, sizeof(xbuf), nfile); 239 if (N_BADMAG(xbuf)) { 240 fprintf(stderr, "%s: %s: bad format\n", whoami , a_outname ); 241 done(); 242 } 243 getstrtab(nfile); 244 getsymtab(nfile); 245 gettextspace( nfile ); 246 qsort(nl, nname, sizeof(nltype), valcmp); 247 fclose(nfile); 248 # ifdef DEBUG 249 if ( debug & AOUTDEBUG ) { 250 register int j; 251 252 for (j = 0; j < nname; j++){ 253 printf("[getnfile] 0X%08x\t%s\n", nl[j].value, nl[j].name); 254 } 255 } 256 # endif DEBUG 257 } 258 259 getstrtab(nfile) 260 FILE *nfile; 261 { 262 263 fseek(nfile, (long)(N_SYMOFF(xbuf) + xbuf.a_syms), 0); 264 if (fread(&ssiz, sizeof (ssiz), 1, nfile) == 0) { 265 fprintf(stderr, "%s: %s: no string table (old format?)\n" , 266 whoami , a_outname ); 267 done(); 268 } 269 strtab = calloc(ssiz, 1); 270 if (strtab == NULL) { 271 fprintf(stderr, "%s: %s: no room for %d bytes of string table\n", 272 whoami , a_outname , ssiz); 273 done(); 274 } 275 if (fread(strtab+sizeof(ssiz), ssiz-sizeof(ssiz), 1, nfile) != 1) { 276 fprintf(stderr, "%s: %s: error reading string table\n", 277 whoami , a_outname ); 278 done(); 279 } 280 } 281 282 /* 283 * Read in symbol table 284 */ 285 getsymtab(nfile) 286 FILE *nfile; 287 { 288 register long i; 289 int askfor; 290 struct nlist nbuf; 291 292 /* pass1 - count symbols */ 293 fseek(nfile, (long)N_SYMOFF(xbuf), 0); 294 nname = 0; 295 for (i = xbuf.a_syms; i > 0; i -= sizeof(struct nlist)) { 296 fread(&nbuf, sizeof(nbuf), 1, nfile); 297 if ( ! funcsymbol( &nbuf ) ) { 298 continue; 299 } 300 nname++; 301 } 302 if (nname == 0) { 303 fprintf(stderr, "%s: %s: no symbols\n", whoami , a_outname ); 304 done(); 305 } 306 askfor = nname + 1; 307 nl = (nltype *) calloc( askfor , sizeof(nltype) ); 308 if (nl == 0) { 309 fprintf(stderr, "%s: No room for %d bytes of symbol table\n", 310 whoami, askfor * sizeof(nltype) ); 311 done(); 312 } 313 314 /* pass2 - read symbols */ 315 fseek(nfile, (long)N_SYMOFF(xbuf), 0); 316 npe = nl; 317 nname = 0; 318 for (i = xbuf.a_syms; i > 0; i -= sizeof(struct nlist)) { 319 fread(&nbuf, sizeof(nbuf), 1, nfile); 320 if ( ! funcsymbol( &nbuf ) ) { 321 # ifdef DEBUG 322 if ( debug & AOUTDEBUG ) { 323 printf( "[getsymtab] rejecting: 0x%x %s\n" , 324 nbuf.n_type , strtab + nbuf.n_un.n_strx ); 325 } 326 # endif DEBUG 327 continue; 328 } 329 npe->value = nbuf.n_value; 330 npe->name = strtab+nbuf.n_un.n_strx; 331 # ifdef DEBUG 332 if ( debug & AOUTDEBUG ) { 333 printf( "[getsymtab] %d %s 0x%08x\n" , 334 nname , npe -> name , npe -> value ); 335 } 336 # endif DEBUG 337 npe++; 338 nname++; 339 } 340 npe->value = -1; 341 } 342 343 /* 344 * read in the text space of an a.out file 345 */ 346 gettextspace( nfile ) 347 FILE *nfile; 348 { 349 350 if ( cflag == 0 ) { 351 return; 352 } 353 textspace = (u_char *) malloc( xbuf.a_text ); 354 if ( textspace == 0 ) { 355 fprintf( stderr , "%s: ran out room for %d bytes of text space: " , 356 whoami , xbuf.a_text ); 357 fprintf( stderr , "can't do -c\n" ); 358 return; 359 } 360 (void) fseek( nfile , N_TXTOFF( xbuf ) , 0 ); 361 if ( fread( textspace , 1 , xbuf.a_text , nfile ) != xbuf.a_text ) { 362 fprintf( stderr , "%s: couldn't read text space: " , whoami ); 363 fprintf( stderr , "can't do -c\n" ); 364 free( textspace ); 365 textspace = 0; 366 return; 367 } 368 } 369 /* 370 * information from a gmon.out file is in two parts: 371 * an array of sampling hits within pc ranges, 372 * and the arcs. 373 */ 374 getpfile(filename) 375 char *filename; 376 { 377 FILE *pfile; 378 FILE *openpfile(); 379 struct rawarc arc; 380 381 pfile = openpfile(filename); 382 readsamples(pfile); 383 /* 384 * the rest of the file consists of 385 * a bunch of <from,self,count> tuples. 386 */ 387 while ( fread( &arc , sizeof arc , 1 , pfile ) == 1 ) { 388 # ifdef DEBUG 389 if ( debug & SAMPLEDEBUG ) { 390 printf( "[getpfile] frompc 0x%x selfpc 0x%x count %d\n" , 391 arc.raw_frompc , arc.raw_selfpc , arc.raw_count ); 392 } 393 # endif DEBUG 394 /* 395 * add this arc 396 */ 397 tally( &arc ); 398 } 399 fclose(pfile); 400 } 401 402 FILE * 403 openpfile(filename) 404 char *filename; 405 { 406 struct gmonhdr tmp; 407 FILE *pfile; 408 int size; 409 int rate; 410 411 if((pfile = fopen(filename, "r")) == NULL) { 412 perror(filename); 413 done(); 414 } 415 fread(&tmp, sizeof(struct gmonhdr), 1, pfile); 416 if ( s_highpc != 0 && ( tmp.lpc != gmonhdr.lpc || 417 tmp.hpc != gmonhdr.hpc || tmp.ncnt != gmonhdr.ncnt ) ) { 418 fprintf(stderr, "%s: incompatible with first gmon file\n", filename); 419 done(); 420 } 421 gmonhdr = tmp; 422 if ( gmonhdr.version == GMONVERSION ) { 423 rate = gmonhdr.profrate; 424 size = sizeof(struct gmonhdr); 425 } else { 426 fseek(pfile, sizeof(struct ophdr), SEEK_SET); 427 size = sizeof(struct ophdr); 428 gmonhdr.profrate = rate = hertz(); 429 gmonhdr.version = GMONVERSION; 430 } 431 if (hz == 0) { 432 hz = rate; 433 } else if (hz != rate) { 434 fprintf(stderr, 435 "%s: profile clock rate (%d) %s (%d) in first gmon file\n", 436 filename, rate, "incompatible with clock rate", hz); 437 done(); 438 } 439 s_lowpc = (unsigned long) gmonhdr.lpc; 440 s_highpc = (unsigned long) gmonhdr.hpc; 441 lowpc = (unsigned long)gmonhdr.lpc / sizeof(UNIT); 442 highpc = (unsigned long)gmonhdr.hpc / sizeof(UNIT); 443 sampbytes = gmonhdr.ncnt - size; 444 nsamples = sampbytes / sizeof (UNIT); 445 # ifdef DEBUG 446 if ( debug & SAMPLEDEBUG ) { 447 printf( "[openpfile] hdr.lpc 0x%x hdr.hpc 0x%x hdr.ncnt %d\n", 448 gmonhdr.lpc , gmonhdr.hpc , gmonhdr.ncnt ); 449 printf( "[openpfile] s_lowpc 0x%x s_highpc 0x%x\n" , 450 s_lowpc , s_highpc ); 451 printf( "[openpfile] lowpc 0x%x highpc 0x%x\n" , 452 lowpc , highpc ); 453 printf( "[openpfile] sampbytes %d nsamples %d\n" , 454 sampbytes , nsamples ); 455 printf( "[openpfile] sample rate %d\n" , hz ); 456 } 457 # endif DEBUG 458 return(pfile); 459 } 460 461 tally( rawp ) 462 struct rawarc *rawp; 463 { 464 nltype *parentp; 465 nltype *childp; 466 467 parentp = nllookup( rawp -> raw_frompc ); 468 childp = nllookup( rawp -> raw_selfpc ); 469 if ( parentp == 0 || childp == 0 ) 470 return; 471 if ( kflag 472 && onlist( kfromlist , parentp -> name ) 473 && onlist( ktolist , childp -> name ) ) { 474 return; 475 } 476 childp -> ncall += rawp -> raw_count; 477 # ifdef DEBUG 478 if ( debug & TALLYDEBUG ) { 479 printf( "[tally] arc from %s to %s traversed %d times\n" , 480 parentp -> name , childp -> name , rawp -> raw_count ); 481 } 482 # endif DEBUG 483 addarc( parentp , childp , rawp -> raw_count ); 484 } 485 486 /* 487 * dump out the gmon.sum file 488 */ 489 dumpsum( sumfile ) 490 char *sumfile; 491 { 492 register nltype *nlp; 493 register arctype *arcp; 494 struct rawarc arc; 495 FILE *sfile; 496 497 if ( ( sfile = fopen ( sumfile , "w" ) ) == NULL ) { 498 perror( sumfile ); 499 done(); 500 } 501 /* 502 * dump the header; use the last header read in 503 */ 504 if ( fwrite( &gmonhdr , sizeof gmonhdr , 1 , sfile ) != 1 ) { 505 perror( sumfile ); 506 done(); 507 } 508 /* 509 * dump the samples 510 */ 511 if (fwrite(samples, sizeof (UNIT), nsamples, sfile) != nsamples) { 512 perror( sumfile ); 513 done(); 514 } 515 /* 516 * dump the normalized raw arc information 517 */ 518 for ( nlp = nl ; nlp < npe ; nlp++ ) { 519 for ( arcp = nlp -> children ; arcp ; arcp = arcp -> arc_childlist ) { 520 arc.raw_frompc = arcp -> arc_parentp -> value; 521 arc.raw_selfpc = arcp -> arc_childp -> value; 522 arc.raw_count = arcp -> arc_count; 523 if ( fwrite ( &arc , sizeof arc , 1 , sfile ) != 1 ) { 524 perror( sumfile ); 525 done(); 526 } 527 # ifdef DEBUG 528 if ( debug & SAMPLEDEBUG ) { 529 printf( "[dumpsum] frompc 0x%x selfpc 0x%x count %d\n" , 530 arc.raw_frompc , arc.raw_selfpc , arc.raw_count ); 531 } 532 # endif DEBUG 533 } 534 } 535 fclose( sfile ); 536 } 537 538 valcmp(p1, p2) 539 nltype *p1, *p2; 540 { 541 if ( p1 -> value < p2 -> value ) { 542 return LESSTHAN; 543 } 544 if ( p1 -> value > p2 -> value ) { 545 return GREATERTHAN; 546 } 547 return EQUALTO; 548 } 549 550 readsamples(pfile) 551 FILE *pfile; 552 { 553 register i; 554 UNIT sample; 555 556 if (samples == 0) { 557 samples = (UNIT *) calloc(sampbytes, sizeof (UNIT)); 558 if (samples == 0) { 559 fprintf( stderr , "%s: No room for %d sample pc's\n", 560 whoami , sampbytes / sizeof (UNIT)); 561 done(); 562 } 563 } 564 for (i = 0; i < nsamples; i++) { 565 fread(&sample, sizeof (UNIT), 1, pfile); 566 if (feof(pfile)) 567 break; 568 samples[i] += sample; 569 } 570 if (i != nsamples) { 571 fprintf(stderr, 572 "%s: unexpected EOF after reading %d/%d samples\n", 573 whoami , --i , nsamples ); 574 done(); 575 } 576 } 577 578 /* 579 * Assign samples to the procedures to which they belong. 580 * 581 * There are three cases as to where pcl and pch can be 582 * with respect to the routine entry addresses svalue0 and svalue1 583 * as shown in the following diagram. overlap computes the 584 * distance between the arrows, the fraction of the sample 585 * that is to be credited to the routine which starts at svalue0. 586 * 587 * svalue0 svalue1 588 * | | 589 * v v 590 * 591 * +-----------------------------------------------+ 592 * | | 593 * | ->| |<- ->| |<- ->| |<- | 594 * | | | | | | 595 * +---------+ +---------+ +---------+ 596 * 597 * ^ ^ ^ ^ ^ ^ 598 * | | | | | | 599 * pcl pch pcl pch pcl pch 600 * 601 * For the vax we assert that samples will never fall in the first 602 * two bytes of any routine, since that is the entry mask, 603 * thus we give call alignentries() to adjust the entry points if 604 * the entry mask falls in one bucket but the code for the routine 605 * doesn't start until the next bucket. In conjunction with the 606 * alignment of routine addresses, this should allow us to have 607 * only one sample for every four bytes of text space and never 608 * have any overlap (the two end cases, above). 609 */ 610 asgnsamples() 611 { 612 register int j; 613 UNIT ccnt; 614 double time; 615 unsigned long pcl, pch; 616 register int i; 617 unsigned long overlap; 618 unsigned long svalue0, svalue1; 619 620 /* read samples and assign to namelist symbols */ 621 scale = highpc - lowpc; 622 scale /= nsamples; 623 alignentries(); 624 for (i = 0, j = 1; i < nsamples; i++) { 625 ccnt = samples[i]; 626 if (ccnt == 0) 627 continue; 628 pcl = lowpc + scale * i; 629 pch = lowpc + scale * (i + 1); 630 time = ccnt; 631 # ifdef DEBUG 632 if ( debug & SAMPLEDEBUG ) { 633 printf( "[asgnsamples] pcl 0x%x pch 0x%x ccnt %d\n" , 634 pcl , pch , ccnt ); 635 } 636 # endif DEBUG 637 totime += time; 638 for (j = j - 1; j < nname; j++) { 639 svalue0 = nl[j].svalue; 640 svalue1 = nl[j+1].svalue; 641 /* 642 * if high end of tick is below entry address, 643 * go for next tick. 644 */ 645 if (pch < svalue0) 646 break; 647 /* 648 * if low end of tick into next routine, 649 * go for next routine. 650 */ 651 if (pcl >= svalue1) 652 continue; 653 overlap = min(pch, svalue1) - max(pcl, svalue0); 654 if (overlap > 0) { 655 # ifdef DEBUG 656 if (debug & SAMPLEDEBUG) { 657 printf("[asgnsamples] (0x%x->0x%x-0x%x) %s gets %f ticks %d overlap\n", 658 nl[j].value/sizeof(UNIT), svalue0, svalue1, 659 nl[j].name, 660 overlap * time / scale, overlap); 661 } 662 # endif DEBUG 663 nl[j].time += overlap * time / scale; 664 } 665 } 666 } 667 # ifdef DEBUG 668 if (debug & SAMPLEDEBUG) { 669 printf("[asgnsamples] totime %f\n", totime); 670 } 671 # endif DEBUG 672 } 673 674 675 unsigned long 676 min(a, b) 677 unsigned long a,b; 678 { 679 if (a<b) 680 return(a); 681 return(b); 682 } 683 684 unsigned long 685 max(a, b) 686 unsigned long a,b; 687 { 688 if (a>b) 689 return(a); 690 return(b); 691 } 692 693 /* 694 * calculate scaled entry point addresses (to save time in asgnsamples), 695 * and possibly push the scaled entry points over the entry mask, 696 * if it turns out that the entry point is in one bucket and the code 697 * for a routine is in the next bucket. 698 */ 699 alignentries() 700 { 701 register struct nl *nlp; 702 unsigned long bucket_of_entry; 703 unsigned long bucket_of_code; 704 705 for (nlp = nl; nlp < npe; nlp++) { 706 nlp -> svalue = nlp -> value / sizeof(UNIT); 707 bucket_of_entry = (nlp->svalue - lowpc) / scale; 708 bucket_of_code = (nlp->svalue + UNITS_TO_CODE - lowpc) / scale; 709 if (bucket_of_entry < bucket_of_code) { 710 # ifdef DEBUG 711 if (debug & SAMPLEDEBUG) { 712 printf("[alignentries] pushing svalue 0x%x to 0x%x\n", 713 nlp->svalue, nlp->svalue + UNITS_TO_CODE); 714 } 715 # endif DEBUG 716 nlp->svalue += UNITS_TO_CODE; 717 } 718 } 719 } 720 721 bool 722 funcsymbol( nlistp ) 723 struct nlist *nlistp; 724 { 725 char *name, c; 726 727 /* 728 * must be a text symbol, 729 * and static text symbols don't qualify if aflag set. 730 */ 731 if ( ! ( ( nlistp -> n_type == ( N_TEXT | N_EXT ) ) 732 || ( ( nlistp -> n_type == N_TEXT ) && ( aflag == 0 ) ) ) ) { 733 return FALSE; 734 } 735 /* 736 * name must start with an underscore if uflag is set. 737 * can't have any `funny' characters in name, 738 * where `funny' includes `.', .o file names 739 * and `$', pascal labels. 740 * need to make an exception for sparc .mul & co. 741 * perhaps we should just drop this code entirely... 742 */ 743 name = strtab + nlistp -> n_un.n_strx; 744 if ( uflag && *name != '_' ) 745 return FALSE; 746 #ifdef sparc 747 if ( *name == '.' ) { 748 char *p = name + 1; 749 if ( *p == 'u' ) 750 p++; 751 if ( strcmp ( p, "mul" ) == 0 || strcmp ( p, "div" ) == 0 || 752 strcmp ( p, "rem" ) == 0 ) 753 return TRUE; 754 } 755 #endif 756 while ( c = *name++ ) { 757 if ( c == '.' || c == '$' ) { 758 return FALSE; 759 } 760 } 761 return TRUE; 762 } 763 764 done() 765 { 766 767 exit(0); 768 } 769