On 7/13/21 10:15 PM, Sebastian Huber wrote:

Hello.

Thanks for working on that, there's my review:

Add __gcov_info_to_gcda() to libgcov to get the gcda data for a gcda info in a
freestanding environment.  It is intended to be used with the
-fprofile-info-section option.  A crude test program which doesn't use a linker
script is (use "gcc -coverage -fprofile-info-section -lgcc test.c" to compile
it):

   #include <gcov.h>
   #include <stdio.h>
   #include <stdlib.h>

   extern const struct gcov_info *my_info;

   static void
   filename (const char *f, void *arg)
   {
     printf("filename: %s\n", f);
   }

   static void
   dump (const void *d, unsigned n, void *arg)
   {
     const unsigned char *c = d;

     for (unsigned i = 0; i < n; ++i)
       printf ("%02x", c[i]);
   }

   static void *
   allocate (unsigned length, void *arg)
   {
     return malloc (length);
   }

   int main()
   {
     __asm__ volatile (".set my_info, .LPBX2");
     __gcov_info_to_gcda (my_info, filename, dump, allocate, NULL);
     return 0;
   }

With this patch, <stdint.h> is included in libgcov-driver.c even if
inhibit_libc is defined.  This header file should be also available for
freestanding environments.  If this is not the case, then we have to define
intptr_t somehow.

The patch removes one use of memset() which makes the <string.h> include
superfluous.

gcc/

        * gcc/gcov-io.h (gcov_write): Declare.
        * gcc/gcov-io.c (gcov_write): New.
        * doc/invoke.texi (fprofile-info-section): Mention
        __gcov_info_to_gdca().

