home *** CD-ROM | disk | FTP | other *** search
/ Simtel MSDOS 1992 September / Simtel20_Sept92.cdr / msdos / c / profil.arc / PROFILE.C < prev    next >
C/C++ Source or Header  |  1989-09-04  |  25KB  |  727 lines

  1. /****************************************************************/
  2. /*               PROFILE.C                */
  3. /*                                */
  4. /* Execution time profiler. Reads an executable and it's link    */
  5. /* map and produces an output file with hit counts for all the    */
  6. /* functions listed in the map file. Handles (by option selec-    */
  7. /* tion) both MicroSoft LINK and Borland TLINK map files.    */
  8. /* PROF only profiles '.EXE' files.                */
  9. /****************************************************************/
  10. /* Command line:                        */
  11. /*                                */
  12. /* prof [-adin0$?] [-#<n>] [-m<mapfile>] [-o<outfile>] <cmd>    */
  13. /*                                */
  14. /* -d    Include DOS areas (DOS and BIOSes) in the profiling.    */
  15. /* -a    Sort output table by address, not by frequency.        */
  16. /* -i    Include intrinsic functions (name starting with '__,    */
  17. /*    or containing '@' or '$')' in the profiling.        */
  18. /* -n    Sort output table by name, not by frequency.        */ 
  19. /* -0    List also functions that got no hits.            */
  20. /* -#<n>                            */
  21. /*    Execute the profiled command <n> times to increase    */
  22. /*    statistical confidence.                    */
  23. /* -?    Report the address of the own PSP and  main() function.    */
  24. /* -$    Report the address of the top DOS aloccation.        */ 
  25. /* -m<mapfile>                            */
  26. /*    Read link map from file <mapfile> (default <cmd>.MAP).    */
  27. /* -o<outfile>                            */
  28. /*    Output profile table in file <outfile> (default        */
  29. /*    <cmd.PRF>).                        */
  30. /* <cmd>                            */
  31. /*    The normal command line for the profiled command.    */
  32. /****************************************************************/
  33. /* Exit codes delivered by prof:                */
  34. /*                                */
  35. /*  0:    No error.                        */
  36. /*  1:    Illegal command line arguments.                */
  37. /*  2:    Cannot open command binary.                */
  38. /*  3:    Cannot open linker map file.                */
  39. /*  4:    Cannot open output file.                */
  40. /*  5:    Invalid map file format.                */
  41. /*  6:    Insufficient memory.                    */
  42. /*  7:    Invalid EXE file format.                */
  43. /*  8:    EXEC error                        */
  44. /*  9:    Program too fast - no hits.                */
  45. /****************************************************************/
  46. /* Revised:                            */
  47. /* 1.02: Doesn't show functions that were not hit, thus        */
  48. /*     reducing the amount of output data:        890909    */
  49. /* 1.01: Attempts to make load address more certain:    890906    */
  50. /* 1.00: Functional:                    890904    */
  51. /****************************************************************/
  52. #include <stdio.h>
  53. #include <stdlib.h>
  54. #include <string.h>
  55. #include <ctype.h>
  56. #include <io.h>
  57. #include <fcntl.h>
  58. #include <sys/types.h>
  59. #include <sys/stat.h>
  60. #include <process.h>
  61. #include <dos.h>
  62. #include <errno.h>
  63. #if TRC_2_0
  64. #include <alloc.h>
  65. #endif
  66. #if MSC_5_1
  67. #include <malloc.h>
  68. #include <signal.h>
  69. #endif
  70.  
  71. /****************************************************************/
  72. /*          Adjuct constant for file load addresses.        */
  73. /*   THIS IS COMPILER SPECIFIC AND MUST BE FOUND EMPIRICALLY!    */
  74. /****************************************************************/
  75.  
  76. #if TRC_2_0
  77. #define    ADJ_CONST    0L
  78. #endif
  79. #if MSC_5_1
  80. #define    ADJ_CONST    0x160L
  81. #endif
  82.  
  83. /* Compiler-specific #defines for DOS access functions */
  84.  
  85. #if TRC_2_0
  86. #define    ENABLE()    enable()
  87. #define    DISABLE()    disable()
  88. #define    ALLMEM(s,p,r)    (r = allocmem(0xffff,p))
  89. #define    ALLMEMERR(s,p)    (allocmem(s,p) != -1)
  90. #define    FREEMEM(p)    freemem(p)
  91. #define    CTRLBREAK(f)    ctrlbrk(f)
  92. #define    SETVECT(i,f)    setvect(i,f)
  93. #define    GETVECT(i)    getvect(i)
  94. #endif
  95. #if MSC_5_1
  96. #define    ENABLE()    _enable()
  97. #define    DISABLE()    _disable()
  98. #define    ALLMEM(s,p,r)    (_dos_allocmem(0xffff,&r))
  99. #define    ALLMEMERR(s,p)    (_dos_allocmem(s,p) != 0)
  100. #define    FREEMEM(p)    _dos_freemem(p)
  101. #define    CTRLBREAK(f)    signal(SIGINT,f)
  102. #define    SETVECT(i,f)    _dos_setvect(i,f)
  103. #define    GETVECT(i)    _dos_getvect(i)
  104. #endif
  105.  
  106. #define    TMRINT    8                /* Timer hardware interrupt */
  107. #define    MAXFUNC 2000                /* Max 2000 functions... */
  108. #define    CMDSIZ    130                /* Cmd's command line size */
  109. #define    FNSIZ    130                /* Max file name size */
  110. #define    LNSIZ    130                /* Max map file line length */
  111. #define    STSIZ    35                /* Size of small strings */
  112.  
  113. #define    ABSTYP    1                /* Table entry is absolute */
  114. #define    USRTYP    2                /* Table entry is USR func */
  115. #define    INRTYP    4                /* Table entry is INR type */
  116. #define    DOSTYP    8                /* Table entry is DOS type */
  117.  
  118. typedef struct                    /* Entry in funcion table */
  119.   {
  120.   unsigned long addr;                /* Function start address */
  121.   unsigned long hits;                /* Hit count */
  122.   char           *name;                /* Function name */
  123.   char        typ;                /* Function properties */
  124.   char        dummy;                /* For word-aligning */
  125.   } fdesc;                    /* Function descriptor */
  126.  
  127. static    char    exename[FNSIZ]  = {0};        /* Prof'ed command's binary */
  128. static    char    mapname[FNSIZ]  = {0};        /* Prof'ed command's map */
  129. static    char    outname[FNSIZ]  = {0};        /* Prof output table file */
  130.  
  131. static    FILE   *file = NULL;            /* For read/write of files */
  132. static    struct stat statinfo;            /* Stat() buffer */
  133. static    fdesc  *descs;                /* Start of desc table */
  134. static    int    exe_count = 1;            /* # times to exec command */
  135. static    int    nfuncs;                /* Number of functions */
  136. static    char    name_ordr = 0;            /* If sorting address-wise */
  137. static    char    addr_ordr = 0;            /* If sorting adress.-wise */
  138. static    char    tell_psp = 0;            /* Tell load PSP address */
  139. static    char    list_nulls = 0;            /* IF listing no-hit funcs */
  140. static    char    wrt_msk = USRTYP;        /* Default only user funcs */
  141. static    double    tot_hits = 0.0;            /* Total function hits */
  142. static    double    dsp_hits = 0.0;            /* Total displayed hits */
  143. static    double    usr_hits = 0.0;            /* Total user pgm hits */
  144. static    char  **cmdline;            /* Command line array ptr */
  145. static    unsigned freemem_pg = 0;        /* At beg of free memory */
  146. static    unsigned load_psp = 0;            /* Paragr of load area */
  147.  
  148. static void (interrupt *old_tmr_handler)() = NULL; /* Original handler */
  149.  
  150. static    char   *msgs[] =
  151.   {
  152.   "profile: ",                    /* 0 */
  153.   "Illegal command line option: `-%s'\n",    /* 1 */
  154.   "No command specified for profiling\n",    /* 2 */
  155.   "Cannot find executable `%s'\n",        /* 3 */
  156.   "`%s' is a directory\n",            /* 4 */
  157.   "Cannot find linker map file `%s'\n",        /* 5 */
  158.   "Output file `%s' is write-protected\n",    /* 6 */
  159.   "Invalid map file format in `%s'",        /* 7 */
  160.   "Insufficient memory for function tables\n",    /* 8 */
  161.   "Not enough function table slots\n",        /* 9 */
  162.   "Cannot open output file `%s'\n",        /* 10 */
  163.   "Insufficient memory to execute command",    /* 11 */
  164.   "Error executing command %s\n",        /* 12 */
  165.   "Invalid EXE file format in `%s'\n",        /* 13 */
  166.   "@(#)profile.c v.1.02 - 890909",        /* 14 */
  167.   "Program ran too fast - no hits!\n"        /* 15 */
  168.   } ; /* msgs */
  169.  
  170. static    void    get_cmdline();            /* Interpret command line */
  171. static    void    set_filenames();        /* Fix definitive filenames */
  172. static    void    check_files();            /* Check files are OK */
  173. static    void    read_map();            /* Read map, build tables */
  174. static    void    add_func();            /* add function to table */
  175. static    int    blankline();            /* Check if line is blank */
  176. static    void    adjust();            /* Set correct func addrs */
  177. static    void    profile();            /* Do the actual profiling */
  178. static    void    write_result();            /* Output result */
  179. static    int    fadr_comp();            /* Compare func addresses */
  180. static    int    fhit_comp();            /* Compare func hitcounts */
  181. static    int    fnam_comp();            /* Compare func names */
  182. static    void    usage();            /* Help routine */
  183. static    void    error();            /* Error/Abort routine */
  184.  
  185. static    int    brk_handler();            /* SIGINT handler */
  186. static    void    interrupt tmr_handler();    /* Timer interrupt handler */
  187.  
  188. /****************************************************************/
  189.  
  190. void    main(narg, args)
  191.   int    narg;
  192.   char *args[];
  193.   {
  194.   CTRLBREAK(brk_handler);
  195.   get_cmdline(narg, args);
  196.   set_filenames();
  197.   check_files();
  198.   read_map();
  199.   adjust();
  200.   profile();
  201.   write_result();
  202.   error(0,"");
  203.   } /* main */
  204.  
  205. /****************************************************************/
  206. /* Get_cmdline() extracts all switches etc, gets the name of    */
  207. /* the command to be profiled, and sets up file names. It also    */
  208. /* builds the command line for the command to be profiled.    */
  209. /****************************************************************/
  210.  
  211. static    void    get_cmdline(narg, args)
  212.   int          narg;
  213.   char         *args[];
  214.   {
  215.   int          i = 1;
  216.   char         *p;
  217.   unsigned long   m;
  218.  
  219.   while ((i < narg) && (*args[i] == '-'))    /* Extract switches */
  220.     {
  221.     args[i]++;
  222.     while (*args[i])                /* Get switches */
  223.       {
  224.       switch (*args[i])
  225.     {
  226.     case '#':   if (sscanf(args[i]+1, "%d", &exe_count) != 1)
  227.               error(1, msgs[1], args[i]);
  228.             args[i] = " ";
  229.             break;
  230.     case '?':   printf("Actual PSP is at absolute address 0x%05x0\n",
  231.                             _psp);
  232.             p = (char *) main;        /* Trick for MSC */
  233.             m = FP_SEG(p);
  234.             m <<= 4;
  235.             m += FP_OFF(p);
  236.             printf("Main() fnc is at absolute address 0x%06lx\n",
  237.                             m);
  238.             error(0,"");
  239.     case '$':   tell_psp = 1;        /* Tell DOS alloc address */
  240.             break;
  241.     case 'a':   name_ordr = 0;        /* Sort table by freq */
  242.             addr_ordr = 1;
  243.             break;
  244.     case 'd':   wrt_msk |= DOSTYP;        /* Include DOS areas */
  245.             break;
  246.     case 'i':   wrt_msk |= INRTYP;        /* Include intrinsic funcs */
  247.             break;
  248.     case 'n':   name_ordr = 1;        /* Sort table by name */
  249.             addr_ordr = 0;
  250.             break;
  251.     case '0':   list_nulls = 1;        /* List no-hit funcs */
  252.             break;
  253.     case 'm':   strcpy(mapname, args[i]+1);    /* Map file name */
  254.             args[i] = " ";
  255.             break;
  256.     case 'o':   strcpy(outname, args[i]+1);    /* Output table file name */
  257.             args[i] = " ";
  258.             break;
  259.     default:    error(1, msgs[1], args[i]);
  260.     } /* switch */
  261.       args[i]++;
  262.       } /* while */
  263.     i++;
  264.   } /* while */
  265.  
  266.   if (i >= narg)                /* Check there is a command */
  267.     error(1, msgs[2]);
  268.   strcpy(exename,args[i]);
  269.   cmdline = args + i;
  270.   } /* get_cmdline */
  271.  
  272. /****************************************************************/
  273. /* Set_filenames() adjust names of needed file names based on    */
  274. /* defaults and options.                    */
  275. /****************************************************************/
  276.  
  277. static    void    set_filenames()
  278.   {
  279.   if (!mapname[0])                /* Set default mapfile name */
  280.     {
  281.     strcpy(mapname, exename);
  282.     strcat(mapname, ".map");
  283.     } /* if */
  284.   if (!outname[0])                /* Set default outfile name */
  285.     {
  286.     strcpy(outname, exename);
  287.     strcat(outname, ".prf");
  288.     } /* if */
  289.   strcat(exename,".exe");            /* It's an 'EXE' file */
  290.   } /* set_filenames */
  291.  
  292. /****************************************************************/
  293. /* Check_files() checks that all files are available, readable    */
  294. /* and writeable as required.                    */
  295. /****************************************************************/
  296.  
  297. static    void    check_files()
  298.   {
  299.   if (stat(exename, &statinfo))            /* Check executable exists */
  300.     error(2, msgs[3], exename);
  301.   if (statinfo.st_mode & S_IFDIR)
  302.     error(2, msgs[4], exename);
  303.   if (stat(mapname, &statinfo))            /* Check mapfile exists */
  304.     error(3, msgs[5], mapname);
  305.   if (statinfo.st_mode & S_IFDIR)
  306.     error(3, msgs[4], mapname);
  307.   if (stat(outname, &statinfo))            /* Check outfile writeable */
  308.     return;
  309.   if (statinfo.st_mode & S_IFDIR)
  310.     error(4, msgs[4], outname);
  311.   if (!(statinfo.st_mode & S_IWRITE))
  312.     error(4, msgs[6], outname);
  313.   } /* check_files */
  314.  
  315. /****************************************************************/
  316. /* Read_map() reads the map file into memory and builds the    */
  317. /* linked list of entries.                    */
  318. /****************************************************************/
  319.  
  320. static    void    read_map()
  321.   {
  322.   char     line[LNSIZ+1];
  323.   char     str1[STSIZ],str2[STSIZ],str3[STSIZ],str4[STSIZ];
  324.   fdesc *p;
  325.   long     ofs, seg;
  326.  
  327.   if ((p = descs = calloc(MAXFUNC,sizeof(fdesc))) == NULL)
  328.     error(6, msgs[8]);
  329.  
  330.   if ((file = fopen(mapname,"r")) == NULL)    /* 'Impossible' */
  331.     error(3, msgs[5], mapname);
  332.  
  333.   while (fgets(line, LNSIZ, file) != NULL)    /* Find 'Add Pub by Val' */
  334.     {
  335.     if (
  336.          (sscanf(line, " %s %s %s %s ", str1, str2, str3, str4) == 4)
  337.        && 
  338.      (stricmp(str1, "Address") == 0)
  339.        && 
  340.      (stricmp(str2, "Publics") == 0)
  341.        && 
  342.      (strcmp(str3, "by") == 0)
  343.        && 
  344.      (strcmp(str4, "Value") == 0)
  345.        )
  346.       break;
  347.     } /* while */
  348.   if (feof(file))
  349.     error(5, msgs[7], mapname);
  350.  
  351.   while (fgets(line, LNSIZ, file) != NULL)    /* Find Non-blank line */
  352.     if (!blankline(line))
  353.       break;
  354.   if (feof(file))
  355.     error(5, msgs[7], mapname);
  356.  
  357.   add_func(p++, "Low Mem", 0l, ABSTYP|DOSTYP);    /* Make entry for low mem */
  358.   add_func(p++, "DOS", 0x400l, ABSTYP|DOSTYP);    /* Make entry for low mem */
  359.   seg = _psp;                    /* Get profiler's psp */
  360.   add_func(p++,"Profiler",seg<<4,ABSTYP|DOSTYP);/* Make entry for prof */
  361.   
  362.   nfuncs = 2;
  363.   do                        /* Process and read another */
  364.     {
  365.     if (blankline(line))            /* Blank line end of data */
  366.       break;
  367.     if (sscanf(line, " %lx:%lx Abs %s ", &seg, &ofs, str1) != 3)
  368.       if (sscanf(line, " %lx:%lx    %s ", &seg, &ofs, str1) != 3)
  369.     error(5, msgs[7], mapname);
  370.     if (str1[0] == '_')                /* Play with '_' for */
  371.       str1[0] = 1;                /* alpha sorting */
  372.     if (str1[1] == '_')                /* This is converted back */
  373.       str1[1] = 0x7f;                /* on output */
  374.  
  375.     if (                    /* Intrinsic function */
  376.          ((str1[0] == 1) && (str1[1] == 0x7f))    /* with '__' */
  377.        ||
  378.          (strchr(str1,'@') != NULL)        /* or with '@' */
  379.        ||
  380.          (strchr(str1,'$') != NULL)        /* or with '$' */
  381.        )
  382.       add_func(p++, str1, (seg << 4) + ofs, INRTYP);/* Make entry */
  383.     else                    /* User function */
  384.       add_func(p++, str1, (seg << 4) + ofs, USRTYP);/* Make entry */
  385.     nfuncs++;
  386.     if (nfuncs > (MAXFUNC - 10))
  387.       error(6, msgs[9]);
  388.     }
  389.   while (fgets(line, LNSIZ, file) != NULL);
  390.  
  391.   add_func(p++,"EGA BIOS", 0xc0000l, ABSTYP|DOSTYP);
  392.   add_func(p++,"Fixed Disk BIOS", 0xc8000l, ABSTYP|DOSTYP);
  393.   add_func(p++,"System ROM", 0xf0000l, ABSTYP|DOSTYP);
  394.   add_func(p++,"System BIOS", 0xfe000l, ABSTYP|DOSTYP);
  395.   nfuncs += 4;
  396.  
  397.   fclose(file);
  398.   file = (FILE *) NULL;
  399.   } /* read_map */
  400.  
  401. /****************************************************************/
  402. /* Add_func() adds a function to the function table.        */
  403. /****************************************************************/
  404.  
  405. static    void    add_func(p, nam, addr, typ)
  406.   fdesc    *p;
  407.   char    *nam;
  408.   long     addr;
  409.   char     typ;
  410.   {
  411.   p->addr = addr;
  412.   p->hits = 0l;
  413.   if ((p->name = calloc(1,strlen(nam)+1)) == NULL)
  414.     error(6, msgs[8]);
  415.   strcpy(p->name, nam);
  416.   p->typ = typ;
  417.   } /* add_func */
  418.  
  419. /****************************************************************/
  420. /* Blankline() returns 1 if the passed line is entirely blank.    */
  421. /****************************************************************/
  422.  
  423. static    int    blankline(s)
  424.   char    *s;
  425.   {
  426.   while (*s)
  427.     {
  428.     if (!isspace(*s))
  429.       return(0);
  430.     s++;
  431.     } /* while */
  432.   return(1);
  433.   } /* blankline */
  434.  
  435. /****************************************************************/
  436. /* Adjust() finds out where in memory the executable will be    */
  437. /* loaded, and adjust function addresses accordingly. Does this    */
  438. /* By allocating first a hole, then sys memory 1, and then sys    */
  439. /* memory 2. Sys memory 2 indicates first free address, and is    */
  440. /* immediately re-freed. The hole is also freed to function as    */
  441. /* work space for functions that will run before the actual    */
  442. /* execution of the profiled program. THIS IS TRICKY AND COM-    */
  443. /* PILER DEPENDENT...                        */
  444. /****************************************************************/
  445.  
  446. static    void    adjust()
  447.   {
  448.   char      *hole;
  449.   long       adj;
  450.   int       i;
  451.   int       maxsz;
  452.  
  453.   if ((hole = malloc(0x800)) == NULL)        /* Fix workspace for others */
  454.     error(6, msgs[11]);
  455.   if (ALLMEMERR(0x20, &freemem_pg))        /* Grab small mem over it */
  456.     error(6, msgs[11]);
  457.   ALLMEM(0xffff, &load_psp, maxsz);        /* See what max space is */
  458.   if (ALLMEMERR(maxsz, &load_psp))        /* Grab it to know address */
  459.     error(6, msgs[11]);
  460.   free (hole);                    /* Make workspace available */
  461.  
  462.   adj = load_psp;
  463.   adj <<= 4;
  464.   adj += 0x100 + ADJ_CONST;
  465.  
  466.   if (tell_psp)                    /* If display free start */
  467.     printf("Expecting PSP at absolute address 0x%06lx\n", adj-0x100L);
  468.        
  469.   for (i = 0; i < nfuncs; i++)            /* Add adj to func addr:s */
  470.     if (!((descs + i)->typ & ABSTYP))        /* Only relocatable ones */
  471.       (descs + i)->addr += adj;
  472.  
  473.   qsort(descs,nfuncs,sizeof(fdesc),fadr_comp);    /* Sort in address order */
  474.   } /* adjust */
  475.  
  476. /****************************************************************/
  477. /* Profile() does the profiling. It finds out where the pro-    */
  478. /* filed command will be loaded, adjusts function addresses    */
  479. /* accordingly, starts timer interrupts, and executes the com-    */
  480. /* mand as a subshell.                        */
  481. /****************************************************************/
  482.  
  483. static    void    profile()
  484.   {
  485.   int    i = 0;
  486.  
  487.   old_tmr_handler = GETVECT(TMRINT);        /* Save old int vector */
  488.   SETVECT(TMRINT,tmr_handler);            /* Start profiling */
  489.   DISABLE();
  490.   FREEMEM(load_psp);                /* Free the load area */
  491.   load_psp = 0;                    /* To not free it at exit */
  492.   ENABLE();
  493.   while(i++ < exe_count)
  494.     {
  495.     fprintf(stderr, "%-3d ----- Executing %s\n", i, *cmdline);
  496.     if (spawnv(P_WAIT, *cmdline, cmdline) == -1)
  497.       {
  498.       switch(errno)
  499.     {
  500.     case ENOEXEC:    error(7,msgs[13], exename);
  501.             break;
  502.         case ENOMEM:    error(6,msgs[11]);
  503.             break;
  504.         default:        error(8,msgs[12]);
  505.         } /* switch */
  506.       } /* if */
  507.     } /* switch */
  508.  
  509.   SETVECT(TMRINT,old_tmr_handler);
  510.   (char *) old_tmr_handler = NULL;
  511.  
  512.   DISABLE();
  513.   FREEMEM(freemem_pg);
  514.   freemem_pg = 0;
  515.   ENABLE();
  516.   fprintf(stderr, "--------- Executing completed\n");
  517.   } /* profile */
  518.  
  519. /****************************************************************/
  520. /* Write_result() sorts the data, computes profiling percen-    */
  521. /* tages, and write out the resulting list.            */
  522. /****************************************************************/
  523.  
  524. static    void    write_result()
  525.   {
  526.   int     i;
  527.  
  528.   if (name_ordr)                /* Sort table by name? */
  529.     qsort(descs,nfuncs,sizeof(fdesc),fnam_comp);/* Sort in name order */
  530.   else
  531.     if (!addr_ordr)
  532.       qsort(descs,nfuncs,sizeof(fdesc),fhit_comp);/* Sort in freq order */
  533.  
  534.   if ((file = fopen(outname,"w")) == NULL)    /* Possible if command did */
  535.     error(4, msgs[10], outname);        /* something like chmod -w */
  536.  
  537.   for (i = 0; i < nfuncs; i++)            /* Add up total hit counts */
  538.     {
  539.     tot_hits += (double) ((descs + i)->hits);    /* Add upp total hits */
  540.     if ((descs + i)->typ & wrt_msk)
  541.       dsp_hits += (double) ((descs + i)->hits);    /* Add upp displayed hits */
  542.     if ((descs + i)->typ & USRTYP)
  543.       usr_hits += (double) ((descs + i)->hits);    /* Add up user hits */
  544.     } /* for */
  545.   if (tot_hits == 0.0)                /* Avoid div by 0.0 */
  546.     tot_hits = 1.0;
  547.   if (dsp_hits == 0.0)                /* Avoid div by 0.0 */
  548.     {
  549.     if (!tell_psp)
  550.       error(9,msgs[15]);
  551.     else
  552.       dsp_hits = 1.0;
  553.     } /* if */
  554.   if (usr_hits == 0.0)                /* Avoid div by 0.0 */
  555.     usr_hits = 1.0;
  556.  
  557.   fprintf(file, "Function name          Addr      Total    Disp    User\n\n");
  558.   for (i = 0; i < nfuncs; i++)
  559.     {
  560.     if (((descs + i)->hits == 0) &&        /* Don't show 0 hit funcs */
  561.         !(list_nulls || tell_psp))
  562.       continue;
  563.     if (wrt_msk & (descs +i)->typ)
  564.       {
  565.       if ((descs + i)->name[0] == 1)        /* Reconvert fixes done */
  566.     (descs + i)->name[0] = '_';        /* when reading the map */
  567.       if ((descs + i)->name[1] == 0x7f)
  568.     (descs + i)->name[1] = '_';
  569.       fprintf(file, "%-20s  %05lx     %6.2f  %6.2f",
  570.     (descs + i)->name, (descs + i)->addr,
  571.     100.0 * ((descs + i)->hits / tot_hits),
  572.     100.0 * ((descs + i)->hits / dsp_hits));
  573.       if ((descs + i)->typ & USRTYP)        /* Only usrfuncs get col 3 */
  574.     fprintf(file,"  %6.2f", 100.0 * ((descs + i)->hits / usr_hits));
  575.       fprintf(file,"\n");
  576.       } /* if */
  577.     } /* for */
  578.   fprintf(file, "\nStatistics based on %6.0f hits\n", tot_hits);
  579.   fclose(file);
  580.   file = (FILE *) NULL;
  581.   } /* write_result */
  582.  
  583. /****************************************************************/
  584. /* Fadr_comp() compares addresses of two functions. If address    */
  585. /* values are the same, name decides.                */
  586. /****************************************************************/
  587.  
  588. static    int    fadr_comp(f1, f2)
  589.   fdesc    *f1, *f2;
  590.   {
  591.   if (f1->addr > f2->addr)
  592.     return(1);
  593.   if (f1->addr < f2->addr)
  594.     return(-1);
  595.   return(fnam_comp(f1,f2));
  596.   } /* fadr_comp */
  597.  
  598. /****************************************************************/
  599. /* Fhit_comp() compares hit counts of two function table    */
  600. /* entries. If counts are the same, name decides.        */
  601. /****************************************************************/
  602.  
  603. static    int    fhit_comp(f1, f2)
  604.   fdesc    *f1, *f2;
  605.   {
  606.   if (f1->hits > f2->hits)
  607.     return(-1);
  608.   if (f1->hits < f2->hits)
  609.     return(1);
  610.   return(fnam_comp(f1,f2));
  611.   } /* fhit_comp */
  612.  
  613. /****************************************************************/
  614. /* Fnam_comp() compares names of two function table entries.    */
  615. /****************************************************************/
  616.  
  617. static    int    fnam_comp(f1, f2)
  618.   fdesc    *f1, *f2;
  619.   {
  620.   return (stricmp(f1->name, f2->name));
  621.   } /* fnam_comp */
  622.  
  623. /****************************************************************/
  624. /* Usage() displays a usage menu.                */
  625. /****************************************************************/
  626.  
  627. static    void    usage()
  628.   {
  629.   fprintf(stderr,
  630.  "Usage: profile [-adin0] [-#<n>] [-m<map>] [-o<out>] <cmd> [<args>...]\n");
  631.   fprintf(stderr,
  632.  "       -a       Sort output by adress, not by frequency\n");
  633.   fprintf(stderr,
  634.  "       -d       Include DOS and BIOS areas in profiling\n");
  635.   fprintf(stderr,
  636.  "       -i       Include intrinsic functions (starting with `__', or\n");
  637.   fprintf(stderr,
  638.  "                containing the characters `@' or '$') in profiling\n");
  639.   fprintf(stderr,
  640.  "       -n       Sort output by name, not by frequency\n");
  641.   fprintf(stderr,
  642.  "       -0       List also functions that had zero frequency\n");
  643.   fprintf(stderr,
  644.  "       -#<n>    Execute profiled command <n> times (default 1)\n");
  645.   fprintf(stderr,
  646.  "       -m<map>  Use file <map> as linker map info (default <cmd>.MAP)\n");
  647.   fprintf(stderr,
  648.  "       -o<out>  Put output result in file <out> (default <cmd>.PRF)\n");
  649.   fprintf(stderr,
  650.  "       <cmd>    Name of command to be profiled (including path)\n");
  651.   fprintf(stderr,
  652.  "       <args>   Optional arguments to <cmd>\n");
  653.   } /* usage */
  654.  
  655. /****************************************************************/
  656. /* Error()                            */
  657. /*                                */
  658. /* Print an error diagnosis and exit.                */
  659. /****************************************************************/
  660. /*VARARGS*/
  661. static    void error(ercode, ermsg, s1, s2, s3, s4)
  662.   int     ercode;
  663.   char    *ermsg;
  664.   char    *s1, *s2, *s3, *s4;
  665.   {
  666.   if ((char *) old_tmr_handler != NULL)
  667.     SETVECT(TMRINT,old_tmr_handler);
  668.   if (freemem_pg)
  669.     FREEMEM(freemem_pg);
  670.   if (load_psp)
  671.     FREEMEM(load_psp);
  672.   if (ercode != 0)
  673.     {
  674.     fprintf(stderr, msgs[0]);
  675.     fprintf(stderr, ermsg, s1, s2, s3, s4);
  676.     } /* if */
  677.   if (ercode == 1)
  678.     usage();
  679.   if (file != (FILE *) NULL)
  680.     fclose(file);
  681.   exit(ercode);
  682.   } /* error */
  683.  
  684. /****************************************************************/
  685. /* Brk_handler() catches CTRL-C interrupts.            */
  686. /****************************************************************/
  687.  
  688. static    int    brk_handler()
  689.   {
  690.   CTRLBREAK(brk_handler);
  691.   error(255,"User abort\n");
  692.   return(0);                    /* Actually not executed */
  693.   } /* brk_handler */
  694.  
  695. /****************************************************************/
  696. /* Tmr_handler() is the interrupt handler that updates hit    */
  697. /* counts. It must be fast.                    */
  698. /****************************************************************/
  699.  
  700. #if TRC_2_0
  701. static    void interrupt tmr_handler(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs)
  702.   unsigned bp,di,si,ds,es,dx,cx,bx,ax,ip,cs;
  703. #endif
  704. #if MSC_5_1
  705. static    void interrupt tmr_handler(es,ds,di,si,bp,sp,bx,dx,cx,ax,ip,cs)
  706.   unsigned es,ds,di,si,bp,sp,bx,dx,cx,ax,ip,cs;
  707. #endif
  708.   {
  709.   long    addr;
  710.   int    lower, upper, middle;
  711.  
  712.   addr = ((unsigned long)cs << 4) + (unsigned long) ip;
  713.   lower = 0;
  714.   upper = nfuncs - 1;
  715.  
  716.   while (upper - lower > 1)
  717.     {
  718.     middle = (lower + upper) / 2;
  719.     if ((descs + middle)->addr <= addr)
  720.       lower = middle;
  721.     else
  722.       upper = middle;
  723.     } /* while */
  724.   (descs + lower)->hits++;
  725.   (*old_tmr_handler)();
  726.   } /* tmr_handler */
  727.