gcov.c (bb_file_time): New static variable.
authorNathan Sidwell <nathan@codesourcery.com>
Sun, 4 Aug 2002 17:06:10 +0000 (17:06 +0000)
committerNathan Sidwell <nathan@gcc.gnu.org>
Sun, 4 Aug 2002 17:06:10 +0000 (17:06 +0000)
* gcov.c (bb_file_time): New static variable.
(object_directory): May also be object file.
(preserve_paths): New static variable.
(print_usage): Adjust.
(options): Adjust.
(process_args): Adjust.
(open_files): Simplify. Cope when OBJECT_DIRECTORY is an object
file. Find modification date on bb file.
(read_profile): Don't rewind a NULL file.
(format_hwint): New static function.
(function_summary): Use format_hwint.
(output_data): SOURCE_FILE_NAME is never relative to
OBJECT_DIRECTORY. Use format_hwint. Adjust gcov file name
mangling. Adjust output format to make it more machine readable.
* doc/gcov.texi: Document & clarify semantics.

From-SVN: r56028

gcc/ChangeLog
gcc/doc/gcov.texi
gcc/gcov.c

index 0ea81d1f89f8253053194f45acc9eaca6cab9644..766d1bc51496be0deeded6219ac4504084307a59 100644 (file)
@@ -1,3 +1,21 @@
+2002-08-04  Nathan Sidwell  <nathan@codesourcery.com>
+
+       * gcov.c (bb_file_time): New static variable.
+       (object_directory): May also be object file.
+       (preserve_paths): New static variable.
+       (print_usage): Adjust.
+       (options): Adjust.
+       (process_args): Adjust.
+       (open_files): Simplify. Cope when OBJECT_DIRECTORY is an object
+       file. Find modification date on bb file.
+       (read_profile): Don't rewind a NULL file.
+       (format_hwint): New static function.
+       (function_summary): Use format_hwint.
+       (output_data): SOURCE_FILE_NAME is never relative to
+       OBJECT_DIRECTORY. Use format_hwint. Adjust gcov file name
+       mangling. Adjust output format to make it more machine readable.
+       * doc/gcov.texi: Document & clarify semantics.
+       
 2002-08-04  Joseph S. Myers  <jsm@polyomino.org.uk>
 
        * doc/include/gcc-common.texi (version-GCC): Increase to 3.3.
index 6b0fd82d85240757eddb44a006c9eb4acb26a7df..070a08cada730b0495a6c0895b984d80de69fc7e 100644 (file)
@@ -1,10 +1,11 @@
-@c Copyright (C) 1996, 1997, 1999, 2000, 2001 Free Software Foundation, Inc.
+@c Copyright (C) 1996, 1997, 1999, 2000, 2001,
+@c 2002 Free Software Foundation, Inc.
 @c This is part of the GCC manual.
 @c For copying conditions, see the file gcc.texi.
 
 @ignore
 @c man begin COPYRIGHT
-Copyright @copyright{} 1996, 1997, 1999, 2000, 2001 Free Software Foundation, Inc.
+Copyright @copyright{} 1996, 1997, 1999, 2000, 2001, 2002 Free Software Foundation, Inc.
 
 Permission is granted to copy, distribute and/or modify this document
 under the terms of the GNU Free Documentation License, Version 1.1 or
@@ -47,12 +48,13 @@ test code coverage in your programs.
 @c man begin DESCRIPTION
 
 @command{gcov} is a test coverage program.  Use it in concert with GCC
-to analyze your programs to help create more efficient, faster
-running code.  You can use @command{gcov} as a profiling tool to help
-discover where your optimization efforts will best affect your code.  You
-can also use @command{gcov} along with the other profiling tool,
-@command{gprof}, to assess which parts of your code use the greatest amount
-of computing time.
+to analyze your programs to help create more efficient, faster running
+code and to discover untested parts of your program.  You can use
+@command{gcov} as a profiling tool to help discover where your
+optimization efforts will best affect your code.  You can also use
+@command{gcov} along with the other profiling tool, @command{gprof}, to
+assess which parts of your code use the greatest amount of computing
+time.
 
 Profiling tools help you analyze your code's performance.  Using a
 profiler such as @command{gcov} or @command{gprof}, you can find out some
@@ -117,10 +119,13 @@ gcov @r{[}@var{options}@r{]} @var{sourcefile}
 @ignore
 @c man begin SYNOPSIS
 gcov [@option{-v}|@option{--version}] [@option{-h}|@option{--help}]
-     [@option{-b}|@option{--branch-probabilities}] [@option{-c}|@option{--branch-counts}]
-     [@option{-n}|@option{--no-output}] [@option{-l}|@option{--long-file-names}]
+     [@option{-b}|@option{--branch-probabilities}]
+     [@option{-c}|@option{--branch-counts}]
+     [@option{-n}|@option{--no-output}]
+     [@option{-l}|@option{--long-file-names}]
+     [@option{-p}|@option{--preserve-paths}]
      [@option{-f}|@option{--function-summaries}]
-     [@option{-o}|@option{--object-directory} @var{directory}] @var{sourcefile}
+     [@option{-o}|@option{--object-directory} @var{directory|file}] @var{sourcefile}
 @c man end
 @c man begin SEEALSO
 gpl(7), gfdl(7), fsf-funding(7), gcc(1) and the Info entry for @file{gcc}.