libgcc/

        Makefile.in (LIBGCOV_DRIVER): Add _gcov_info_to_gcda.
        gcov.h (gcov_info): Declare.
        (__gcov_info_to_gdca): Likewise.
        libgcov-driver.c (#include <stdint.h>): New.
        (#include <string.h>): Remove.
        (NEED_L_GCOV): Conditionally define.
        (NEED_L_GCOV_INFO_TO_GCDA): Likewise.
        (are_all_counters_zero): New.
        (dump_handler): Likewise.
        (allocate_handler): Likewise.
        (dump_unsigned): Likewise.
        (dump_counter): Likewise.
        (write_topn_counters): Add dump, allocate, and arg parameters.  Use
        dump_unsigned() and dump_counter().
        (write_one_data): Add dump, allocate, and arg parameters.  Use
        dump_unsigned(), dump_counter(), and are_all_counters_zero().
        (__gcov_info_to_gcda): New.
---
  gcc/doc/invoke.texi     |  80 +++++++++++++++---
  gcc/gcov-io.c           |  10 +++
  gcc/gcov-io.h           |   1 +
  libgcc/Makefile.in      |   2 +-
  libgcc/gcov.h           |  17 ++++
  libgcc/libgcov-driver.c | 176 ++++++++++++++++++++++++++++++----------
  6 files changed, 230 insertions(+), 56 deletions(-)

diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index e67d47af676d..2c514acf2003 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -14782,17 +14782,17 @@ To optimize the program based on the collected 
profile information, use
  Register the profile information in the specified section instead of using a
  constructor/destructor.  The section name is @var{name} if it is specified,
  otherwise the section name defaults to @code{.gcov_info}.  A pointer to the
-profile information generated by @option{-fprofile-arcs} or
-@option{-ftest-coverage} is placed in the specified section for each
-translation unit.  This option disables the profile information registration
-through a constructor and it disables the profile information processing
-through a destructor.  This option is not intended to be used in hosted
-environments such as GNU/Linux.  It targets systems with limited resources
-which do not support constructors and destructors.  The linker could collect
-the input sections in a continuous memory block and define start and end
-symbols.  The runtime support could dump the profiling information registered
-in this linker set during program termination to a serial line for example.  A
-GNU linker script example which defines a linker output section follows:
+profile information generated by @option{-fprofile-arcs} is placed in the
+specified section for each translation unit.  This option disables the profile
+information registration through a constructor and it disables the profile
+information processing through a destructor.  This option is not intended to be
+used in hosted environments such as GNU/Linux.  It targets free-standing
+environments (for example embedded systems) with limited resources which do not
+support constructors/destructors or the C library file I/O.
+
+The linker could collect the input sections in a continuous memory block and
+define start and end symbols.  A GNU linker script example which defines a
+linker output section follows:
@smallexample
    .gcov_info      :
@@ -14803,6 +14803,64 @@ GNU linker script example which defines a linker 
output section follows:
    @}
  @end smallexample
+The program could dump the profiling information registered in this linker set
+for example like this:
+
+@smallexample
+#include <gcov.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+extern const struct gcov_info *__gcov_info_start[];
+extern const struct gcov_info *__gcov_info_end[];
+
+static void
+filename (const char *f, void *arg)
+@{
+  puts (f);
+@}
+
+static void
+dump (const void *d, unsigned n, void *arg)
+@{
+  const unsigned char *c = d;
+
+  for (unsigned i = 0; i < n; ++i)
+    printf ("%02x", c[i]);
+@}
+
+static void *
+allocate (unsigned length, void *arg)
+@{
+  return malloc (length);
+@}
+
+static void
+dump_gcov_info (void)
+@{
+  const struct gcov_info **info = __gcov_info_start;
+  const struct gcov_info **end = __gcov_info_end;
+
+  /* Obfuscate variable to prevent compiler optimizations.  */
+  __asm__ ("" : "+r" (end));
+
+  while (info != end)
+  @{
+    void *arg = NULL;
+    __gcov_info_to_gcda (*info, filename, dump, allocate, arg);
+    putchar ('\n');
+    ++info;
+  @}
+@}
+
+int
+main()
+@{
+  dump_gcov_info();
+  return 0;
+@}
+@end smallexample
+
  @item -fprofile-note=@var{path}
  @opindex fprofile-note
diff --git a/gcc/gcov-io.c b/gcc/gcov-io.c
index 4b1e11d45305..8155cd129f6e 100644
--- a/gcc/gcov-io.c
+++ b/gcc/gcov-io.c
@@ -227,6 +227,16 @@ gcov_magic (gcov_unsigned_t magic, gcov_unsigned_t 
expected)
  #endif
#if !IN_GCOV
+/* Write DATA of LENGTH characters to coverage file.  */
+
+GCOV_LINKAGE void
+gcov_write (const void *data, unsigned length)
+{
+  gcov_unsigned_t r = fwrite (data, length, 1, gcov_var.file);
+  if (r != 1)
+    gcov_var.error = 1;
+}
+
  /* Write unsigned VALUE to coverage file.  */
GCOV_LINKAGE void
diff --git a/gcc/gcov-io.h b/gcc/gcov-io.h
index 538bee81263a..99e1964c1094 100644
--- a/gcc/gcov-io.h
+++ b/gcc/gcov-io.h
@@ -367,6 +367,7 @@ char *mangle_path (char const *base);
#if !IN_GCOV
  /* Available outside gcov */
+GCOV_LINKAGE void gcov_write (const void *, unsigned) ATTRIBUTE_HIDDEN;
  GCOV_LINKAGE void gcov_write_unsigned (gcov_unsigned_t) ATTRIBUTE_HIDDEN;
  #endif
diff --git a/libgcc/Makefile.in b/libgcc/Makefile.in
index 2c8be561eb53..7ec975845544 100644
--- a/libgcc/Makefile.in
+++ b/libgcc/Makefile.in
@@ -908,7 +908,7 @@ LIBGCOV_INTERFACE = _gcov_dump _gcov_fork                   
        \
        _gcov_execl _gcov_execlp                                        \
        _gcov_execle _gcov_execv _gcov_execvp _gcov_execve _gcov_reset  \
        _gcov_lock_unlock
-LIBGCOV_DRIVER = _gcov
+LIBGCOV_DRIVER = _gcov _gcov_info_to_gcda
libgcov-merge-objects = $(patsubst %,%$(objext),$(LIBGCOV_MERGE))
  libgcov-profiler-objects = $(patsubst %,%$(objext),$(LIBGCOV_PROFILER))
diff --git a/libgcc/gcov.h b/libgcc/gcov.h
index e6492cdd31ea..eea97fb8e9c9 100644
--- a/libgcc/gcov.h
+++ b/libgcc/gcov.h
@@ -25,6 +25,8 @@
  #ifndef GCC_GCOV_H
  #define GCC_GCOV_H
+struct gcov_info;
+
  /* Set all counters to zero.  */
extern void __gcov_reset (void);
@@ -33,4 +35,19 @@ extern void __gcov_reset (void);
extern void __gcov_dump (void); +/* Convert the gcov information to a gcda data stream. The first callback is
+   called exactly once with the filename associated with the gcov information.
+   The filename may be NULL.  Afterwards, the second callback is subsequently
+   called with chunks (the begin and length of the chunk are passed as the
+   first two callback parameters) of the gcda data stream.  The third callback
+   shall allocate memory with a size in characters specified by the first
+   callback parameter.  The fifth parameter is a user-provided argument passed
+   as the last argument to the callback functions.  */
+
+extern void __gcov_info_to_gcda (const struct gcov_info *,
+                                void (*) (const char *, void *),
+                                void (*) (const void *, unsigned, void *),
+                                void *(*) (unsigned, void *),
+                                void *);

I would prefer having argument names in the function declaration, it would be 
more readable.

+
  #endif /* GCC_GCOV_H */
diff --git a/libgcc/libgcov-driver.c b/libgcc/libgcov-driver.c
index df7ccb235677..c10f0c2c0a23 100644
--- a/libgcc/libgcov-driver.c
+++ b/libgcc/libgcov-driver.c
@@ -26,6 +26,20 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If 
not, see
  #include "libgcov.h"
  #include "gcov-io.h"
+#include <stdint.h>
+
+/* Return 1, if all counter values are zero, otherwise 0. */
+
+static inline int
+are_all_counters_zero (const struct gcov_ctr_info *ci_ptr)
+{
+  for (unsigned i = 0; i < ci_ptr->num; i++)
+    if (ci_ptr->values[i] != 0)
+       return 0;

There's one more level of the indentation if I'm correct.

+
+  return 1;
+}
+
  #if defined(inhibit_libc)
  /* If libc and its header files are not available, provide dummy functions.  
*/
@@ -35,8 +49,6 @@ void __gcov_init (struct gcov_info *p __attribute__ ((unused))) {} #else /* inhibit_libc */ -#include <string.h>
-
  #if GCOV_LOCKED
  #include <fcntl.h>
  #include <errno.h>
@@ -51,8 +63,17 @@ void __gcov_init (struct gcov_info *p __attribute__ 
((unused))) {}
  #include <sys/mman.h>
  #endif
-#ifdef L_gcov
+#endif /* inhibit_libc */
+
+#if defined(L_gcov) && !defined(inhibit_libc)
+#define NEED_L_GCOV
+#endif
+
+#if defined(L_gcov_info_to_gcda) && !IN_GCOV_TOOL
+#define NEED_L_GCOV_INFO_TO_GCDA
+#endif
+#ifdef NEED_L_GCOV
  /* A utility function for outputting errors.  */
  static int gcov_error (const char *, ...);
@@ -343,6 +364,43 @@ read_error:
    return -1;
  }

