gcov: Add __gcov_info_to_gdca()

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 -lgcov 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/

	* gcov-io.h (gcov_write): Declare.
	* gcov-io.c (gcov_write): New.
	(gcov_write_counter): Remove.
	(gcov_write_tag_length): Likewise.
	(gcov_write_summary): Replace gcov_write_tag_length() with calls to
	gcov_write_unsigned().
	* doc/invoke.texi (fprofile-info-section): Mention
	__gcov_info_to_gdca().

gcc/testsuite/

	* gcc.dg/gcov-info-to-gcda.c: New test.

libgcc/

	* Makefile.in (LIBGCOV_DRIVER): Add _gcov_info_to_gcda.
	* gcov.h (gcov_info): Declare.
	(__gcov_info_to_gdca): Likewise.
	* libgcov.h (gcov_write_counter): Remove.
	(gcov_write_tag_length): 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.
	(gcov_dump_handler): Likewise.
	(gcov_allocate_handler): Likewise.
	(dump_unsigned): Likewise.
	(dump_counter): Likewise.
	(write_topn_counters): Add dump_fn, allocate_fn, and arg parameters.
	Use dump_unsigned() and dump_counter().
	(write_one_data): Add dump_fn, allocate_fn, and arg parameters.  Use
	dump_unsigned(), dump_counter(), and are_all_counters_zero().
	(__gcov_info_to_gcda): New.
This commit is contained in:
Sebastian Huber 2020-11-14 13:51:09 +01:00
parent a3d3e8c362
commit 9124bbe185
8 changed files with 304 additions and 88 deletions

View File

@ -14801,17 +14801,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 :
@ -14822,6 +14822,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" (info));
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

View File

@ -229,6 +229,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
@ -239,21 +249,6 @@ gcov_write_unsigned (gcov_unsigned_t value)
gcov_var.error = 1;
}
/* Write counter VALUE to coverage file. Sets error flag
appropriately. */
#if IN_LIBGCOV
GCOV_LINKAGE void
gcov_write_counter (gcov_type value)
{
gcov_write_unsigned ((gcov_unsigned_t) value);
if (sizeof (value) > sizeof (gcov_unsigned_t))
gcov_write_unsigned ((gcov_unsigned_t) (value >> 32));
else
gcov_write_unsigned (0);
}
#endif /* IN_LIBGCOV */
#if !IN_LIBGCOV
/* Write STRING to coverage file. Sets error flag on file
error, overflow flag on overflow */
@ -349,22 +344,13 @@ gcov_write_length (gcov_position_t position)
#else /* IN_LIBGCOV */
/* Write a tag TAG and length LENGTH. */
GCOV_LINKAGE void
gcov_write_tag_length (gcov_unsigned_t tag, gcov_unsigned_t length)
{
gcov_write_unsigned (tag);
gcov_write_unsigned (length);
}
/* Write a summary structure to the gcov file. Return nonzero on
overflow. */
/* Write a summary structure to the gcov file. */
GCOV_LINKAGE void
gcov_write_summary (gcov_unsigned_t tag, const struct gcov_summary *summary)
{
gcov_write_tag_length (tag, GCOV_TAG_SUMMARY_LENGTH);
gcov_write_unsigned (tag);
gcov_write_unsigned (GCOV_TAG_SUMMARY_LENGTH);
gcov_write_unsigned (summary->runs);
gcov_write_unsigned (summary->sum_max);
}

View File

@ -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

View File

@ -0,0 +1,60 @@
/* { dg-do run } */
/* { dg-skip-if "profile-info-section" { powerpc-ibm-aix* } } */
/* { dg-options "-fprofile-arcs -fprofile-info-section" } */
#define assert(expr) \
((expr) \
? (void)0 \
: (__builtin_printf ("%s:%i: Assertion `%s' failed.\n", \
__FILE__, __LINE__, #expr), \
__builtin_abort ()))
struct gcov_info;
extern void
__gcov_info_to_gcda (const struct gcov_info *__info,
void (*__filename_fn) (const char *, void *),
void (*__dump_fn) (const void *, unsigned, void *),
void *(*__allocate_fn) (unsigned, void *),
void *__arg);
extern const struct gcov_info *my_info;
static unsigned counter;
static void
filename (const char *f, void *arg)
{
assert (arg == &counter);
assert (__builtin_strstr (f, "gcov-info-to-gcda.c") == 0);
}
static void
dump (const void *d, unsigned n, void *arg)
{
unsigned *m = (unsigned *)arg;
assert (arg == &counter);
if (*m == 0)
{
const unsigned *u = d;
assert (*u == 0x67636461);
}
*m += n;
}
static void *
allocate (unsigned length, void *arg)
{
assert (arg == &counter);
return __builtin_malloc (length);
}
int main()
{
__asm__ volatile (".set my_info, .LPBX2");
__gcov_info_to_gcda (my_info, filename, dump, allocate, &counter);
assert (counter > 4);
return 0;
}

View File

@ -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))

View File

@ -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,21 @@ extern void __gcov_reset (void);
extern void __gcov_dump (void);
/* Convert the gcov information referenced by INFO to a gcda data stream.
The FILENAME_FN callback is called exactly once with the filename associated
with the gcov information. The filename may be NULL. Afterwards, the
DUMP_FN 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 ALLOCATE_FN callback shall allocate memory with a size in
characters specified by the first callback parameter. The ARG 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 *__info,
void (*__filename_fn) (const char *, void *),
void (*__dump_fn) (const void *, unsigned, void *),
void *(*__allocate_fn) (unsigned, void *),
void *__arg);
#endif /* GCC_GCOV_H */