@@ -159,31 +164,70 @@ Do not create the @command{gcov} output file.
 Create long file names for included source files.  For example, if the
 header file @file{x.h} contains code, and was included in the file
 @file{a.c}, then running @command{gcov} on the file @file{a.c} will produce
-an output file called @file{a.c.x.h.gcov} instead of @file{x.h.gcov}.
+an output file called @file{a.c##x.h.gcov} instead of @file{x.h.gcov}.
 This can be useful if @file{x.h} is included in multiple source files.
 
+@item -p
+@itemx --preserve-paths
+Preserve complete path information in the names of generated
+@file{.gcov} files. Without this option, just the filename component is
+used. With this option, all directories are used, with '/' characters
+translated to '#' characters, '.' directory components removed and '..'
+components renamed to '^'. This is useful if sourcefiles are in several
+different directories. It also affects the @samp{-l} option.
+
 @item -f
 @itemx --function-summaries
 Output summaries for each function in addition to the file level summary.
 
-@item -o @var{directory}
+@item -o @var{directory|file}
 @itemx --object-directory @var{directory}
-The directory where the object files live.  Gcov will search for @file{.bb},
-@file{.bbg}, and @file{.da} files in this directory.
+@itemx --object-file @var{file}
+Specify either the directory containing the gcov data files, or the
+object path name. The @file{.bb}, @file{.bbg}, and
+@file{.da} data files are searched for using this option. If a directory
+is specified, the data files are in that directory and named after the
+source file name, without its extension. If a file is specified here,
+the data files are named after that file, without its extension. If this
+option is not supplied, it defaults to the current directory.
+
 @end table
 
-@need 3000
+Gcov should be run with the current directory the same as that when you
+invoked the compiler. Otherwise it will not be able to locate the source
+files. Gcov produces files called @file{@var{mangledname}.gcov} in the
+current directory. These contain the coverage information of the source
+file they correspond to. One @file{.gcov} file is produced for each
+source file containing code, which was compiled to produce the data
+files. The @file{.gcov} files contain the ':' separated fields along
+with program source code. The format is
+
+@smallexample
+@var{execution_count}:@var{line_number}:@var{source line text}
+@end smallexample
+
+Additional block information may succeed each line, when requested by
+command line option. The @var{execution_count} is @samp{-} for lines
+containing no code and @samp{#####} for lines which were never
+executed. Some lines of information at the start have @var{line_number}
+of zero.
+
+When printing percentages, 0% and 100% are only printed when the values
+are @emph{exactly} 0% and 100% respectively. Other values which would
+conventionally be rounded to 0% or 100% are instead printed as the
+nearest non-boundary value.
+
 When using @command{gcov}, you must first compile your program with two
 special GCC options: @samp{-fprofile-arcs -ftest-coverage}.
 This tells the compiler to generate additional information needed by
 gcov (basically a flow graph of the program) and also includes
 additional code in the object files for generating the extra profiling
 information needed by gcov.  These additional files are placed in the
-directory where the source code is located.
+directory where the object file is located.
 
 Running the program will cause profile output to be generated.  For each
 source file compiled with @option{-fprofile-arcs}, an accompanying @file{.da}
-file will be placed in the source directory.
+file will be placed in the object file directory.
 
 Running @command{gcov} with your program's source file names as arguments
 will now produce a listing of the code along with frequency of execution
@@ -194,7 +238,7 @@ is what you see when you use the basic @command{gcov} facility:
 $ gcc -fprofile-arcs -ftest-coverage tmp.c
 $ a.out
 $ gcov tmp.c
- 87.50% of 8 source lines executed in file tmp.c
+90.00% of 10 source lines executed in file tmp.c
 Creating tmp.c.gcov.
 @end smallexample
 
@@ -202,20 +246,25 @@ The file @file{tmp.c.gcov} contains output from @command{gcov}.
 Here is a sample:
 
 @smallexample
-                main()
-                @{
-           1      int i, total;
-
-           1      total = 0;
-
-          11      for (i = 0; i < 10; i++)
-          10        total += i;
-
-           1      if (total != 45)
-      ######        printf ("Failure\n");
-                  else
-           1        printf ("Success\n");
-           1    @}
+        -:    0:Source:tmp.c
+        -:    0:Object:tmp.bb
+        -:    1:#include <stdio.h>
+        -:    2:
+        -:    3:int main (void)
+        1:    4:@{
+        1:    5:  int i, total;
+        -:    6:  
+        1:    7:  total = 0;
+        -:    8:  
+       11:    9:  for (i = 0; i < 10; i++)
+       10:   10:    total += i;
+        -:   11:  
+        1:   12:  if (total != 45)
+    #####:   13:    printf ("Failure\n");
+        -:   14:  else
+        1:   15:    printf ("Success\n");
+        1:   16:  return 0;
+        1:   17:@}
 @end smallexample
 
 @need 450
@@ -223,37 +272,42 @@ When you use the @option{-b} option, your output looks like this:
 
 @smallexample
 $ gcov -b tmp.c
- 87.50% of 8 source lines executed in file tmp.c
- 80.00% of 5 branches executed in file tmp.c
- 80.00% of 5 branches taken at least once in file tmp.c
- 50.00% of 2 calls executed in file tmp.c
+90.00% of 10 source lines executed in file tmp.c
+80.00% of 5 branches executed in file tmp.c
+80.00% of 5 branches taken at least once in file tmp.c
+50.00% of 2 calls executed in file tmp.c
 Creating tmp.c.gcov.
 @end smallexample
 
 Here is a sample of a resulting @file{tmp.c.gcov} file:
 
 @smallexample
-                main()
-                @{
-           1      int i, total;
-
-           1      total = 0;
-
-          11      for (i = 0; i < 10; i++)
-branch 0 taken = 91%
-branch 1 taken = 100%
-branch 2 taken = 100%
-          10        total += i;
-
-           1      if (total != 45)
-branch 0 taken = 100%
-      ######        printf ("Failure\n");
-call 0 never executed
-branch 1 never executed
-                  else
-           1        printf ("Success\n");
-call 0 returns = 100%
-           1    @}
+        -:    0:Source:tmp.c
+        -:    0:Object:tmp.bb
+        -:    1:#include <stdio.h>
+        -:    2:
+        -:    3:int main (void)
+        1:    4:@{
+        1:    5:  int i, total;
+        -:    6:  
+        1:    7:  total = 0;
+        -:    8:  
+       11:    9:  for (i = 0; i < 10; i++)
+branch  0: taken 90%
+branch  1: taken 100%
+branch  2: taken 100%
+       10:   10:    total += i;
+        -:   11:  
+        1:   12:  if (total != 45)
+branch  0: taken 100%
+    #####:   13:    printf ("Failure\n");
+call    0: never executed
+branch  1: never executed
+        -:   14:  else
+        1:   15:    printf ("Success\n");
+call    0: returns 100%
+        1:   16:  return 0;
+        1:   17:@}
 @end smallexample
 
 For each basic block, a line is printed after the last line of the basic
@@ -286,11 +340,11 @@ provide more accurate long-term information over a large number of
 program runs.
 
 The data in the @file{.da} files is saved immediately before the program
-exits.  For each source file compiled with @option{-fprofile-arcs}, the profiling
-code first attempts to read in an existing @file{.da} file; if the file
-doesn't match the executable (differing number of basic block counts) it
-will ignore the contents of the file.  It then adds in the new execution
-counts and finally writes the data to the file.
+exits.  For each source file compiled with @option{-fprofile-arcs}, the
+profiling code first attempts to read in an existing @file{.da} file; if
+the file doesn't match the executable (differing number of basic block
+counts) it will ignore the contents of the file.  It then adds in the
+new execution counts and finally writes the data to the file.
 
 @node Gcov and Optimization
 @section Using @command{gcov} with GCC Optimization
@@ -319,10 +373,10 @@ the @command{gcov} output looks like this if you compiled the program with
 optimization:
 
 @smallexample
-      100  if (a != b)
-      100    c = 1;
-      100  else
-      100    c = 0;
+      100:   12:if (a != b)
+      100:   13:  c = 1;
+      100:   14:else
+      100:   15:  c = 0;
 @end smallexample
 
 The output shows that this block of code, combined by optimization,
index 01e1a1dd77e93763713f256094a3eb3d09b5fadf..9925e95999bbbb97ebd5b5cf7568cf1cc930dd8a 100644 (file)
@@ -160,6 +160,10 @@ struct bb_info_list {
 
 static struct bb_info_list *bb_graph_list = 0;
 
+/* Modification time of data files. */
+
+static time_t bb_file_time;
+
 /* Name and file pointer of the input file for the basic block graph.  */
 
 static char *bbg_file_name;
@@ -212,11 +216,15 @@ static int output_long_names = 0;
 
 static int output_function_summary = 0;
 
-/* Object directory file prefix.  This is the directory where .bb and .bbg
-   files are looked for, if non-zero.  */
+/* Object directory file prefix.  This is the directory/file
+   where .bb and .bbg files are looked for, if non-zero.  */
 
 static char *object_directory = 0;
 
+/* Preserve all pathname components. Needed when object files and
+   source files are in subdirectories.  */
+static int preserve_paths = 0;
+
 /* Output the number of times a branch was taken as opposed to the percentage
    of times it was taken.  Turned on by the -c option */
 
@@ -238,6 +246,7 @@ static void solve_program_flow_graph PARAMS ((struct bb_info_list *));
 static void calculate_branch_probs PARAMS ((struct bb_info_list *, int,
                                            struct arcdata **, int));
 static void function_summary PARAMS ((void));
+static const char *format_hwint PARAMS ((HOST_WIDEST_INT, HOST_WIDEST_INT, int));
 
 extern int main PARAMS ((int, char **));
 
@@ -304,7 +313,8 @@ print_usage (error_p)
   fnotice (file, "  -l, --long-file-names           Use long output file names for included\n\
                                     source files\n");
   fnotice (file, "  -f, --function-summaries        Output summaries for each function\n");
-  fnotice (file, "  -o, --object-directory OBJDIR   Search for object files in OBJDIR\n");
+  fnotice (file, "  -o, --object-directory DIR|FILE Search for object files in DIR or called FILE\n");
+  fnotice (file, "  -p, --preserve-paths            Preserve all pathname components\n");
   fnotice (file, "\nFor bug reporting instructions, please see:\n%s.\n",
           GCCBUGURL);
   exit (status);
@@ -332,7 +342,9 @@ static const struct option options[] =
   { "no-output",            no_argument,       NULL, 'n' },
   { "long-file-names",      no_argument,       NULL, 'l' },
   { "function-summaries",   no_argument,       NULL, 'f' },
-  { "object-directory",     required_argument, NULL, 'o' }
+  { "preserve-paths",       no_argument,       NULL, 'p' },
+  { "object-directory",     required_argument, NULL, 'o' },
+  { "object-file",          required_argument, NULL, 'o' },
 };
 
 /* Parse the command line.  */
@@ -344,7 +356,7 @@ process_args (argc, argv)
 {
   int opt;
 
-  while ((opt = getopt_long (argc, argv, "hvbclnfo:", options, NULL)) != -1)
+  while ((opt = getopt_long (argc, argv, "hvbclnfo:p", options, NULL)) != -1)
     {
       switch (opt)
        {
@@ -372,6 +384,9 @@ process_args (argc, argv)
        case 'o':
          object_directory = optarg;
          break;
+       case 'p':
+         preserve_paths = 1;
+         break;
        default:
          print_usage (true);
          /* print_usage will exit.  */
@@ -385,77 +400,66 @@ process_args (argc, argv)
 }
 
 
-/* Find and open the .bb, .da, and .bbg files.  */
+/* Find and open the .bb, .da, and .bbg files. If OBJECT_DIRECTORY is
+   not specified, these are looked for in the current directory, and
+   named from the basename of the input_file_name sans extension. If
+   OBJECT_DIRECTORY is specified and is a directory, the files are in
+   that directory, but named from the basename of the input_file_name,
+   sans extension. Otherwise OBJECT_DIRECTORY is taken to be the name
+   of the object *file*, and the data files are named from that.  */
 
 static void
 open_files ()
 {
-  int count, objdir_count;
   char *cptr;
-
-  /* Determine the names of the .bb, .bbg, and .da files.  Strip off the
-     extension, if any, and append the new extensions.  */
-  count = strlen (input_file_name);
-  if (object_directory)
-    objdir_count = strlen (object_directory);
-  else
-    objdir_count = 0;
-
-  da_file_name = xmalloc (count + objdir_count + 4);
-  bb_file_name = xmalloc (count + objdir_count + 4);
-  bbg_file_name = xmalloc (count + objdir_count + 5);
-
-  if (object_directory)
+  char *name;
+  int length = strlen (input_file_name);
+  int base;
+  
+  if (object_directory && object_directory[0])
     {
-      strcpy (da_file_name, object_directory);
-      strcpy (bb_file_name, object_directory);
-      strcpy (bbg_file_name, object_directory);
-
-      if (object_directory[objdir_count - 1] != '/')
-       {
-         strcat (da_file_name, "/");
-         strcat (bb_file_name, "/");
-         strcat (bbg_file_name, "/");
-       }
-
-      cptr = strrchr (input_file_name, '/');
-      if (cptr)
-       {
-         strcat (da_file_name, cptr + 1);
-         strcat (bb_file_name, cptr + 1);
-         strcat (bbg_file_name, cptr + 1);
-       }
-      else
-       {
-         strcat (da_file_name, input_file_name);
-         strcat (bb_file_name, input_file_name);
-         strcat (bbg_file_name, input_file_name);
-       }
+      struct stat status;
+
+      length += strlen (object_directory) + 2;
+      name = xmalloc (length);
+      name[0] = 0;
+      
+      base = !stat (object_directory, &status) && S_ISDIR (status.st_mode);
+      strcat (name, object_directory);
+      if (base && name[strlen (name) - 1] != '/')
+       strcat (name, "/");
     }
   else
     {
-      strcpy (da_file_name, input_file_name);
-      strcpy (bb_file_name, input_file_name);
-      strcpy (bbg_file_name, input_file_name);
+      name = xmalloc (length + 1);
+      name[0] = 0;
+      base = 1;
     }
+  
+  if (base)
+    {
+      /* Append source file name */
+      cptr = strrchr (input_file_name, '/');
+      cptr = cptr ? cptr + 1 : input_file_name;
 
-  cptr = strrchr (bb_file_name, '.');
-  if (cptr)
-    strcpy (cptr, ".bb");
-  else
-    strcat (bb_file_name, ".bb");
-
-  cptr = strrchr (da_file_name, '.');
-  if (cptr)
-    strcpy (cptr, ".da");
-  else
-    strcat (da_file_name, ".da");
-
-  cptr = strrchr (bbg_file_name, '.');
+      strcat (name, cptr);
+    }
+  /* Remove the extension. */
+  cptr = strrchr (name, '.');
   if (cptr)
-    strcpy (cptr, ".bbg");
-  else
-    strcat (bbg_file_name, ".bbg");
+    *cptr = 0;
+  
+  length = strlen (name);
+  da_file_name = xmalloc (length + 4);
+  bb_file_name = xmalloc (length + 4);
+  bbg_file_name = xmalloc (length + 5);
+
+  strcpy (da_file_name, name);
+  strcpy (bb_file_name, name);
+  strcpy (bbg_file_name, name);
+  strcpy (da_file_name + length, ".da");
+  strcpy (bb_file_name + length, ".bb");
+  strcpy (bbg_file_name + length, ".bbg");
 
   bb_file = fopen (bb_file_name, "rb");
   if (bb_file == NULL)
@@ -464,6 +468,21 @@ open_files ()
       exit (FATAL_EXIT_CODE);
     }
 
+  bbg_file = fopen (bbg_file_name, "rb");
+  if (bbg_file == NULL)
+    {
+      fnotice (stderr, "Could not open program flow graph file %s.\n",
+              bbg_file_name);
+      exit (FATAL_EXIT_CODE);
+    }
+  
+  {
+    struct stat status;
+
+    if (!fstat (fileno (bb_file), &status))
+      bb_file_time = status.st_mtime;
+  }
+  
   /* If none of the functions in the file were executed, then there won't
      be a .da file.  Just assume that all counts are zero in this case.  */
   da_file = fopen (da_file_name, "rb");
@@ -473,14 +492,6 @@ open_files ()
       fnotice (stderr, "Assuming that all execution counts are zero.\n");
     }
 
-  bbg_file = fopen (bbg_file_name, "rb");
-  if (bbg_file == NULL)
-    {
-      fnotice (stderr, "Could not open program flow graph file %s.\n",
-              bbg_file_name);
-      exit (FATAL_EXIT_CODE);
-    }
-
   /* Check for empty .bbg file.  This indicates that there is no executable
      code in this source file.  */
   /* Set the EOF condition if at the end of file.  */
@@ -554,7 +565,6 @@ read_profile (function_name, cfg_checksum, instr_arcs)
   int function_name_buffer_len;
 
   profile = xmalloc (sizeof (gcov_type) * instr_arcs);
-  rewind (da_file);
   function_name_buffer_len = strlen (function_name) + 1;
   function_name_buffer = xmalloc (function_name_buffer_len + 1);
 
@@ -564,6 +574,7 @@ read_profile (function_name, cfg_checksum, instr_arcs)
   if (!da_file)
     return profile;
 
+  rewind (da_file);
   while (1)
     {
       long magic, extra_bytes;
@@ -1100,15 +1111,64 @@ calculate_branch_probs (current_graph, block_num, branch_probs, last_line_num)
     }
 }
 
+/* Format a HOST_WIDE_INT as either a percent ratio, or absolute
+   count.  If dp >= 0, format TOP/BOTTOM * 100 to DP decimal places.
+   If DP is zero, no decimal point is printed. Only print 100% when
+   TOP==BOTTOM and only print 0% when TOP=0.  If dp < 0, then simply
+   format TOP.  Return pointer to a static string.  */
+
+static char const *
+format_hwint (top, bottom, dp)
+     HOST_WIDEST_INT top, bottom;
+     int dp;
+{
+  static char buffer[20];
+  
+  if (dp >= 0)
+    {
+      float ratio = bottom ? (float)top / bottom : 0;
+      int ix;
+      unsigned limit = 100;
+      unsigned percent;
+  
+      for (ix = dp; ix--; )
+       limit *= 10;
+      
+      percent = (unsigned) (ratio * limit);
+      if (!percent && top)
+       percent = 1;
+      else if (percent == limit && top != bottom)
+       percent = limit - 1;
+      ix = sprintf (buffer, "%.*u%%", dp + 1, percent);
+      if (dp)
+       {
+         dp++;
+         do
+           {
+             buffer[ix+1] = buffer[ix];
+             ix--;
+           }
+         while (dp--);
+         buffer[ix + 1] = '.';
+       }
+    }
+  else
+    sprintf (buffer, HOST_WIDEST_INT_PRINT_DEC, top);
+  
+  return buffer;
+}
+
+
 /* Output summary info for a function.  */
 
 static void
 function_summary ()
 {
   if (function_source_lines)
-    fnotice (stdout, "%6.2f%% of %d source lines executed in function %s\n",
-            (((double) function_source_lines_executed / function_source_lines)
-             * 100), function_source_lines, function_name);
+    fnotice (stdout, "%s of %d source lines executed in function %s\n",
+            format_hwint (function_source_lines_executed,
+                          function_source_lines, 2),
+            function_source_lines, function_name);
   else
     fnotice (stdout, "No executable source lines in function %s\n",
             function_name);
@@ -1117,20 +1177,23 @@ function_summary ()
     {
       if (function_branches)
        {
-         fnotice (stdout, "%6.2f%% of %d branches executed in function %s\n",
-                  (((double) function_branches_executed / function_branches)
-                   * 100), function_branches, function_name);
+         fnotice (stdout, "%s of %d branches executed in function %s\n",
+                  format_hwint (function_branches_executed,
+                                function_branches, 2),
+                  function_branches, function_name);
          fnotice (stdout,
-               "%6.2f%% of %d branches taken at least once in function %s\n",
-                  (((double) function_branches_taken / function_branches)
-                   * 100), function_branches, function_name);
+               "%s of %d branches taken at least once in function %s\n",
+                  format_hwint (function_branches_taken,
+                                function_branches, 2),
+                  function_branches, function_name);
        }
       else
        fnotice (stdout, "No branches in function %s\n", function_name);
       if (function_calls)
-       fnotice (stdout, "%6.2f%% of %d calls executed in function %s\n",
-                (((double) function_calls_executed / function_calls)
-                 * 100), function_calls, function_name);
+       fnotice (stdout, "%s of %d calls executed in function %s\n",
+                format_hwint (function_calls_executed,
+                              function_calls, 2),
+                function_calls, function_name);
       else
        fnotice (stdout, "No calls in function %s\n", function_name);
     }
@@ -1185,21 +1248,7 @@ output_data ()
 
   for (s_ptr = sources; s_ptr; s_ptr = s_ptr->next)
     {
-      /* If this is a relative file name, and an object directory has been
-        specified, then make it relative to the object directory name.  */
-      if (! IS_ABSOLUTE_PATHNAME (s_ptr->name)
-         && object_directory != 0
-         && *object_directory != '\0')
-       {
-         int objdir_count = strlen (object_directory);
-         source_file_name = xmalloc (objdir_count + strlen (s_ptr->name) + 2);
-         strcpy (source_file_name, object_directory);
-         if (object_directory[objdir_count - 1] != '/')
-           source_file_name[objdir_count++] = '/';
-         strcpy (source_file_name + objdir_count, s_ptr->name);
-       }
-      else
-       source_file_name = s_ptr->name;
+      source_file_name = s_ptr->name;
 
       line_counts = (gcov_type *) xcalloc (sizeof (gcov_type), s_ptr->maxlineno);
       line_exists = xcalloc (1, s_ptr->maxlineno);
@@ -1376,9 +1425,10 @@ output_data ()
 
       if (total_source_lines)
        fnotice (stdout,
-                "%6.2f%% of %d source lines executed in file %s\n",
-                (((double) total_source_lines_executed / total_source_lines)
-                 * 100), total_source_lines, source_file_name);
+                "%s of %d source lines executed in file %s\n",
+                format_hwint (total_source_lines_executed,
+                              total_source_lines, 2),
+                total_source_lines, source_file_name);
       else
        fnotice (stdout, "No executable source lines in file %s\n",
                 source_file_name);
@@ -1387,20 +1437,22 @@ output_data ()
        {
          if (total_branches)
            {
-             fnotice (stdout, "%6.2f%% of %d branches executed in file %s\n",
-                      (((double) total_branches_executed / total_branches)
-                       * 100), total_branches, source_file_name);
+             fnotice (stdout, "%s of %d branches executed in file %s\n",
+                      format_hwint (total_branches_executed,
+                                    total_branches, 2),
+                      total_branches, source_file_name);
              fnotice (stdout,
-                   "%6.2f%% of %d branches taken at least once in file %s\n",
-                      (((double) total_branches_taken / total_branches)
-                       * 100), total_branches, source_file_name);
+                      "%s of %d branches taken at least once in file %s\n",
+                      format_hwint (total_branches_taken,
+                                    total_branches, 2),
+                      total_branches, source_file_name);
            }
          else
            fnotice (stdout, "No branches in file %s\n", source_file_name);
          if (total_calls)
-           fnotice (stdout, "%6.2f%% of %d calls executed in file %s\n",
-                    (((double) total_calls_executed / total_calls)
-                     * 100), total_calls, source_file_name);
+           fnotice (stdout, "%s of %d calls executed in file %s\n",
+                    format_hwint (total_calls_executed, total_calls, 2),
+                    total_calls, source_file_name);
          else
            fnotice (stdout, "No calls in file %s\n", source_file_name);
        }