A comment is missing. I would rather name it gcov_dump_handler.

+static void
+dump_handler (const void *data, unsigned length, void *unused)
+{
+  (void)unused;
+  gcov_write (data, length);
+}
+

Simiarly here, gcov_ prefix would be preferred.

+static void *
+allocate_handler (unsigned size, void *unused)
+{
+  (void)unused;
+  return xmalloc (size);
+}
+#endif /* NEED_L_GCOV */
+
+#if defined(NEED_L_GCOV) || defined(NEED_L_GCOV_INFO_TO_GCDA)
+static inline void

Likewise here.

+dump_unsigned (gcov_unsigned_t word,
+              void (*dump) (const void *, unsigned, void *),
+              void *arg)
+{
+  (*dump) (&word, sizeof (word), arg);
+}

Likewise here.

+
+static inline void
+dump_counter (gcov_type counter,
+             void (*dump) (const void *, unsigned, void *),
+             void *arg)
+{
+  dump_unsigned ((gcov_unsigned_t)counter, dump, arg);
+
+  if (sizeof (counter) > sizeof (gcov_unsigned_t))
+    dump_unsigned ((gcov_unsigned_t)(counter >> 32), dump, arg);
+  else
+    dump_unsigned (0, dump, arg);
+}
+
  #define MAX(X,Y) ((X) > (Y) ? (X) : (Y))
