From c7ef3aa25dbd9d3fa738e2e985b8052f4e2b8c59 Mon Sep 17 00:00:00 2001 From: Sebastian Huber Date: Wed, 23 Mar 2022 10:20:56 +0100 Subject: [PATCH] Back port v1 of gcov-tool merge-stream to GCC 12 gcov-tool: Allow merging of empty profile lists The gcov_profile_merge() already had code to deal with profile information which had no counterpart to merge with. For profile information from files with no associated counterpart, the profile information is simply used as is with the weighting transformation applied. Make sure that gcov_profile_merge() works with an empty target profile list. Return the merged profile list. gcc/ * gcov-tool.cc (gcov_profile_merge): Adjust return type. (profile_merge): Allow merging of directories which contain no profile files. libgcc/ * libgcov-util.c (gcov_profile_merge): Return the list of merged profiles. Accept empty target and source profile lists. gcov: Add mode to all gcov_open() gcc/ * gcov-io.cc (gcov_open): Always use the mode parameter. * gcov-io.h (gcov_open): Declare it unconditionally. libgcc/ * libgcov-driver-system.c (gcov_exit_open_gcda_file): Open file for reading and writing. * libgcov-util.c (read_gcda_file): Open file for reading. * libgcov.h (gcov_open): Delete declaration. gcov: Add open mode parameter to gcov_do_dump() gcc/ * gcov-tool.cc (gcov_do_dump): Add mode parameter. (gcov_output_files): Open files for reading and writing. libgcc/ * libgcov-driver-system.c (gcov_exit_open_gcda_file): Add mode parameter. Pass mode to gcov_open() calls. * libgcov-driver.c (dump_one_gcov): Add mode parameter. Pass mode to gcov_exit_open_gcda_file() call. (gcov_do_dump): Add mode parameter. Pass mode to dump_one_gcov() calls. (__gcov_dump_one): Open file for reading and writing. gcov: Make gcov_seek() static This function is only used by gcov_write_length() in the gcov-io.cc file. gcc/ * gcov-io.cc (gcov_seek): Make it static. * gcov-io.h (struct gcov_summary): Do not mention gcov_seek(). libgcc/ * libgcov.h (gcov_seek): Remove define and declaration. gcov: Add __gcov_filename_to_gcfn() gcc/ * doc/invoke.texi (fprofile-info-section): Mention __gcov_filename_to_gcfn(). Use "freestanding" to match with C11 standard language. Fix minor example code issues. * gcov-io.h (GCOV_FILENAME_MAGIC): Define and document. gcc/testsuite/ * gcc.dg/gcov-info-to-gcda.c: Test __gcov_filename_to_gcfn(). libgcc/ * gcov.h (__gcov_info_to_gcda): Mention __gcov_filename_to_gcfn(). (__gcov_filename_to_gcfn): Declare and document. * libgcov-driver.c (dump_string): New. (__gcov_filename_to_gcfn): Likewise. (__gcov_info_to_gcda): Adjust comment to match C11 standard language. gcov-tool: Support file input from stdin gcc/ * gcov-io.cc (GCOV_MODE_STDIN): Define. (gcov_position): For gcov-tool, return calculated position if file is stdin. (gcov_open): For gcov-tool, use stdin if filename is NULL. (gcov_close): For gcov-tool, do not close stdin. (gcov_read_bytes): For gcov-tool, update position if file is stdin. (gcov_sync): For gcov-tool, discard input if file is stdin. gcov: Use xstrdup() Move duplication of filename to caller and use xstrdup() instead of custom code. This helps to reuse read_gcda_file() for other purposes. libgcc/ * libgcov-util.c (read_gcda_file): Do not duplicate filename. (ftw_read_file): Duplicate filename for read_gcda_file(). gcov: Move prepend to list to read_gcda_file() This helps to reuse read_gcda_file(). libgcc/ * libgcov-util.c (read_gcda_file): Prepend new info object to global list. (ftw_read_file): Remove list append here. gcov: Move gcov_open() to caller of read_gcda_file() This allows to reuse read_gcda_file() to read multiple objects from a single file. libgcc/ * libgcov-util.c (read_gcda_file): Do not open file. (ftw_read_file): Open file here. gcov: Fix integer types in ftw_read_file() libgcc/ * libgcov-util.c (ftw_read_file): Use size_t for strlen() variables. gcov: Record EOF error during read Use an enum for file error codes. gcc/ * gcov-io.cc (gcov_file_error): New enum. (gcov_var): Use gcov_file_error enum for the error member. (gcov_open): Use GCOV_FILE_NO_ERROR. (gcov_close): Use GCOV_FILE_WRITE_ERROR. (gcov_write): Likewise. (gcov_write_unsigned): Likewise. (gcov_write_string): Likewise. (gcov_read_bytes): Set error code if EOF is reached. (gcov_read_counter): Use GCOV_FILE_COUNTER_OVERFLOW. gcov-tool: Add merge-stream subcommand gcc/ * doc/gcov-tool.texi: Document merge-stream subcommand. * doc/invoke.texi (fprofile-info-section): Mention merge-stream subcommand of gcov-tool. * gcov-tool.cc (gcov_profile_merge_stream): Declare. (print_merge_stream_usage_message): New. (merge_stream_usage): Likewise. (do_merge_stream): Likewise. (print_usage): Call print_merge_stream_usage_message(). (main): Call do_merge_stream() to execute merge-stream subcommand. libgcc/ * libgcov-util.c (consume_stream): New. (get_target_profiles_for_merge): Likewise. (gcov_profile_merge_stream): Likewise. gcov: Use xstrerror() libgcc/ * libgcov-util.c (ftw_read_file): Improve notice using xstrerror(). (gcov_profile_merge_stream): Likewise. gcov: Add section for freestanding environments gcc/ * doc/gcov.texi (Profiling and Test Coverage in Freestanding Environments): New section. gcov: Fix first time gcov info dump This patch fixes an issue introduced by commit ef9a53feae5701953da9161afef2aea0329ec8b2: gcc --coverage main.c && ./a.out libgcov profiling error:a-main.gcda:Error writing gcc/ChangeLog: * gcov-io.cc (gcov_rewrite): Clear the file error status. --- gcc/doc/gcov-tool.texi | 36 +++ gcc/doc/gcov.texi | 389 +++++++++++++++++++++++ gcc/doc/invoke.texi | 31 +- gcc/gcov-io.cc | 84 +++-- gcc/gcov-io.h | 35 +- gcc/gcov-tool.cc | 107 +++++-- gcc/testsuite/gcc.dg/gcov-info-to-gcda.c | 36 ++- libgcc/gcov.h | 17 +- libgcc/libgcov-driver-system.c | 7 +- libgcc/libgcov-driver.c | 44 ++- libgcc/libgcov-util.c | 150 +++++++-- libgcc/libgcov.h | 3 - 12 files changed, 821 insertions(+), 118 deletions(-) diff --git a/gcc/doc/gcov-tool.texi b/gcc/doc/gcov-tool.texi index d79dbc94a8c..77150836acc 100644 --- a/gcc/doc/gcov-tool.texi +++ b/gcc/doc/gcov-tool.texi @@ -52,6 +52,10 @@ Current gcov-tool supports the following functionalities: @item merge two sets of profiles with weights. +@item +read a stream of profiles with associated filenames and merge it with a set of +profiles with weights. + @item read one set of profile and rewrite profile contents. One can scale or normalize the count values. @@ -64,6 +68,12 @@ Collect the profiles for different set of inputs, and use this tool to merge them. One can specify the weight to factor in the relative importance of each input. +@item +Collect profiles from target systems without a filesystem (freestanding +environments). Merge the collected profiles with associated profiles +present on the host system. One can specify the weight to factor in the +relative importance of each input. + @item Rewrite the profile after removing a subset of the gcda files, while maintaining the consistency of the summary and the histogram. @@ -117,6 +127,10 @@ gcov-tool merge [merge-options] @var{directory1} @var{directory2} [@option{-v}|@option{--verbose}] [@option{-w}|@option{--weight} @var{w1,w2}] +gcov-tool merge-stream [merge-stream-options] [@var{file}] + [@option{-v}|@option{--verbose}] + [@option{-w}|@option{--weight} @var{w1,w2}] + gcov-tool rewrite [rewrite-options] @var{directory} [@option{-n}|@option{--normalize} @var{long_long_value}] [@option{-o}|@option{--output} @var{directory}] @@ -169,6 +183,28 @@ Set the merge weights of the @var{directory1} and @var{directory2}, respectively. The default weights are 1 for both. @end table +@item merge-stream +Collect profiles with associated filenames from a @emph{gcfn} and @emph{gcda} +data stream. Read the stream from the file specified by @var{file} or from +@file{stdin}. Merge the profiles with associated profiles in the host +filesystem. Apply the optional weights while merging profiles. + +For the generation of a @emph{gcfn} and @emph{gcda} data stream on the target +system, please have a look at the @code{__gcov_filename_to_gcfn()} and +@code{__gcov_info_to_gcda()} functions declared in @code{#include }. +@table @gcctabopt + +@item -v +@itemx --verbose +Set the verbose mode. + +@item -w @var{w1},@var{w2} +@itemx --weight @var{w1},@var{w2} +Set the merge weights of the profiles from the @emph{gcfn} and @emph{gcda} data +stream and the associated profiles in the host filesystem, respectively. The +default weights are 1 for both. +@end table + @item rewrite Read the specified profile directory and rewrite to a new directory. @table @gcctabopt diff --git a/gcc/doc/gcov.texi b/gcc/doc/gcov.texi index fc39da0f02d..a1f7d26e610 100644 --- a/gcc/doc/gcov.texi +++ b/gcc/doc/gcov.texi @@ -41,6 +41,8 @@ test code coverage in your programs. * Gcov and Optimization:: Using gcov with GCC optimization. * Gcov Data Files:: The files used by gcov. * Cross-profiling:: Data file relocation. +* Freestanding Environments:: How to use profiling and test + coverage in freestanding environments. @end menu @node Gcov Intro @@ -971,3 +973,390 @@ setting will name the data file @file{/target/run/build/foo.gcda}. You must move the data files to the expected directory tree in order to use them for profile directed optimizations (@option{-fprofile-use}), or to use the @command{gcov} tool. + +@node Freestanding Environments +@section Profiling and Test Coverage in Freestanding Environments + +In case your application runs in a hosted environment such as GNU/Linux, then +this section is likely not relevant to you. This section is intended for +application developers targeting freestanding environments (for example +embedded systems) with limited resources. In particular, systems or test cases +which do not support constructors/destructors or the C library file I/O. In +this section, the @dfn{target system} runs your application instrumented for +profiling or test coverage. You develop and analyze your application on the +@dfn{host system}. We now provide an overview how profiling and test coverage +can be obtained in this scenario followed by a tutorial which can be exercised +on the host system. Finally, some system initialization caveats are listed. + +@subsection Overview + +For an application instrumented for profiling or test coverage, the compiler +generates some global data structures which are updated by instrumentation code +while the application runs. These data structures are called the @dfn{gcov +information}. Normally, when the application exits, the gcov information is +stored to @file{.gcda} files. There is one file per translation unit +instrumented for profiling or test coverage. The function +@code{__gcov_exit()}, which stores the gcov information to a file, is called by +a global destructor function for each translation unit instrumented for +profiling or test coverage. It runs at process exit. In a global constructor +function, the @code{__gcov_init()} function is called to register the gcov +information of a translation unit in a global list. In some situations, this +procedure does not work. Firstly, if you want to profile the global +constructor or exit processing of an operating system, the compiler generated +functions may conflict with the test objectives. Secondly, you may want to +test early parts of the system initialization or abnormal program behaviour +which do not allow a global constructor or exit processing. Thirdly, you need +a filesystem to store the files. + +The @option{-fprofile-info-section} GCC option enables you to use profiling and +test coverage in freestanding environments. This option disables the use of +global constructors and destructors for the gcov information. Instead, a +pointer to the gcov information is stored in a special linker input section for +each translation unit which is compiled with this option. By default, the +section name is @code{.gcov_info}. The gcov information is statically +initialized. The pointers to the gcov information from all translation units +of an executable can be collected by the linker in a contiguous memory block. +For the GNU linker, the below linker script output section definition can be +used to achieve this: + +@smallexample + .gcov_info : + @{ + PROVIDE (__gcov_info_start = .); + KEEP (*(.gcov_info)) + PROVIDE (__gcov_info_end = .); + @} +@end smallexample + +The linker will provide two global symbols, @code{__gcov_info_start} and +@code{__gcov_info_end}, which define the start and end of the array of pointers +to gcov information blocks, respectively. The @code{KEEP ()} directive is +required to prevent a garbage collection of the pointers. They are not +directly referenced by anything in the executable. The section may be placed +in a read-only memory area. + +In order to transfer the profiling and test coverage data from the target to +the host system, the application has to provide a function to produce a +reliable in order byte stream from the target to the host. The byte stream may +be compressed and encoded using error detection and correction codes to meet +application-specific requirements. The GCC provided @file{libgcov} target +library provides two functions, @code{__gcov_info_to_gcda()} and +@code{__gcov_filename_to_gcfn()}, to generate a byte stream from a gcov +information bock. The functions are declared in @code{#include }. The +byte stream can be deserialized by the @command{merge-stream} subcommand of the +@command{gcov-tool} to create or update @file{.gcda} files in the host +filesystem for the instrumented application. + +@subsection Tutorial + +This tutorial should be exercised on the host system. We will build a program +instrumented for test coverage. The program runs an application and dumps the +gcov information to @file{stderr} encoded as a printable character stream. The +application simply decodes such character streams from @file{stdin} and writes +the decoded character stream to @file{stdout} (warning: this is binary data). +The decoded character stream is consumed by the @command{merge-stream} +subcommand of the @command{gcov-tool} to create or update the @file{.gcda} +files. + +To get started, create an empty directory. Change into the new directory. +Then you will create the following three files in this directory + +@enumerate +@item +@file{app.h} - a header file included by @file{app.c} and @file{main.c}, + +@item +@file{app.c} - a source file which contains an example application, and + +@item +@file{main.c} - a source file which contains the program main function and code +to dump the gcov information. +@end enumerate + +Firstly, create the header file @file{app.h} with the following content: + +@smallexample +static const unsigned char a = 'a'; + +static inline unsigned char * +encode (unsigned char c, unsigned char buf[2]) +@{ + buf[0] = c % 16 + a; + buf[1] = (c / 16) % 16 + a; + return buf; +@} + +extern void application (void); +@end smallexample + +Secondly, create the source file @file{app.c} with the following content: + +@smallexample +#include "app.h" + +#include + +/* The application reads a character stream encoded by encode() from stdin, + decodes it, and writes the decoded characters to stdout. Characters other + than the 16 characters 'a' to 'p' are ignored. */ + +static int can_decode (unsigned char c) +@{ + return (unsigned char)(c - a) < 16; +@} + +void +application (void) +@{ + int first = 1; + int i; + unsigned char c; + + while ((i = fgetc (stdin)) != EOF) + @{ + unsigned char x = (unsigned char)i; + + if (can_decode (x)) + @{ + if (first) + c = x - a; + else + fputc (c + 16 * (x - a), stdout); + first = !first; + @} + else + first = 1; + @} +@} +@end smallexample + +Thirdly, create the source file @file{main.c} with the following content: + +@smallexample +#include "app.h" + +#include +#include +#include + +/* The start and end symbols are provided by the linker script. We use the + array notation to avoid issues with a potential small-data area. */ + +extern const struct gcov_info *const __gcov_info_start[]; +extern const struct gcov_info *const __gcov_info_end[]; + +/* This function shall produce a reliable in order byte stream to transfer the + gcov information from the target to the host system. */ + +static void +dump (const void *d, unsigned n, void *arg) +@{ + (void)arg; + const unsigned char *c = d; + unsigned char buf[2]; + + for (unsigned i = 0; i < n; ++i) + fwrite (encode (c[i], buf), sizeof (buf), 1, stderr); +@} + +/* The filename is serialized to a gcfn data stream by the + __gcov_filename_to_gcfn() function. The gcfn data is used by the + "merge-stream" subcommand of the "gcov-tool" to figure out the filename + associated with the gcov information. */ + +static void +filename (const char *f, void *arg) +@{ + __gcov_filename_to_gcfn (f, dump, arg); +@} + +/* The __gcov_info_to_gcda() function may have to allocate memory under + certain conditions. Simply try it out if it is needed for your application + or not. */ + +static void * +allocate (unsigned length, void *arg) +@{ + (void)arg; + return malloc (length); +@} + +/* Dump the gcov information of all translation units. */ + +static void +dump_gcov_info (void) +@{ + const struct gcov_info *const *info = __gcov_info_start; + const struct gcov_info *const *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); + fputc ('\n', stderr); + ++info; + @} +@} + +/* The main() function just runs the application and then dumps the gcov + information to stderr. */ + +int +main (void) +@{ + application (); + dump_gcov_info (); + return 0; +@} +@end smallexample + +If we compile @file{app.c} with test coverage and no extra profiling options, +then a global constructor (@code{_sub_I_00100_0} here, it may have a different +name in your environment) and destructor (@code{_sub_D_00100_1}) is used to +register and dump the gcov information, respectively. We also see undefined +references to @code{__gcov_init} and @code{__gcov_exit}: + +@smallexample +$ gcc --coverage -c app.c +$ nm app.o +0000000000000000 r a +0000000000000030 T application +0000000000000000 t can_decode + U fgetc + U fputc +0000000000000000 b __gcov0.application +0000000000000038 b __gcov0.can_decode +0000000000000000 d __gcov_.application +00000000000000c0 d __gcov_.can_decode + U __gcov_exit + U __gcov_init + U __gcov_merge_add + U stdin + U stdout +0000000000000161 t _sub_D_00100_1 +0000000000000151 t _sub_I_00100_0 +@end smallexample + +Compile @file{app.c} and @file{main.c} with test coverage and +@option{-fprofile-info-section}. Now, a read-only pointer size object is +present in the @code{.gcov_info} section and there are no undefined references +to @code{__gcov_init} and @code{__gcov_exit}: + +@smallexample +$ gcc --coverage -fprofile-info-section -c main.c +$ gcc --coverage -fprofile-info-section -c app.c +$ objdump -h app.o + +app.o: file format elf64-x86-64 + +Sections: +Idx Name Size VMA LMA File off Algn + 0 .text 00000151 0000000000000000 0000000000000000 00000040 2**0 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 1 .data 00000100 0000000000000000 0000000000000000 000001a0 2**5 + CONTENTS, ALLOC, LOAD, RELOC, DATA + 2 .bss 00000040 0000000000000000 0000000000000000 000002a0 2**5 + ALLOC + 3 .rodata 0000003c 0000000000000000 0000000000000000 000002a0 2**3 + CONTENTS, ALLOC, LOAD, READONLY, DATA + 4 .gcov_info 00000008 0000000000000000 0000000000000000 000002e0 2**3 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA + 5 .comment 0000004e 0000000000000000 0000000000000000 000002e8 2**0 + CONTENTS, READONLY + 6 .note.GNU-stack 00000000 0000000000000000 0000000000000000 00000336 2**0 + CONTENTS, READONLY + 7 .eh_frame 00000058 0000000000000000 0000000000000000 00000338 2**3 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA +@end smallexample + +We have to customize the program link procedure so that all the +@code{.gcov_info} linker input sections are placed in a contiguous memory block +with a begin and end symbol. Firstly, get the default linker script using the +following commands (we assume a GNU linker): + +@smallexample +$ ld --verbose | sed '1,/^===/d' | sed '/^===/d' > linkcmds +@end smallexample + +Secondly, open the file @file{linkcmds} with a text editor and place the linker +output section definition from the overview after the @code{.rodata} section +definition. Link the program executable using the customized linker script: + +@smallexample +$ gcc --coverage main.o app.o -T linkcmds -Wl,-Map,app.map +@end smallexample + +In the linker map file @file{app.map}, we see that the linker placed the +read-only pointer size objects of our objects files @file{main.o} and +@file{app.o} into a contiguous memory block and provided the symbols +@code{__gcov_info_start} and @code{__gcov_info_end}: + +@smallexample +$ grep -C 1 "\.gcov_info" app.map + +.gcov_info 0x0000000000403ac0 0x10 + 0x0000000000403ac0 PROVIDE (__gcov_info_start = .) + *(.gcov_info) + .gcov_info 0x0000000000403ac0 0x8 main.o + .gcov_info 0x0000000000403ac8 0x8 app.o + 0x0000000000403ad0 PROVIDE (__gcov_info_end = .) +@end smallexample + +Make sure no @file{.gcda} files are present. Run the program with nothing to +decode and dump @file{stderr} to the file @file{gcda-0.txt} (first run). Run +the program to decode @file{gcda-0.txt} and send it to the @command{gcov-tool} +using the @command{merge-stream} subcommand to create the @file{.gcda} files +(second run). Run @command{gcov} to produce a report for @file{app.c}. We see +that the first run with nothing to decode results in a partially covered +application: + +@smallexample +$ rm -f app.gcda main.gcda +$ echo "" | ./a.out 2>gcda-0.txt +$ ./a.out gcda-1.txt | gcov-tool merge-stream +$ gcov -bc app.c +File 'app.c' +Lines executed:69.23% of 13 +Branches executed:66.67% of 6 +Taken at least once:50.00% of 6 +Calls executed:66.67% of 3 +Creating 'app.c.gcov' + +Lines executed:69.23% of 13 +@end smallexample + +Run the program to decode @file{gcda-1.txt} and send it to the +@command{gcov-tool} using the @command{merge-stream} subcommand to update the +@file{.gcda} files. Run @command{gcov} to produce a report for @file{app.c}. +Since the second run decoded the gcov information of the first run, we have now +a fully covered application: + +@smallexample +$ ./a.out gcda-2.txt | gcov-tool merge-stream +$ gcov -bc app.c +File 'app.c' +Lines executed:100.00% of 13 +Branches executed:100.00% of 6 +Taken at least once:100.00% of 6 +Calls executed:100.00% of 3 +Creating 'app.c.gcov' + +Lines executed:100.00% of 13 +@end smallexample + +@subsection System Initialization Caveats + +The gcov information of a translation unit consists of several global data +structures. For example, the instrumented code may update program flow graph +edge counters in a zero-initialized data structure. It is safe to run +instrumented code before the zero-initialized data is cleared to zero. The +coverage information obtained before the zero-initialized data is cleared to +zero is unusable. Dumping the gcov information using +@code{__gcov_info_to_gcda()} before the zero-initialized data is cleared to +zero or the initialized data is loaded, is undefined behaviour. Clearing the +zero-initialized data to zero through a function instrumented for profiling or +test coverage is undefined behaviour, since it may produce inconsistent program +flow graph edge counters for example. diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 07b440190c3..cef0fa9f084 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -15462,7 +15462,7 @@ 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 +used in hosted environments such as GNU/Linux. It targets freestanding environments (for example embedded systems) with limited resources which do not support constructors/destructors or the C library file I/O. @@ -15487,14 +15487,8 @@ for example like this: #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); -@} +extern const struct gcov_info *const __gcov_info_start[]; +extern const struct gcov_info *const __gcov_info_end[]; static void dump (const void *d, unsigned n, void *arg) @@ -15505,6 +15499,12 @@ dump (const void *d, unsigned n, void *arg) printf ("%02x", c[i]); @} +static void +filename (const char *f, void *arg) +@{ + __gcov_filename_to_gcfn (f, dump, arg ); +@} + static void * allocate (unsigned length, void *arg) @{ @@ -15514,8 +15514,8 @@ allocate (unsigned length, void *arg) static void dump_gcov_info (void) @{ - const struct gcov_info **info = __gcov_info_start; - const struct gcov_info **end = __gcov_info_end; + const struct gcov_info *const *info = __gcov_info_start; + const struct gcov_info *const *end = __gcov_info_end; /* Obfuscate variable to prevent compiler optimizations. */ __asm__ ("" : "+r" (info)); @@ -15530,13 +15530,18 @@ dump_gcov_info (void) @} int -main() +main (void) @{ - dump_gcov_info(); + dump_gcov_info (); return 0; @} @end smallexample +The @command{merge-stream} subcommand of @command{gcov-tool} may be used to +deserialize the data stream generated by the @code{__gcov_filename_to_gcfn} and +@code{__gcov_info_to_gcda} functions and merge the profile information into +@file{.gcda} files on the host filesystem. + @item -fprofile-note=@var{path} @opindex fprofile-note diff --git a/gcc/gcov-io.cc b/gcc/gcov-io.cc index 72c40f8eaa0..62032ccfa18 100644 --- a/gcc/gcov-io.cc +++ b/gcc/gcov-io.cc @@ -29,14 +29,29 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see static gcov_unsigned_t *gcov_read_words (void *buffer, unsigned); +/* Indicates the last gcov file access error or that no error occurred + so far. */ +enum gcov_file_error +{ + GCOV_FILE_COUNTER_OVERFLOW = -1, + GCOV_FILE_NO_ERROR = 0, + GCOV_FILE_WRITE_ERROR = 1, + GCOV_FILE_EOF = 2 +}; + struct gcov_var { FILE *file; - int error; /* < 0 overflow, > 0 disk error. */ + enum gcov_file_error error; int mode; /* < 0 writing, > 0 reading. */ int endian; /* Swap endianness. */ +#ifdef IN_GCOV_TOOL + gcov_position_t pos; /* File position for stdin support. */ +#endif } gcov_var; +#define GCOV_MODE_STDIN 2 + /* Save the current position in the gcov file. */ /* We need to expose this function when compiling for gcov-tool. */ #ifndef IN_GCOV_TOOL @@ -45,6 +60,10 @@ static inline gcov_position_t gcov_position (void) { +#ifdef IN_GCOV_TOOL + if (gcov_var.mode == GCOV_MODE_STDIN) + return gcov_var.pos; +#endif return ftell (gcov_var.file); } @@ -60,11 +79,14 @@ gcov_is_error (void) } #if IN_LIBGCOV -/* Move to beginning of file and initialize for writing. */ +/* Move to beginning of file, initialize for writing, and clear file error + status. */ + GCOV_LINKAGE inline void gcov_rewrite (void) { gcov_var.mode = -1; + gcov_var.error = GCOV_FILE_NO_ERROR; fseek (gcov_var.file, 0L, SEEK_SET); } #endif @@ -89,15 +111,8 @@ from_file (gcov_unsigned_t value) Return zero on failure, non-zero on success. */ GCOV_LINKAGE int -#if IN_LIBGCOV -gcov_open (const char *name) -#else gcov_open (const char *name, int mode) -#endif { -#if IN_LIBGCOV - int mode = 0; -#endif #if GCOV_LOCKED struct flock s_flock; int fd; @@ -111,10 +126,20 @@ gcov_open (const char *name, int mode) #endif gcov_nonruntime_assert (!gcov_var.file); - gcov_var.error = 0; + gcov_var.error = GCOV_FILE_NO_ERROR; #if !IN_LIBGCOV || defined (IN_GCOV_TOOL) gcov_var.endian = 0; #endif +#ifdef IN_GCOV_TOOL + gcov_var.pos = 0; + if (!name) + { + gcov_nonruntime_assert (gcov_var.mode > 0); + gcov_var.file = stdin; + gcov_var.mode = GCOV_MODE_STDIN; + return 1; + } +#endif #if GCOV_LOCKED if (mode > 0) { @@ -197,10 +222,15 @@ gcov_open (const char *name, int mode) GCOV_LINKAGE int gcov_close (void) { +#ifdef IN_GCOV_TOOL + if (gcov_var.file == stdin) + gcov_var.file = 0; + else +#endif if (gcov_var.file) { if (fclose (gcov_var.file)) - gcov_var.error = 1; + gcov_var.error = GCOV_FILE_WRITE_ERROR; gcov_var.file = 0; } @@ -236,7 +266,7 @@ 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; + gcov_var.error = GCOV_FILE_WRITE_ERROR; } /* Write unsigned VALUE to coverage file. */ @@ -246,7 +276,7 @@ gcov_write_unsigned (gcov_unsigned_t value) { gcov_unsigned_t r = fwrite (&value, sizeof (value), 1, gcov_var.file); if (r != 1) - gcov_var.error = 1; + gcov_var.error = GCOV_FILE_WRITE_ERROR; } #if !IN_LIBGCOV @@ -266,7 +296,7 @@ gcov_write_string (const char *string) { gcov_unsigned_t r = fwrite (string, length, 1, gcov_var.file); if (r != 1) - gcov_var.error = 1; + gcov_var.error = GCOV_FILE_WRITE_ERROR; } } #endif @@ -301,17 +331,15 @@ gcov_write_filename (const char *filename) gcov_write_string (filename); } -#endif /* Move to a given position in a gcov file. */ -GCOV_LINKAGE void +static void gcov_seek (gcov_position_t base) { fseek (gcov_var.file, base, SEEK_SET); } -#if !IN_LIBGCOV /* Write a tag TAG and reserve space for the record length. Return a value to be used for gcov_write_length. */ @@ -370,8 +398,15 @@ gcov_read_bytes (void *buffer, unsigned count) unsigned read = fread (buffer, count, 1, gcov_var.file); if (read != 1) - return NULL; + { + if (feof (gcov_var.file)) + gcov_var.error = GCOV_FILE_EOF; + return NULL; + } +#ifdef IN_GCOV_TOOL + gcov_var.pos += count; +#endif return buffer; } @@ -416,7 +451,7 @@ gcov_read_counter (void) if (sizeof (value) > sizeof (gcov_unsigned_t)) value |= ((gcov_type) from_file (buffer[1])) << 32; else if (buffer[1]) - gcov_var.error = -1; + gcov_var.error = GCOV_FILE_COUNTER_OVERFLOW; return value; } @@ -508,6 +543,17 @@ gcov_sync (gcov_position_t base, gcov_unsigned_t length) { gcov_nonruntime_assert (gcov_var.mode > 0); base += length; +#ifdef IN_GCOV_TOOL + if (gcov_var.mode == GCOV_MODE_STDIN) + { + while (gcov_var.pos < base) + { + ++gcov_var.pos; + (void)fgetc (gcov_var.file); + } + return; + } +#endif fseek (gcov_var.file, base, SEEK_SET); } #endif diff --git a/gcc/gcov-io.h b/gcc/gcov-io.h index 99ce7dbccc8..30947634d73 100644 --- a/gcc/gcov-io.h +++ b/gcc/gcov-io.h @@ -60,14 +60,21 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see file : int32:magic int32:version int32:stamp record* - The magic ident is different for the notes and the data files. The - magic ident is used to determine the endianness of the file, when - reading. The version is the same for both files and is derived - from gcc's version number. The stamp value is used to synchronize - note and data files and to synchronize merging within a data - file. It need not be an absolute time stamp, merely a ticker that - increments fast enough and cycles slow enough to distinguish - different compile/run/compile cycles. + A filename header may be used to provide a filename for the data in + a stream of data to support gcov in freestanding environments. This + header is used by the merge-stream subcommand of the gcov-tool. The + format of the filename header is + + filename-header : int32:magic int32:version string + + The magic ident is different for the notes and the data files as + well as the filename header. The magic ident is used to determine + the endianness of the file, when reading. The version is the same + for both files and is derived from gcc's version number. The stamp + value is used to synchronize note and data files and to synchronize + merging within a data file. It need not be an absolute time stamp, + merely a ticker that increments fast enough and cycles slow enough + to distinguish different compile/run/compile cycles. Although the ident and version are formally 32 bit numbers, they are derived from 4 character ASCII strings. The version number @@ -228,6 +235,7 @@ typedef uint64_t gcov_type_unsigned; /* File magic. Must not be palindromes. */ #define GCOV_DATA_MAGIC ((gcov_unsigned_t)0x67636461) /* "gcda" */ #define GCOV_NOTE_MAGIC ((gcov_unsigned_t)0x67636e6f) /* "gcno" */ +#define GCOV_FILENAME_MAGIC ((gcov_unsigned_t)0x6763666e) /* "gcfn" */ #include "version.h" @@ -340,22 +348,19 @@ struct gcov_summary /* Functions for reading and writing gcov files. In libgcov you can open the file for reading then writing. Elsewhere you can open the file either for reading or for writing. When reading a file you may - use the gcov_read_* functions, gcov_sync, gcov_position, & - gcov_error. When writing a file you may use the gcov_write - functions, gcov_seek & gcov_error. When a file is to be rewritten + use the gcov_read_* functions, gcov_sync, gcov_position, and + gcov_error. When writing a file you may use the gcov_write* + functions and gcov_error. When a file is to be rewritten you use the functions for reading, then gcov_rewrite then the functions for writing. Your file may become corrupted if you break these invariants. */ -#if !IN_LIBGCOV -GCOV_LINKAGE int gcov_open (const char */*name*/, int /*direction*/); -#endif - #if !IN_LIBGCOV || defined (IN_GCOV_TOOL) GCOV_LINKAGE int gcov_magic (gcov_unsigned_t, gcov_unsigned_t); #endif /* Available everywhere. */ +GCOV_LINKAGE int gcov_open (const char *, int) ATTRIBUTE_HIDDEN; GCOV_LINKAGE int gcov_close (void) ATTRIBUTE_HIDDEN; GCOV_LINKAGE gcov_unsigned_t gcov_read_unsigned (void) ATTRIBUTE_HIDDEN; GCOV_LINKAGE gcov_type gcov_read_counter (void) ATTRIBUTE_HIDDEN; diff --git a/gcc/gcov-tool.cc b/gcc/gcov-tool.cc index f4e42ae763c..ceb250143c8 100644 --- a/gcc/gcov-tool.cc +++ b/gcc/gcov-tool.cc @@ -40,12 +40,14 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see #endif #include -extern int gcov_profile_merge (struct gcov_info*, struct gcov_info*, int, int); +extern struct gcov_info *gcov_profile_merge (struct gcov_info*, + struct gcov_info*, int, int); +extern struct gcov_info *gcov_profile_merge_stream (const char *, int, int); extern int gcov_profile_overlap (struct gcov_info*, struct gcov_info*); extern int gcov_profile_normalize (struct gcov_info*, gcov_type); extern int gcov_profile_scale (struct gcov_info*, float, int, int); extern struct gcov_info* gcov_read_profile_dir (const char*, int); -extern void gcov_do_dump (struct gcov_info *, int); +extern void gcov_do_dump (struct gcov_info *, int, int); extern const char *gcov_get_filename (struct gcov_info *list); extern void gcov_set_verbose (void); @@ -123,7 +125,7 @@ gcov_output_files (const char *out, struct gcov_info *profile) fatal_error (input_location, "output file %s already exists in folder %s", filename, out); - gcov_do_dump (profile, 0); + gcov_do_dump (profile, 0, 0); ret = chdir (pwd); if (ret) @@ -141,26 +143,18 @@ profile_merge (const char *d1, const char *d2, const char *out, int w1, int w2) { struct gcov_info *d1_profile; struct gcov_info *d2_profile; - int ret; + struct gcov_info *merged_profile; d1_profile = gcov_read_profile_dir (d1, 0); - if (!d1_profile) - return 1; + d2_profile = gcov_read_profile_dir (d2, 0); - if (d2) - { - d2_profile = gcov_read_profile_dir (d2, 0); - if (!d2_profile) - return 1; - - /* The actual merge: we overwrite to d1_profile. */ - ret = gcov_profile_merge (d1_profile, d2_profile, w1, w2); - - if (ret) - return ret; - } + /* The actual merge: we overwrite to d1_profile. */ + merged_profile = gcov_profile_merge (d1_profile, d2_profile, w1, w2); - gcov_output_files (out, d1_profile); + if (merged_profile) + gcov_output_files (out, merged_profile); + else if (verbose) + fnotice (stdout, "no profile files were merged\n"); return 0; } @@ -236,6 +230,78 @@ do_merge (int argc, char **argv) return profile_merge (argv[optind], argv[optind+1], output_dir, w1, w2); } +/* Usage message for profile merge-stream. */ + +static void +print_merge_stream_usage_message (int error_p) +{ + FILE *file = error_p ? stderr : stdout; + + fnotice (file, " merge-stream [options] [] Merge coverage stream file (or stdin)\n" + " and coverage file contents\n"); + fnotice (file, " -v, --verbose Verbose mode\n"); + fnotice (file, " -w, --weight Set weights (float point values)\n"); +} + +static const struct option merge_stream_options[] = +{ + { "verbose", no_argument, NULL, 'v' }, + { "weight", required_argument, NULL, 'w' }, + { 0, 0, 0, 0 } +}; + +/* Print merge-stream usage and exit. */ + +static void ATTRIBUTE_NORETURN +merge_stream_usage (void) +{ + fnotice (stderr, "Merge-stream subcomand usage:"); + print_merge_stream_usage_message (true); + exit (FATAL_EXIT_CODE); +} + +/* Driver for profile merge-stream sub-command. */ + +static int +do_merge_stream (int argc, char **argv) +{ + int opt; + int w1 = 1, w2 = 1; + struct gcov_info *merged_profile; + + optind = 0; + while ((opt = getopt_long (argc, argv, "vw:", + merge_stream_options, NULL)) != -1) + { + switch (opt) + { + case 'v': + verbose = true; + gcov_set_verbose (); + break; + case 'w': + sscanf (optarg, "%d,%d", &w1, &w2); + if (w1 < 0 || w2 < 0) + fatal_error (input_location, "weights need to be non-negative"); + break; + default: + merge_stream_usage (); + } + } + + if (argc - optind > 1) + merge_stream_usage (); + + merged_profile = gcov_profile_merge_stream (argv[optind], w1, w2); + + if (merged_profile) + gcov_do_dump (merged_profile, 0, -1); + else if (verbose) + fnotice (stdout, "no profile files were merged\n"); + + return 0; +} + /* If N_VAL is no-zero, normalize the profile by setting the largest counter counter value to N_VAL and scale others counters proportionally. Otherwise, multiply the all counters by SCALE. */ @@ -512,6 +578,7 @@ print_usage (int error_p) fnotice (file, " -h, --help Print this help, then exit\n"); fnotice (file, " -v, --version Print version number, then exit\n"); print_merge_usage_message (error_p); + print_merge_stream_usage_message (error_p); print_rewrite_usage_message (error_p); print_overlap_usage_message (error_p); fnotice (file, "\nFor bug reporting instructions, please see:\n%s.\n", @@ -601,6 +668,8 @@ main (int argc, char **argv) if (!strcmp (sub_command, "merge")) return do_merge (argc - optind, argv + optind); + else if (!strcmp (sub_command, "merge-stream")) + return do_merge_stream (argc - optind, argv + optind); else if (!strcmp (sub_command, "rewrite")) return do_rewrite (argc - optind, argv + optind); else if (!strcmp (sub_command, "overlap")) diff --git a/gcc/testsuite/gcc.dg/gcov-info-to-gcda.c b/gcc/testsuite/gcc.dg/gcov-info-to-gcda.c index 4583360feef..ca9b3e831fe 100644 --- a/gcc/testsuite/gcc.dg/gcov-info-to-gcda.c +++ b/gcc/testsuite/gcc.dg/gcov-info-to-gcda.c @@ -17,16 +17,20 @@ __gcov_info_to_gcda (const struct gcov_info *__info, void *(*__allocate_fn) (unsigned, void *), void *__arg); +extern void +__gcov_filename_to_gcfn (const char *__filename, + void (*__dump_fn) (const void *, 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 unsigned counter_after_filename; + +static int check_zero; + +static int check_after_filename; static void dump (const void *d, unsigned n, void *arg) @@ -35,14 +39,30 @@ dump (const void *d, unsigned n, void *arg) assert (arg == &counter); if (*m == 0) + { + const unsigned *u = d; + assert (*u == 0x6763666e); + check_zero = 1; + } + else if (*m == counter_after_filename) { const unsigned *u = d; assert (*u == 0x67636461); + check_after_filename = 1; } *m += n; } +static void +filename (const char *f, void *arg) +{ + assert (arg == &counter); + assert (__builtin_strstr (f, "gcov-info-to-gcda.c") == 0); + __gcov_filename_to_gcfn (f, dump, arg); + counter_after_filename = counter; +} + static void * allocate (unsigned length, void *arg) { @@ -54,6 +74,8 @@ int main() { __asm__ volatile (".set my_info, .LPBX2"); __gcov_info_to_gcda (my_info, filename, dump, allocate, &counter); - assert (counter > 4); + assert (counter > 8); + assert (check_zero); + assert (check_after_filename); return 0; } diff --git a/libgcc/gcov.h b/libgcc/gcov.h index cea93023920..cdd4206f625 100644 --- a/libgcc/gcov.h +++ b/libgcc/gcov.h @@ -43,7 +43,8 @@ extern void __gcov_dump (void); 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. */ + functions. It is recommended to use the __gcov_filename_to_gcfn() + in the filename callback function. */ extern void __gcov_info_to_gcda (const struct gcov_info *__info, @@ -52,4 +53,18 @@ __gcov_info_to_gcda (const struct gcov_info *__info, void *(*__allocate_fn) (unsigned, void *), void *__arg); +/* Convert the FILENAME to a gcfn data stream. 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 gcfn data stream. + The ARG parameter is a user-provided argument passed as the last + argument to the DUMP_FN callback function. This function is intended + to be used by the filename callback of __gcov_info_to_gcda(). The gcfn + data stream is used by the merge-stream subcommand of the gcov-tool to + get the filename associated with a gcda data stream. */ + +extern void +__gcov_filename_to_gcfn (const char *__filename, + void (*__dump_fn) (const void *, unsigned, void *), + void *__arg); + #endif /* GCC_GCOV_H */ diff --git a/libgcc/libgcov-driver-system.c b/libgcc/libgcov-driver-system.c index eef6e3cbda1..ac405c38e3a 100644 --- a/libgcc/libgcov-driver-system.c +++ b/libgcc/libgcov-driver-system.c @@ -261,7 +261,8 @@ allocate_filename_struct (struct gcov_filename *gf) static int gcov_exit_open_gcda_file (struct gcov_info *gi_ptr, - struct gcov_filename *gf) + struct gcov_filename *gf, + int mode) { int append_slash = 0; const char *fname = gi_ptr->filename; @@ -309,7 +310,7 @@ gcov_exit_open_gcda_file (struct gcov_info *gi_ptr, gf->filename = replace_filename_variables (gf->filename); - if (!gcov_open (gf->filename)) + if (!gcov_open (gf->filename, mode)) { /* Open failed likely due to missed directory. Create directory and retry to open file. */ @@ -318,7 +319,7 @@ gcov_exit_open_gcda_file (struct gcov_info *gi_ptr, fprintf (stderr, "profiling:%s:Skip\n", gf->filename); return -1; } - if (!gcov_open (gf->filename)) + if (!gcov_open (gf->filename, mode)) { fprintf (stderr, "profiling:%s:Cannot open\n", gf->filename); return -1; diff --git a/libgcc/libgcov-driver.c b/libgcc/libgcov-driver.c index 7e52c5676e5..d4517d269eb 100644 --- a/libgcc/libgcov-driver.c +++ b/libgcc/libgcov-driver.c @@ -410,6 +410,23 @@ dump_counter (gcov_type counter, dump_unsigned (0, dump_fn, arg); } +/* Dump the STRING using the DUMP handler called with ARG. */ + +static inline void +dump_string (const char *string, + void (*dump_fn) (const void *, unsigned, void *), + void *arg) +{ + unsigned length = 0; + + if (string) + length = strlen (string) + 1; + + dump_unsigned (length, dump_fn, arg); + if (string) + (*dump_fn) (string, length, arg); +} + #define MAX(X,Y) ((X) > (Y) ? (X) : (Y)) /* Store all TOP N counters where each has a dynamic length. */ @@ -595,14 +612,14 @@ write_one_data (const struct gcov_info *gi_ptr, static void dump_one_gcov (struct gcov_info *gi_ptr, struct gcov_filename *gf, unsigned run_counted ATTRIBUTE_UNUSED, - gcov_type run_max ATTRIBUTE_UNUSED) + gcov_type run_max ATTRIBUTE_UNUSED, int mode) { struct gcov_summary summary = {}; int error; gcov_unsigned_t tag; fn_buffer = 0; - error = gcov_exit_open_gcda_file (gi_ptr, gf); + error = gcov_exit_open_gcda_file (gi_ptr, gf, mode); if (error == -1) return; @@ -649,13 +666,13 @@ read_fatal:; /* Dump all the coverage counts for the program. It first computes program summary and then traverses gcov_list list and dumps the gcov_info - objects one by one. */ + objects one by one. Use MODE to open files. */ #if !IN_GCOV_TOOL static #endif void -gcov_do_dump (struct gcov_info *list, int run_counted) +gcov_do_dump (struct gcov_info *list, int run_counted, int mode) { struct gcov_info *gi_ptr; struct gcov_filename gf; @@ -678,7 +695,7 @@ gcov_do_dump (struct gcov_info *list, int run_counted) /* Now merge each file. */ for (gi_ptr = list; gi_ptr; gi_ptr = gi_ptr->next) { - dump_one_gcov (gi_ptr, &gf, run_counted, run_max); + dump_one_gcov (gi_ptr, &gf, run_counted, run_max, mode); free (gf.filename); } @@ -701,7 +718,7 @@ __gcov_dump_one (struct gcov_root *root) if (root->dumped) return; - gcov_do_dump (root->list, root->run_counted); + gcov_do_dump (root->list, root->run_counted, 0); root->dumped = 1; root->run_counted = 1; @@ -768,7 +785,7 @@ __gcov_init (struct gcov_info *info) #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. */ + freestanding environments which do not support the C library file I/O. */ void __gcov_info_to_gcda (const struct gcov_info *gi_ptr, @@ -780,4 +797,17 @@ __gcov_info_to_gcda (const struct gcov_info *gi_ptr, (*filename_fn) (gi_ptr->filename, arg); write_one_data (gi_ptr, NULL, dump_fn, allocate_fn, arg); } + +/* Convert the filename to a gcfn data stream. It is intended for + freestanding environments which do not support the C library file I/O. */ + +void +__gcov_filename_to_gcfn (const char *filename, + void (*dump_fn) (const void *, unsigned, void *), + void *arg) +{ + dump_unsigned (GCOV_FILENAME_MAGIC, dump_fn, arg); + dump_unsigned (GCOV_VERSION, dump_fn, arg); + dump_string (filename, dump_fn, arg); +} #endif /* NEED_L_GCOV_INFO_TO_GCDA */ diff --git a/libgcc/libgcov-util.c b/libgcc/libgcov-util.c index ba7fb924b53..ba4670b3c0d 100644 --- a/libgcc/libgcov-util.c +++ b/libgcc/libgcov-util.c @@ -268,17 +268,10 @@ read_gcda_file (const char *filename) k_ctrs_mask[i] = 0; k_ctrs_types = 0; - if (!gcov_open (filename)) - { - fnotice (stderr, "%s:cannot open\n", filename); - return NULL; - } - /* Read magic. */ if (!gcov_magic (gcov_read_unsigned (), GCOV_DATA_MAGIC)) { fnotice (stderr, "%s:not a gcov data file\n", filename); - gcov_close (); return NULL; } @@ -287,7 +280,6 @@ read_gcda_file (const char *filename) if (version != GCOV_VERSION) { fnotice (stderr, "%s:incorrect gcov version %d vs %d \n", filename, version, GCOV_VERSION); - gcov_close (); return NULL; } @@ -296,16 +288,14 @@ read_gcda_file (const char *filename) sizeof (struct gcov_ctr_info) * GCOV_COUNTERS, 1); obj_info->version = version; + obj_info->filename = filename; obstack_init (&fn_info); num_fn_info = 0; curr_fn_info = 0; - { - size_t len = strlen (filename) + 1; - char *str_dup = (char*) xmalloc (len); - memcpy (str_dup, filename, len); - obj_info->filename = str_dup; - } + /* Prepend to global gcov info list. */ + obj_info->next = gcov_info_head; + gcov_info_head = obj_info; /* Read stamp. */ obj_info->stamp = gcov_read_unsigned (); @@ -381,7 +371,6 @@ read_gcda_file (const char *filename) } read_gcda_finalize (obj_info); - gcov_close (); return obj_info; } @@ -395,9 +384,8 @@ ftw_read_file (const char *filename, const struct stat *status ATTRIBUTE_UNUSED, int type) { - int filename_len; - int suffix_len; - struct gcov_info *obj_info; + size_t filename_len; + size_t suffix_len; /* Only read regular files. */ if (type != FTW_F) @@ -415,12 +403,14 @@ ftw_read_file (const char *filename, if (verbose) fnotice (stderr, "reading file: %s\n", filename); - obj_info = read_gcda_file (filename); - if (!obj_info) - return 0; + if (!gcov_open (filename, 1)) + { + fnotice (stderr, "%s:cannot open:%s\n", filename, xstrerror (errno)); + return 0; + } - obj_info->next = gcov_info_head; - gcov_info_head = obj_info; + (void)read_gcda_file (xstrdup (filename)); + gcov_close (); return 0; } @@ -674,16 +664,16 @@ find_match_gcov_info (struct gcov_info **array, int size, } /* Merge the list of gcov_info objects from SRC_PROFILE to TGT_PROFILE. - Return 0 on success: without mismatch. - Reutrn 1 on error. */ + Return the list of merged gcov_info objects. Return NULL if the list is + empty. */ -int +struct gcov_info * gcov_profile_merge (struct gcov_info *tgt_profile, struct gcov_info *src_profile, int w1, int w2) { struct gcov_info *gi_ptr; struct gcov_info **tgt_infos; - struct gcov_info *tgt_tail; + struct gcov_info **tgt_tail; struct gcov_info **in_src_not_tgt; unsigned tgt_cnt = 0, src_cnt = 0; unsigned unmatch_info_cnt = 0; @@ -703,7 +693,10 @@ gcov_profile_merge (struct gcov_info *tgt_profile, struct gcov_info *src_profile for (gi_ptr = tgt_profile, i = 0; gi_ptr; gi_ptr = gi_ptr->next, i++) tgt_infos[i] = gi_ptr; - tgt_tail = tgt_infos[tgt_cnt - 1]; + if (tgt_cnt) + tgt_tail = &tgt_infos[tgt_cnt - 1]->next; + else + tgt_tail = &tgt_profile; /* First pass on tgt_profile, we multiply w1 to all counters. */ if (w1 > 1) @@ -732,14 +725,109 @@ gcov_profile_merge (struct gcov_info *tgt_profile, struct gcov_info *src_profile gi_ptr = in_src_not_tgt[i]; gcov_merge (gi_ptr, gi_ptr, w2 - 1); gi_ptr->next = NULL; - tgt_tail->next = gi_ptr; - tgt_tail = gi_ptr; + *tgt_tail = gi_ptr; + tgt_tail = &gi_ptr->next; } free (in_src_not_tgt); free (tgt_infos); - return 0; + return tgt_profile; +} + +/* Deserialize gcov_info objects and associated filenames from the file + specified by FILENAME to create a profile list. When FILENAME is NULL, read + from stdin. Return the profile list. */ + +struct gcov_info * +deserialize_profiles (const char *filename) +{ + read_profile_dir_init (); + + while (true) + { + unsigned version; + const char *filename_of_info; + struct gcov_info *obj_info; + + if (!gcov_magic (gcov_read_unsigned (), GCOV_FILENAME_MAGIC)) + { + if (gcov_is_error () != 2) + fnotice (stderr, "%s:not a gcfn stream\n", filename); + break; + } + + version = gcov_read_unsigned (); + if (version != GCOV_VERSION) + { + fnotice (stderr, "%s:incorrect gcov version %d vs %d \n", + filename, version, GCOV_VERSION); + break; + } + + filename_of_info = gcov_read_string (); + if (!filename_of_info) + { + fnotice (stderr, "%s:no filename in gcfn stream\n", + filename); + break; + } + + obj_info = read_gcda_file (filename); + if (!obj_info) + break; + + obj_info->filename = filename_of_info; + } + + return gcov_info_head; +} + +/* For each profile of the list specified by SRC_PROFILE, read the GCDA file of + the profile. If a GCDA file exists, add the profile to a list. Return the + profile list. */ + +struct gcov_info * +get_target_profiles_for_merge (struct gcov_info *src_profile) +{ + struct gcov_info *gi_ptr; + + read_profile_dir_init (); + + for (gi_ptr = src_profile; gi_ptr; gi_ptr = gi_ptr->next) + if (gcov_open (gi_ptr->filename, 1)) + { + (void)read_gcda_file (gi_ptr->filename); + gcov_close (); + } + + return gcov_info_head; +} + +/* Deserialize gcov_info objects and associated filenames from the file + specified by FILENAME to create a source profile list. When FILENAME is + NULL, read from stdin. Use the filenames of the source profile list to get + a target profile list. Merge the source profile list into the target + profile list using weights W1 and W2. Return the list of merged gcov_info + objects. Return NULL if the list is empty. */ + +struct gcov_info * +gcov_profile_merge_stream (const char *filename, int w1, int w2) +{ + struct gcov_info *tgt_profile; + struct gcov_info *src_profile; + + if (!gcov_open (filename, 1)) + { + fnotice (stderr, "%s:cannot open:%s\n", filename, xstrerror (errno)); + return NULL; + } + + src_profile = deserialize_profiles (filename ? filename : ""); + gcov_close (); + tgt_profile = get_target_profiles_for_merge (src_profile); + + return gcov_profile_merge (tgt_profile, src_profile, w1, w2); } typedef gcov_type (*counter_op_fn) (gcov_type, void*, void*); diff --git a/libgcc/libgcov.h b/libgcc/libgcov.h index 40e845ce3ea..487bd1464cd 100644 --- a/libgcc/libgcov.h +++ b/libgcc/libgcov.h @@ -115,7 +115,6 @@ typedef unsigned gcov_type_unsigned __attribute__ ((mode (QI))); #define gcov_open __gcov_open #define gcov_close __gcov_close #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 @@ -343,11 +342,9 @@ extern int __gcov_execve (const char *, char *const [], char *const []) ATTRIBUTE_HIDDEN; /* Functions that only available in libgcov. */ -GCOV_LINKAGE int gcov_open (const char */*name*/) ATTRIBUTE_HIDDEN; GCOV_LINKAGE void gcov_write_summary (gcov_unsigned_t /*tag*/, const struct gcov_summary *) ATTRIBUTE_HIDDEN; -GCOV_LINKAGE void gcov_seek (gcov_position_t /*position*/) ATTRIBUTE_HIDDEN; GCOV_LINKAGE void gcov_rewrite (void) ATTRIBUTE_HIDDEN; /* "Counts" stored in gcda files can be a real counter value, or -- 2.34.1