@@ -1410,51 +1462,70 @@ output_data ()
          /* Now the statistics are ready.  Read in the source file one line
             at a time, and output that line to the gcov file preceded by
             its execution count if non zero.  */
-
-         source_file = fopen (source_file_name, "r");
-         if (source_file == NULL)
+         char const *retval;
+
+         /* Generate an output file name. LONG_OUTPUT_NAMES and
+            PRESERVE_PATHS affect name generation. With
+            preserve_paths we create a filename from all path
+            components of the source file, replacing '/' with '#',
+            without it we simply take the basename component. With
+            long_output_names we prepend the processed name of the
+            input file to each output name (except when the current
+            source file is the input file, so you don't get a double
+            concatenation). The two components are separated by
+            '##'. Also '.' filename components are removed and '..'
+            components are renamed to '^'. */
+         gcov_file_name = xmalloc (strlen (source_file_name)
+                                   + strlen (input_file_name) + 10);
+         gcov_file_name[0] = 0;
+         if (output_long_names && strcmp (source_file_name, input_file_name))
            {
-             fnotice (stderr, "Could not open source file %s.\n",
-                      source_file_name);
-             free (line_counts);
-             free (line_exists);
-             continue;
+             /* Generate the input filename part.  */
+             cptr = preserve_paths ? NULL : strrchr (input_file_name, '/');
+             cptr = cptr ? cptr + 1 : input_file_name;
+             strcat (gcov_file_name, cptr);
+             strcat (gcov_file_name, "##");
            }