/* Store all TOP N counters where each has a dynamic length. */
@@ -350,7 +408,10 @@ read_error:
  static void
  write_topn_counters (const struct gcov_ctr_info *ci_ptr,
                     unsigned t_ix,
-                    gcov_unsigned_t n_counts)
+                    gcov_unsigned_t n_counts,
+                    void (*dump) (const void *, unsigned, void *),
+                    void *(*allocate)(unsigned, void *),
+                    void *arg)

I would likely prefer dump_fn and allocate_fn argumen t names.

  {
    unsigned counters = n_counts / GCOV_TOPN_MEM_COUNTERS;
    gcc_assert (n_counts % GCOV_TOPN_MEM_COUNTERS == 0);
@@ -365,46 +426,48 @@ write_topn_counters (const struct gcov_ctr_info *ci_ptr,
    if (list_sizes == NULL || counters > list_size_length)
      {
        list_size_length = MAX (LIST_SIZE_MIN_LENGTH, 2 * counters);
-#if HAVE_SYS_MMAN_H
+#if !defined(inhibit_libc) && HAVE_SYS_MMAN_H
        list_sizes
        = (unsigned *)malloc_mmap (list_size_length * sizeof (unsigned));
  #endif
/* Malloc fallback. */
        if (list_sizes == NULL)
-       list_sizes = (unsigned *)xmalloc (list_size_length * sizeof (unsigned));
+       list_sizes =
+         (unsigned *)(*allocate) (list_size_length * sizeof (unsigned), arg);
      }
- memset (list_sizes, 0, counters * sizeof (unsigned));

Are you sure we don't need this zeroing?

    unsigned pair_total = 0;
for (unsigned i = 0; i < counters; i++)
      {
        gcov_type start = ci_ptr->values[GCOV_TOPN_MEM_COUNTERS * i + 2];
+      unsigned sizes = 0;
+
        for (struct gcov_kvp *node = (struct gcov_kvp *)(intptr_t)start;
           node != NULL; node = node->next)
-       {
-         ++pair_total;
-         ++list_sizes[i];
-       }
+       ++sizes;
+
+      pair_total += sizes;
+      list_sizes[i] = sizes;
      }
unsigned disk_size = GCOV_TOPN_DISK_COUNTERS * counters + 2 * pair_total;
-  gcov_write_tag_length (GCOV_TAG_FOR_COUNTER (t_ix),
-                        GCOV_TAG_COUNTER_LENGTH (disk_size));
+  dump_unsigned (GCOV_TAG_FOR_COUNTER (t_ix), dump, arg),
+  dump_unsigned (GCOV_TAG_COUNTER_LENGTH (disk_size), dump, arg);
for (unsigned i = 0; i < counters; i++)
      {
-      gcov_write_counter (ci_ptr->values[GCOV_TOPN_MEM_COUNTERS * i]);
-      gcov_write_counter (list_sizes[i]);
+      dump_counter (ci_ptr->values[GCOV_TOPN_MEM_COUNTERS * i], dump, arg);
+      dump_counter (list_sizes[i], dump, arg);
        gcov_type start = ci_ptr->values[GCOV_TOPN_MEM_COUNTERS * i + 2];
unsigned j = 0;
        for (struct gcov_kvp *node = (struct gcov_kvp *)(intptr_t)start;
           j < list_sizes[i]; node = node->next, j++)
        {
-         gcov_write_counter (node->value);
-         gcov_write_counter (node->count);
+         dump_counter (node->value, dump, arg);
+         dump_counter (node->count, dump, arg);
        }
      }
  }
@@ -415,25 +478,36 @@ write_topn_counters (const struct gcov_ctr_info *ci_ptr,
static void
  write_one_data (const struct gcov_info *gi_ptr,
-               const struct gcov_summary *prg_p)
+               const struct gcov_summary *prg_p,
+               void (*dump) (const void *, unsigned, void *),
+               void *(*allocate) (unsigned, void *),
+               void *arg)
  {
    unsigned f_ix;
- gcov_write_tag_length (GCOV_DATA_MAGIC, GCOV_VERSION);
-  gcov_write_unsigned (gi_ptr->stamp);
+  dump_unsigned (GCOV_DATA_MAGIC, dump, arg);
+  dump_unsigned (GCOV_VERSION, dump, arg);
+  dump_unsigned (gi_ptr->stamp, dump, arg);
+#ifdef NEED_L_GCOV
    /* Generate whole program statistics.  */
    gcov_write_summary (GCOV_TAG_OBJECT_SUMMARY, prg_p);
+#else
+  (void)prg_p;
+#endif

Please use rather ATTRIBUTE_UNUSED.
/* Write execution counts for each function. */
    for (f_ix = 0; f_ix != gi_ptr->n_functions; f_ix++)
      {
+#ifdef NEED_L_GCOV
        unsigned buffered = 0;
+#endif
        const struct gcov_fn_info *gfi_ptr;
        const struct gcov_ctr_info *ci_ptr;
        gcov_unsigned_t length;
        unsigned t_ix;
+#ifdef NEED_L_GCOV
        if (fn_buffer && fn_buffer->fn_ix == f_ix)
          {
            /* Buffered data from another program.  */
@@ -442,6 +516,7 @@ write_one_data (const struct gcov_info *gi_ptr,
            length = GCOV_TAG_FUNCTION_LENGTH;
          }
        else
+#endif
          {
            gfi_ptr = gi_ptr->functions[f_ix];
            if (gfi_ptr && gfi_ptr->key == gi_ptr)
@@ -450,13 +525,14 @@ write_one_data (const struct gcov_info *gi_ptr,
                  length = 0;
          }
- gcov_write_tag_length (GCOV_TAG_FUNCTION, length);
+      dump_unsigned (GCOV_TAG_FUNCTION, dump, arg);
+      dump_unsigned (length, dump, arg);
        if (!length)
          continue;
- gcov_write_unsigned (gfi_ptr->ident);
-      gcov_write_unsigned (gfi_ptr->lineno_checksum);
-      gcov_write_unsigned (gfi_ptr->cfg_checksum);
+      dump_unsigned (gfi_ptr->ident, dump, arg);
+      dump_unsigned (gfi_ptr->lineno_checksum, dump, arg);
+      dump_unsigned (gfi_ptr->cfg_checksum, dump, arg);
ci_ptr = gfi_ptr->ctrs;
        for (t_ix = 0; t_ix < GCOV_COUNTERS; t_ix++)
@@ -469,39 +545,36 @@ write_one_data (const struct gcov_info *gi_ptr,
          n_counts = ci_ptr->num;
if (t_ix == GCOV_COUNTER_V_TOPN || t_ix == GCOV_COUNTER_V_INDIR)
-           write_topn_counters (ci_ptr, t_ix, n_counts);
+           write_topn_counters (ci_ptr, t_ix, n_counts, dump, allocate, arg);
          else
            {
-             /* Do not stream when all counters are zero.  */
-             int all_zeros = 1;
-             for (unsigned i = 0; i < n_counts; i++)
-               if (ci_ptr->values[i] != 0)
-                 {
-                   all_zeros = 0;
-                   break;
-                 }
-
-             if (all_zeros)
-               gcov_write_tag_length (GCOV_TAG_FOR_COUNTER (t_ix),
-                                      GCOV_TAG_COUNTER_LENGTH (-n_counts));
+             dump_unsigned (GCOV_TAG_FOR_COUNTER (t_ix), dump, arg);
+             if (are_all_counters_zero (ci_ptr))
+               /* Do not stream when all counters are zero.  */
+               dump_unsigned (GCOV_TAG_COUNTER_LENGTH (-n_counts), dump, arg);
              else
                {
-                 gcov_write_tag_length (GCOV_TAG_FOR_COUNTER (t_ix),
-                                        GCOV_TAG_COUNTER_LENGTH (n_counts));
+                 dump_unsigned (GCOV_TAG_COUNTER_LENGTH (n_counts),
+                                dump,
+                                arg);
                  for (unsigned i = 0; i < n_counts; i++)
-                   gcov_write_counter (ci_ptr->values[i]);
+                   dump_counter (ci_ptr->values[i], dump, arg);
                }
            }
ci_ptr++;
        }
+#ifdef NEED_L_GCOV
        if (buffered)
          fn_buffer = free_fn_data (gi_ptr, fn_buffer, GCOV_COUNTERS);
+#endif
      }
- gcov_write_unsigned (0);
+  dump_unsigned (0, dump, arg);
  }
+#endif /* NEED_L_GCOV || NEED_L_GCOV_INFO_TO_GCDA */
+#ifdef NEED_L_GCOV
  /* Dump the coverage counts for one gcov_info object. We merge with existing
     counts when possible, to avoid growing the .da files ad infinitum. We use
     this program's checksum to make sure we only accumulate whole program
@@ -550,7 +623,7 @@ dump_one_gcov (struct gcov_info *gi_ptr, struct 
gcov_filename *gf,
    summary = gi_ptr->summary;
  #endif
- write_one_data (gi_ptr, &summary);
+  write_one_data (gi_ptr, &summary, dump_handler, allocate_handler, NULL);
    /* fall through */
read_fatal:;
@@ -680,5 +753,20 @@ __gcov_init (struct gcov_info *info)
      }
  }
  #endif /* !IN_GCOV_TOOL */
-#endif /* L_gcov */
-#endif /* inhibit_libc */
+#endif /* NEED_L_GCOV */
+
+#ifdef NEED_L_GCOV_INFO_TO_GCDA
+/* Convert the gcov info to a gcda data stream.  It is intended for
+   free-standing environments which do not support the C library file I/O.  */
+
+void
+__gcov_info_to_gcda (const struct gcov_info *gi_ptr,
+                    void (*filename) (const char *, void *),

What about begin_finaname_fn?

+                    void (*dump) (const void *, unsigned, void *),
+                    void *(*allocate) (unsigned, void *),
+                    void *arg)
+{
+  (*filename) (gi_ptr->filename, arg);
+  write_one_data (gi_ptr, NULL, dump, allocate, arg);
+}
+#endif /* NEED_L_GCOV_INFO_TO_GCDA */


About gcov_write_summary: it should be also dumped in order to have a complete 
.gcda file, right?
Can we remove gcov_write_counter function?
I think it can be handy having a script that creates a .gcda file from your 
reference dump output,
what do you think?
It would be nice having a test-case that can test your approach.

Thanks,
Martin

Reply via email to