From patchwork Tue Jul 13 13:03:41 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sebastian Huber X-Patchwork-Id: 1504595 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org (client-ip=2620:52:3:1:0:246e:9693:128c; helo=sourceware.org; envelope-from=gcc-patches-bounces+incoming=patchwork.ozlabs.org@gcc.gnu.org; receiver=) Received: from sourceware.org (server2.sourceware.org [IPv6:2620:52:3:1:0:246e:9693:128c]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4GPLSJ0zrDz9sf9 for ; Tue, 13 Jul 2021 23:04:15 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 5A7F03938C25 for ; Tue, 13 Jul 2021 13:04:12 +0000 (GMT) X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from dedi548.your-server.de (dedi548.your-server.de [85.10.215.148]) by sourceware.org (Postfix) with ESMTPS id E4BBE3889C0C for ; Tue, 13 Jul 2021 13:03:47 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org E4BBE3889C0C Authentication-Results: sourceware.org; dmarc=none (p=none dis=none) header.from=embedded-brains.de Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=embedded-brains.de Received: from sslproxy01.your-server.de ([78.46.139.224]) by dedi548.your-server.de with esmtpsa (TLSv1.3:TLS_AES_256_GCM_SHA384:256) (Exim 4.92.3) (envelope-from ) id 1m3I4g-000Dfn-As; Tue, 13 Jul 2021 15:03:46 +0200 Received: from [82.100.198.138] (helo=mail.embedded-brains.de) by sslproxy01.your-server.de with esmtpsa (TLSv1.3:TLS_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1m3I4g-000Vi2-7T; Tue, 13 Jul 2021 15:03:46 +0200 Received: from localhost (localhost.localhost [127.0.0.1]) by mail.embedded-brains.de (Postfix) with ESMTP id DFE6F2A1610; Tue, 13 Jul 2021 15:03:45 +0200 (CEST) Received: from mail.embedded-brains.de ([127.0.0.1]) by localhost (zimbra.eb.localhost [127.0.0.1]) (amavisd-new, port 10032) with ESMTP id wMCka0iB4i4z; Tue, 13 Jul 2021 15:03:44 +0200 (CEST) Received: from localhost (localhost.localhost [127.0.0.1]) by mail.embedded-brains.de (Postfix) with ESMTP id D9BEA2A165B; Tue, 13 Jul 2021 15:03:44 +0200 (CEST) X-Virus-Scanned: amavisd-new at zimbra.eb.localhost Received: from mail.embedded-brains.de ([127.0.0.1]) by localhost (zimbra.eb.localhost [127.0.0.1]) (amavisd-new, port 10026) with ESMTP id WQvIG7ZNZ4XG; Tue, 13 Jul 2021 15:03:44 +0200 (CEST) Received: from zimbra.eb.localhost (unknown [192.168.96.242]) by mail.embedded-brains.de (Postfix) with ESMTPSA id B56CC2A1610; Tue, 13 Jul 2021 15:03:44 +0200 (CEST) From: Sebastian Huber To: gcc-patches@gcc.gnu.org Subject: [PATCH] gcov: Add __gcov_info_to_gdca() Date: Tue, 13 Jul 2021 15:03:41 +0200 Message-Id: <20210713130341.70832-1-sebastian.huber@embedded-brains.de> X-Mailer: git-send-email 2.26.2 MIME-Version: 1.0 X-Authenticated-Sender: smtp-embedded@poldinet.de X-Virus-Scanned: Clear (ClamAV 0.103.2/26230/Tue Jul 13 13:07:40 2021) X-Spam-Status: No, score=-11.5 required=5.0 tests=BAYES_00, GIT_PATCH_0, KAM_DMARC_STATUS, SPF_HELO_NONE, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on server2.sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gcc-patches-bounces+incoming=patchwork.ozlabs.org@gcc.gnu.org Sender: "Gcc-patches" Add __gcov_info_to_gcda() to libgcov to get the gcda data for a gcda info in a free-standing 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 #include #include 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; } 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 (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 | 155 +++++++++++++++++++++++++++++++--------- 6 files changed, 218 insertions(+), 47 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 +#include +#include + +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 *); + #endif /* GCC_GCOV_H */ diff --git a/libgcc/libgcov-driver.c b/libgcc/libgcov-driver.c index df7ccb235677..4109f15e7ddb 100644 --- a/libgcc/libgcov-driver.c +++ b/libgcc/libgcov-driver.c @@ -26,6 +26,18 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see #include "libgcov.h" #include "gcov-io.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. */ @@ -51,8 +63,10 @@ void __gcov_init (struct gcov_info *p __attribute__ ((unused))) {} #include #endif -#ifdef L_gcov +#endif /* inhibit_libc */ +#ifdef L_gcov +#if !defined(inhibit_libc) /* A utility function for outputting errors. */ static int gcov_error (const char *, ...); @@ -343,6 +357,44 @@ read_error: return -1; } +static void +dump_handler (const void *data, unsigned length, void *unused) +{ + (void)unused; + gcov_write (data, length); +} + +static void * +allocate_handler (unsigned size, void *unused) +{ + (void)unused; + return xmalloc (size); +} +#endif /* inhibit_libc */ +#endif /* L_gcov */ + +#if defined(L_gcov) || defined(L_gcov_info_to_gcda) +static inline void +dump_unsigned (gcov_unsigned_t word, + void (*dump) (const void *, unsigned, void *), + void *arg) +{ + (*dump) (&word, sizeof (word), arg); +} + +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 +402,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) { unsigned counters = n_counts / GCOV_TOPN_MEM_COUNTERS; gcc_assert (n_counts % GCOV_TOPN_MEM_COUNTERS == 0); @@ -365,14 +420,15 @@ 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)); @@ -390,21 +446,21 @@ write_topn_counters (const struct gcov_ctr_info *ci_ptr, } 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 +471,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); +#if defined(L_gcov) && !defined(inhibit_libc) /* Generate whole program statistics. */ gcov_write_summary (GCOV_TAG_OBJECT_SUMMARY, prg_p); +#else + (void)prg_p; +#endif /* Write execution counts for each function. */ for (f_ix = 0; f_ix != gi_ptr->n_functions; f_ix++) { +#if defined(L_gcov) && !defined(inhibit_libc) 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; +#if defined(L_gcov) && !defined(inhibit_libc) if (fn_buffer && fn_buffer->fn_ix == f_ix) { /* Buffered data from another program. */ @@ -442,6 +509,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 +518,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 +538,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, 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++; } +#if defined(L_gcov) && !defined(inhibit_libc) if (buffered) fn_buffer = free_fn_data (gi_ptr, fn_buffer, GCOV_COUNTERS); +#endif } - gcov_write_unsigned (0); + dump_unsigned (0, dump, arg); } +#endif /* L_gcov || L_gcov_info_to_gcda */ +#if defined(L_gcov) +#if !defined(inhibit_libc) /* 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 +617,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:; @@ -682,3 +749,21 @@ __gcov_init (struct gcov_info *info) #endif /* !IN_GCOV_TOOL */ #endif /* L_gcov */ #endif /* inhibit_libc */ + +#if !IN_GCOV_TOOL +#ifdef 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 *), + 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 /* L_gcov_info_to_gcda */ +#endif /* !IN_GCOV_TOOL */