+         /* Generate the source filename part. */
+         cptr = preserve_paths ? NULL : strrchr (source_file_name, '/');
+         cptr = cptr ? cptr + 1 : source_file_name;
+         strcat (gcov_file_name, cptr);
 
-         count = strlen (source_file_name);
-         cptr = strrchr (s_ptr->name, '/');
-         if (cptr)
-           cptr = cptr + 1;
-         else
-           cptr = s_ptr->name;
-         if (output_long_names && strcmp (cptr, input_file_name))
-           {
-             gcov_file_name = xmalloc (count + 7 + strlen (input_file_name));
-
-             cptr = strrchr (input_file_name, '/');
-             if (cptr)
-               strcpy (gcov_file_name, cptr + 1);
-             else
-               strcpy (gcov_file_name, input_file_name);
-
-             strcat (gcov_file_name, ".");
-
-             cptr = strrchr (source_file_name, '/');
-             if (cptr)
-               strcat (gcov_file_name, cptr + 1);
-             else
-               strcat (gcov_file_name, source_file_name);
-           }
-         else
+         if (preserve_paths)
            {
-             gcov_file_name = xmalloc (count + 6);
-             cptr = strrchr (source_file_name, '/');
-             if (cptr)
-               strcpy (gcov_file_name, cptr + 1);
-             else
-               strcpy (gcov_file_name, source_file_name);
+             /* Convert '/' to '#', remove '/./', convert '/../' to
+                '/^/' */
+             char *prev;
+             
+             for (cptr = gcov_file_name;
+                  (cptr = strchr ((prev = cptr), '/'));)
+               {
+                 unsigned shift = 0;
+                 
+                 if (prev + 1 == cptr && prev[0] == '.')
+                   {
+                     /* Remove '.' */
+                     shift = 2;
+                   }
+                 else if (prev + 2 == cptr
+                          && prev[0] == '.' && prev[1] == '.')
+                   {
+                     /* Convert '..' */
+                     shift = 1;
+                     prev[1] = '^';
+                   }
+                 else
+                   *cptr++ = '#';
+                 if (shift)
+                   {
+                     cptr = prev;
+                     do
+                       prev[0] = prev[shift];
+                     while (*prev++);
+                   }
+               }
            }