View File

@ -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;
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,51 @@ read_error:
return -1;
}
/* Write the DATA of LENGTH characters to the gcov file. */
static void
gcov_dump_handler (const void *data,
unsigned length,
void *arg ATTRIBUTE_UNUSED)
{
gcov_write (data, length);
}
/* Allocate SIZE characters and return the address of the allocated memory. */
static void *
gcov_allocate_handler (unsigned size, void *arg ATTRIBUTE_UNUSED)
{
return xmalloc (size);
}
#endif /* NEED_L_GCOV */
#if defined(NEED_L_GCOV) || defined(NEED_L_GCOV_INFO_TO_GCDA)
/* Dump the WORD using the DUMP handler called with ARG. */
static inline void
dump_unsigned (gcov_unsigned_t word,
void (*dump_fn) (const void *, unsigned, void *),
void *arg)
{
(*dump_fn) (&word, sizeof (word), arg);
}
/* Dump the COUNTER using the DUMP handler called with ARG. */
static inline void
dump_counter (gcov_type counter,
void (*dump_fn) (const void *, unsigned, void *),
void *arg)
{
dump_unsigned ((gcov_unsigned_t)counter, dump_fn, arg);
if (sizeof (counter) > sizeof (gcov_unsigned_t))
dump_unsigned ((gcov_unsigned_t)(counter >> 32), dump_fn, arg);
else
dump_unsigned (0, dump_fn, arg);
}
#define MAX(X,Y) ((X) > (Y) ? (X) : (Y))
/* Store all TOP N counters where each has a dynamic length. */
@ -350,7 +416,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_fn) (const void *, unsigned, void *),
void *(*allocate_fn)(unsigned, void *),
void *arg)
{
unsigned counters = n_counts / GCOV_TOPN_MEM_COUNTERS;
gcc_assert (n_counts % GCOV_TOPN_MEM_COUNTERS == 0);
@ -365,46 +434,49 @@ 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_fn) (list_size_length * sizeof (unsigned),
arg);
}
memset (list_sizes, 0, counters * sizeof (unsigned));
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_fn, arg),
dump_unsigned (GCOV_TAG_COUNTER_LENGTH (disk_size), dump_fn, 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_fn, arg);
dump_counter (list_sizes[i], dump_fn, 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_fn, arg);
dump_counter (node->count, dump_fn, arg);
}
}
}
@ -415,25 +487,34 @@ 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 ATTRIBUTE_UNUSED,
void (*dump_fn) (const void *, unsigned, void *),
void *(*allocate_fn) (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_fn, arg);
dump_unsigned (GCOV_VERSION, dump_fn, arg);
dump_unsigned (gi_ptr->stamp, dump_fn, arg);
#ifdef NEED_L_GCOV
/* Generate whole program statistics. */
gcov_write_summary (GCOV_TAG_OBJECT_SUMMARY, prg_p);
#endif
/* 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 +523,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 +532,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_fn, arg);
dump_unsigned (length, dump_fn, 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_fn, arg);
dump_unsigned (gfi_ptr->lineno_checksum, dump_fn, arg);
dump_unsigned (gfi_ptr->cfg_checksum, dump_fn, arg);
ci_ptr = gfi_ptr->ctrs;
for (t_ix = 0; t_ix < GCOV_COUNTERS; t_ix++)
@ -469,39 +552,37 @@ 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_fn, allocate_fn,
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_fn, 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_fn, 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_fn, arg);
for (unsigned i = 0; i < n_counts; i++)
gcov_write_counter (ci_ptr->values[i]);
dump_counter (ci_ptr->values[i], dump_fn, 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_fn, 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 +631,8 @@ 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, gcov_dump_handler, gcov_allocate_handler,
NULL);
/* fall through */
read_fatal:;
@ -680,5 +762,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_fn) (const char *, void *),
void (*dump_fn) (const void *, unsigned, void *),
void *(*allocate_fn) (unsigned, void *),
void *arg)
{
(*filename_fn) (gi_ptr->filename, arg);
write_one_data (gi_ptr, NULL, dump_fn, allocate_fn, arg);
}
#endif /* NEED_L_GCOV_INFO_TO_GCDA */

View File

@ -114,13 +114,11 @@ typedef unsigned gcov_type_unsigned __attribute__ ((mode (QI)));
#define gcov_var __gcov_var
#define gcov_open __gcov_open
#define gcov_close __gcov_close
#define gcov_write_tag_length __gcov_write_tag_length
#define gcov_position __gcov_position
#define gcov_seek __gcov_seek
#define gcov_rewrite __gcov_rewrite
#define gcov_is_error __gcov_is_error
#define gcov_write_unsigned __gcov_write_unsigned
#define gcov_write_counter __gcov_write_counter
#define gcov_write_summary __gcov_write_summary
#define gcov_read_unsigned __gcov_read_unsigned
#define gcov_read_counter __gcov_read_counter
@ -345,9 +343,6 @@ extern int __gcov_execve (const char *, char *const [], char *const [])
/* Functions that only available in libgcov. */
GCOV_LINKAGE int gcov_open (const char */*name*/) ATTRIBUTE_HIDDEN;
GCOV_LINKAGE void gcov_write_counter (gcov_type) ATTRIBUTE_HIDDEN;
GCOV_LINKAGE void gcov_write_tag_length (gcov_unsigned_t, gcov_unsigned_t)
ATTRIBUTE_HIDDEN;
GCOV_LINKAGE void gcov_write_summary (gcov_unsigned_t /*tag*/,
const struct gcov_summary *)
ATTRIBUTE_HIDDEN;