gcov: Add section for freestanding environments

gcc/

	* doc/gcov.texi (Profiling and Test Coverage in Freestanding
	Environments): New section.
This commit is contained in:
Sebastian Huber 2022-04-08 17:16:09 +02:00
parent 92475ea84f
commit 9ae8b993cd
1 changed files with 389 additions and 0 deletions

View File

@ -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 <gcov.h>}. 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 <stdio.h>
/* 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 <gcov.h>
#include <stdio.h>
#include <stdlib.h>
/* 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-0.txt 2>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-1.txt 2>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.