-
+         
          /* Don't strip off the ending for compatibility with tcov, since
             this results in confusion if there is more than one file with
             the same basename, e.g. tmp.c and tmp.h.  */
@@ -1466,7 +1537,6 @@ output_data ()
            {
              fnotice (stderr, "Could not open output file %s.\n",
                       gcov_file_name);
-             fclose (source_file);
              free (line_counts);
              free (line_exists);
              continue;
@@ -1474,44 +1544,61 @@ output_data ()
 
          fnotice (stdout, "Creating %s.\n", gcov_file_name);
 
-         for (count = 1; count < s_ptr->maxlineno; count++)
+         fprintf (gcov_file, "%9s:%5d:Source:%s\n", "-", 0, source_file_name);
+         fprintf (gcov_file, "%9s:%5d:Object:%s\n", "-", 0, bb_file_name);
+         
+         source_file = fopen (source_file_name, "r");
+         if (source_file == NULL)
+           fnotice (stderr, "Could not open source file %s.\n",
+                    source_file_name);
+         else
            {
-             char *retval;
-             int len;
-
-             retval = fgets (string, STRING_SIZE, source_file);
+             struct stat status;
 
-             /* For lines which don't exist in the .bb file, print nothing
-                before the source line.  For lines which exist but were never
-                executed, print ###### before the source line.  Otherwise,
-                print the execution count before the source line.  */
+             if (!fstat (fileno (source_file), &status)
+                 && status.st_mtime > bb_file_time)
+               {
+                 fnotice (stderr, "Warning: source file %s is newer than %s\n",
+                          source_file_name, bb_file_name);
+                 fprintf (gcov_file, "%9s:%5d:Source is newer than compiler output\n", "-", 0);
+               }
+           }
+         
+         for (retval = source_file ? "" : NULL, count = 1;
+              count < s_ptr->maxlineno; count++)
+           {
+             /* For lines which don't exist in the .bb file, print
+                '-' before the source line.  For lines which exist
+                but were never executed, print '#####' before the source
+                line.  Otherwise, print the execution count before
+                the source line.  */
+             
              /* There are 16 spaces of indentation added before the source
                 line so that tabs won't be messed up.  */
-             if (line_exists[count])
+             fprintf (gcov_file, "%9s:%5ld:",
+                      !line_exists[count] ? "-"
+                      : !line_counts[count] ? "#####"
+                      : format_hwint (line_counts[count], 0, -1), count);
+             
+             if (retval)
                {
-                 if (line_counts[count])
+                 do
                    {
-                     char c[20];
-                     sprintf (c, HOST_WIDEST_INT_PRINT_DEC, (HOST_WIDEST_INT)line_counts[count]);
-                     fprintf (gcov_file, "%12s    %s", c,
-                              string);
+                     retval = fgets (string, STRING_SIZE, source_file);
+                     if (!retval)
+                       {
+                         fnotice (stderr,
+                                  "Unexpected EOF while reading source file %s.\n",
+                                  source_file_name);
+                         break;
+                       }
+                     fputs (retval, gcov_file);
                    }
-                 else
-                   fprintf (gcov_file, "      ######    %s", string);
-               }
-             else
-               fprintf (gcov_file, "\t\t%s", string);
-
-             /* In case the source file line is larger than our buffer, keep
-                reading and outputting lines until we get a newline.  */
-             len = strlen (string);
-             while ((len == 0 || string[strlen (string) - 1] != '\n')
-                    && retval != NULL)
-               {
-                 retval = fgets (string, STRING_SIZE, source_file);
-                 fputs (string, gcov_file);
+                 while (!retval[0] || retval[strlen (retval) - 1] != '\n');
                }
-
+             if (!retval)
+               fputs ("??\n", gcov_file);
+             
              if (output_branch_probs)
                {
                  for (i = 0, a_ptr = branch_probs[count]; a_ptr;
@@ -1520,95 +1607,53 @@ output_data ()
                      if (a_ptr->call_insn)
                        {
                          if (a_ptr->total == 0)
-                           fnotice (gcov_file, "call %d never executed\n", i);
-                           else
-                             {
-                               if (output_branch_counts)
-                                 {
-                                   char c[20];
-                                   sprintf (c, HOST_WIDEST_INT_PRINT_DEC,
-                                            a_ptr->total - a_ptr->hits);
-                                   fnotice (gcov_file,
-                                            "call %d returns = %s\n", i, c);
-                                 }
-                               else
-                                 {
-                                   char c[20];
-                                   sprintf (c, HOST_WIDEST_INT_PRINT_DEC,
-                                            100 - ((a_ptr->hits * 100)
-                                                   + (a_ptr->total >> 1))
-                                            / a_ptr->total);
-                                   fnotice (gcov_file,
-                                            "call %d returns = %s%%\n", i, c);
-                                 }
-                             }
+                           fnotice (gcov_file, "call   %2d: never executed\n", i);
+                         else
+                           fnotice
+                             (gcov_file, "call   %2d: returns %s\n", i,
+                              format_hwint (a_ptr->total - a_ptr->hits,
+                                            a_ptr->total,
+                                            -output_branch_counts));
                        }
                      else
                        {
                          if (a_ptr->total == 0)
-                           fnotice (gcov_file, "branch %d never executed\n",
+                           fnotice (gcov_file, "branch %2d: never executed\n",
                                     i);
                          else
-                           {
-                             if (output_branch_counts)
-                               {
-                                 char c[20];
-                                 sprintf (c, HOST_WIDEST_INT_PRINT_DEC,
-                                          a_ptr->hits);
-                                 fnotice (gcov_file,
-                                          "branch %d taken = %s\n", i, c);
-                               }
-                             else
-                               {
-                                 char c[20];
-                                 sprintf (c, HOST_WIDEST_INT_PRINT_DEC,
-                                          ((a_ptr->hits * 100)
-                                           + (a_ptr->total >> 1))
-                                          / a_ptr->total);
-                                 fnotice (gcov_file,
-                                          "branch %d taken = %s%%\n", i, c);
-                               }
-                           }
+                           fnotice
+                             (gcov_file, "branch %2d: taken %s\n", i,
+                              format_hwint (a_ptr->hits, a_ptr->total,
+                                            -output_branch_counts));
                        }
                   }
              }
-
-             /* Gracefully handle errors while reading the source file.  */
-             if (retval == NULL)
-               {
-                 fnotice (stderr,
-                          "Unexpected EOF while reading source file %s.\n",
-                          source_file_name);
-                 break;
-               }
            }
 
          /* Handle all remaining source lines.  There may be lines
             after the last line of code.  */
+         if (retval)
+           {
+             for (; (retval = fgets (string, STRING_SIZE, source_file));
+                  count++)
+               {
+                 fprintf (gcov_file, "%9s:%5ld:%s", "-", count, retval);
 
-         {
-           char *retval = fgets (string, STRING_SIZE, source_file);
-           while (retval != NULL)
-             {
-               int len;
-
-               fprintf (gcov_file, "\t\t%s", string);
-
-               /* In case the source file line is larger than our buffer, keep
-                  reading and outputting lines until we get a newline.  */
-               len = strlen (string);
-               while ((len == 0 || string[strlen (string) - 1] != '\n')
-                      && retval != NULL)
-                 {
-                   retval = fgets (string, STRING_SIZE, source_file);
-                   fputs (string, gcov_file);
-                 }
-
-               retval = fgets (string, STRING_SIZE, source_file);
-             }
-         }
+                 while (!retval[0] || retval[strlen (retval) - 1] != '\n')
+                   {
+                     retval = fgets (string, STRING_SIZE, source_file);
+                     if (!retval)
+                       break;
+                     fputs (retval, gcov_file);
+                   }
+               }
+           }
 
-         fclose (source_file);
+         if (source_file)
+           fclose (source_file);
+         if (ferror (gcov_file))
+           fnotice (stderr, "Error writing output file %s.\n",
+                    gcov_file_name);
          fclose (gcov_file);
        }