GCOV(1)

๐Ÿ“ฆ
version 14-20240412 ยท 4 versions
, in ubuntu noble

๐Ÿ’– GCOV(1) ๐Ÿ’–

GNU manual page, bestie ๐ŸŽ€ Let's talk about gcov, the coverage tool that's lowkey the GOAT. โœจ

โœจ NAME โœจ

gcov - coverage testing tool ๐Ÿงช

โœจ SYNOPSIS โœจ

gcov [-v|--version] [-h|--help]
     [-a|--all-blocks]
     [-b|--branch-probabilities]
     [-c|--branch-counts]
     [-g|--conditions]
     [-d|--display-progress]
     [-f|--function-summaries]
     [-j|--json-format]
     [-H|--human-readable]
     [-k|--use-colors]
     [-l|--long-file-names]
     [-m|--demangled-names]
     [-n|--no-output]
     [-o|--object-directory directory|file]
     [-p|--preserve-paths]
     [-q|--use-hotness-colors]
     [-r|--relative-only]
     [-s|--source-prefix directory]
     [-t|--stdout]
     [-u|--unconditional-branches]
     [-x|--hash-filenames]
     files

โœจ DESCRIPTION โœจ

gcov is a test coverage program ๐Ÿ”. Use it with GCC to analyze your programs so you can write more efficient, faster code, and find the parts of your program nobody has tested yet. It also works as a profiling tool, so you can figure out where your optimization work will actually pay off. If you pair it with the other profiler, gprof, you can see which parts of your code are eating the most computing time ๐Ÿ’….

Profiling tools are how you check your code's performance ๐Ÿ“Š. With a profiler like gcov or gprof, you can find out basic performance stats, like:

  • how often each line of code executes
  • what lines of code are actually executed
  • how much computing time each section of code uses

Once you know all of that about how your code works when compiled, you can look at each module and decide which ones need optimizing. gcov helps you figure out where to put your optimization energy ๐ŸŽฏ.

Devs also use coverage testing alongside testsuites to make sure software is actually good enough for a release ๐Ÿš€. A testsuite checks that a program works as expected, while a coverage program checks how much of the program the testsuite actually exercises. Then devs can see what kinds of test cases to add to the testsuites for better testing and a better final product ๐Ÿ’–.

Compile without optimization if you're planning to use gcov ๐Ÿ™…โ€โ™€๏ธ! Optimization can combine lines of code into one function, which may not give you enough info to find hot spots' where the code is burning a ton of computer time ๐Ÿ”ฅ. Also, gcov collects stats by line (at the lowest resolution), so it works best with a style that puts only one statement on each line. If you use complicated macros that expand into loops or other control structures, the stats get less helpful---they only report on the line where the macro call appears ๐Ÿ™ˆ. If your complex macros act like functions, swap them out for inline functions to fix that.

gcov creates a logfile called sourcefile.gcov that shows how many times each line of a source file sourcefile.c has run ๐Ÿ“. You can use these logfiles with gprof to fine-tune your program's performance, since gprof gives timing info you can pair with what gcov tells you.

gcov only works on code compiled with GCC ๐Ÿšซ. It is not compatible with any other profiling or test coverage mechanism, so don't try to mix it up with something else!

โœจ OPTIONS โœจ

Here's the full options lineup ๐Ÿ’…:

  • -a, --all-blocks โ€” Gives you execution counts for every single basic block ๐Ÿงฑ, not just the main blocks of each line. Normally gcov only reports counts for the main blocks of a line, so this one is how you spot blocks inside a single line that never get executed.
  • -b, --branch-probabilities โ€” Writes branch frequencies to the output file and branch summary info to standard output ๐ŸŒฟ, so you can see how often each branch in your program was taken. Unconditional branches stay hidden unless you also pass -u.
  • -c, --branch-counts โ€” Shows branch frequencies as the number of branches taken, instead of the percentage of branches taken ๐Ÿ”ข.
  • -g, --conditions โ€” Writes condition coverage to the output file and condition summary info to standard output ๐Ÿ’œ. This lets you see whether each condition in your program at least once had an independent effect on the outcome of the boolean expression (modified condition/decision coverage). You need to compile the source with -fcondition-coverage for this one ๐Ÿ™.
  • -d, --display-progress โ€” Shows the progress on standard output โณ.
  • -f, --function-summaries โ€” Adds summaries for each function on top of the file-level summary ๐Ÿง.
  • -h, --help โ€” Shows help about using gcov on standard output, then exits without doing any more processing ๐Ÿ†˜.
  • -j, --json-format โ€” Outputs the gcov file in an easy-to-parse JSON intermediate format ๐Ÿงพ that doesn't need the source code to generate. The JSON file is gzip-compressed, and the files have the .gcov.json.gz extension ๐ŸŽ.

๐Ÿงพ JSON structure, bestie

The JSON structure goes like this ๐Ÿซถ:

{
  "current_working_directory": "foo/bar",
  "data_file": "a.out",
  "format_version": "2",
  "gcc_version": "11.1.1 20210510"
  "files": ["$file"]
}

The root element's fields work like this:

  • current_working_directory: the working directory where a compilation unit was compiled ๐Ÿ“
  • data_file: name of the data file (GCDA) ๐Ÿ—‚๏ธ
  • format_version: semantic version of the format
    • Changes in version 2:
      • calls: information about function calls is added โ˜Ž๏ธ
  • gcc_version: version of the GCC compiler ๐Ÿ› ๏ธ

Each file has the following form ๐Ÿ“„:

{
  "file": "a.c",
  "functions": ["$function"],
  "lines": ["$line"]
}

The file element's fields have these meanings:

  • file_name: name of the source file

Each function has the following form ๐ŸŽญ:

{
  "blocks": 2,
  "blocks_executed": 2,
  "demangled_name": "foo",
  "end_column": 1,
  "end_line": 4,
  "execution_count": 1,
  "name": "foo",
  "start_column": 5,
  "start_line": 1
}

The function element's fields have these meanings:

  • blocks: number of blocks that are in the function
  • blocks_executed: number of executed blocks of the function
  • demangled_name: demangled name of the function
  • end_column: column in the source file where the function ends
  • end_line: line in the source file where the function ends
  • execution_count: number of executions of the function
  • name: name of the function
  • start_column: column in the source file where the function begins
  • start_line: line in the source file where the function begins

Heads up: line numbers and column numbers both start from 1 ๐Ÿ”ข. In the current implementation, start_line and start_column don't include any template parameters or the leading return type, but that's likely to get fixed in the future ๐Ÿ”ฎ.

Each line has the following form ๐Ÿ“:

{
  "block_ids": ["$block_id"],
  "branches": ["$branch"],
  "calls": ["$call"],
  "count": 2,
  "conditions": ["$condition"],
  "line_number": 15,
  "unexecuted_block": false,
  "function_name": "foo",
}

Branches and calls only show up with the -b option ๐ŸŒฟ. The line element's fields have these meanings:

  • block_ids: IDs of basic blocks that belong to the line
  • count: number of executions of the line
  • line_number: line number
  • unexecuted_block: flag for whether the line contains an unexecuted block (not all statements on the line are executed) ๐Ÿšฉ
  • function_name: name of a function this line belongs to (for a line with inlined statements, this can be left unset)

Each branch has the following form ๐ŸŒฑ:

{
  "count": 11,
  "destination_block_id": 17,
  "fallthrough": true,
  "source_block_id": 13,
  "throw": false
}

The branch element's fields have these meanings:

  • count: number of executions of the branch
  • fallthrough: true when the branch is a fall through branch
  • throw: true when the branch is an exceptional branch โšก
  • isource_block_id: ID of the basic block where this branch happens
  • destination_block_id: ID of the basic block this branch jumps to

Each call has the following form โ˜Ž๏ธ:

{
  "destination_block_id": 1,
  "returned": 11,
  "source_block_id": 13
}

The call element's fields have these meanings:

  • returned: number of times a function call returned (call count is equal to line::count)
  • isource_block_id: ID of the basic block where this call happens
  • destination_block_id: ID of the basic block this call continues after returning to

Each condition has the following form ๐Ÿงฉ:

{
  "count": 4,
  "covered": 2,
  "not_covered_false": [],
  "not_covered_true": [0, 1],
}

The condition element's fields have these meanings:

  • count: number of condition outcomes in this expression
  • covered: number of covered condition outcomes in this expression
  • not_covered_true: terms, by index, not seen as true in this expression
  • not_covered_false: terms, by index, not seen as false in this expression
  • -H, --human-readable โ€” Shows counts in a human-readable format, like 24.6k ๐Ÿ‘€.
  • -k, --use-colors โ€” Colors the lines of code that have zero coverage ๐ŸŽจ. Red is for non-exceptional lines and cyan is for exceptional ones. The same colors get used for basic blocks when you pass -a.
  • -l, --long-file-names โ€” okay so this one makes long file names for included source files ๐ŸŽ€ Say the header x.h has code and gets included in a.c. Running gcov on a.c gives you a.c##x.h.gcov instead of x.h.gcov โœจ It's super useful if x.h is included in a bunch of source files and you want to see each one's individual contributions ๐Ÿ’… With -p too, both the including and included file names come out as complete path names ๐Ÿ—บ๏ธ
  • -m, --demangled-names โ€” displays demangled function names in the output ๐Ÿฆ‹ The default is to show mangled function names, so this is the flag for the readable version ๐Ÿ’–
  • -n, --no-output โ€” don't create the gcov output file at all ๐Ÿšซ
  • -o directory|file, --object-directory directory, --object-file file โ€” this is where you point gcov at the directory with your gcov data files, or at the object path name ๐Ÿ—‚๏ธ The .gcno and .gcda data files get searched for using this option. If you give it a directory, the data files live in there and are named after the input file name, minus its extension. If you give it a file, the data files are named after that file, minus its extension ๐Ÿงš
  • -p, --preserve-paths โ€” keeps the complete path info in the names of generated .gcov files ๐Ÿ›ค๏ธ Without it, only the filename part gets used. With it, all directories get used: / characters turn into #, . directory components get removed, and the unremoveable .. components get renamed to ^. Super handy if your sourcefiles are spread across several different directories ๐ŸŒธ
  • -q, --use-hotness-colors โ€” emits perf-like colored output for hot lines ๐Ÿ”ฅ A legend of the color scale gets printed at the very beginning of the output file ๐ŸŒˆ
  • -r, --relative-only โ€” only outputs info about source files with a relative pathname (after source prefix elision) ๐Ÿ™„ Absolute paths are usually system header files, and coverage of inline functions in those is normally uninteresting.
  • -s directory, --source-prefix directory โ€” a prefix for source file names that gets removed when generating the output coverage files ๐Ÿงน It's useful when you build in a separate directory and don't want the path to the source directory messing with your output file names. Heads up: this prefix detection gets applied before deciding whether the source file is absolute โš ๏ธ
  • -t, --stdout โ€” sends the output to standard output instead of output files ๐Ÿ“บ
  • -u, --unconditional-branches โ€” when branch probabilities are given, this includes the ones for unconditional branches ๐ŸŒ€ Unconditional branches are normally not interesting, so you probably won't need this one ๐Ÿ™ˆ
  • -v, --version โ€” displays the gcov version number on standard output, then exits without doing any further processing ๐Ÿ‘‘
  • -w, --verbose โ€” prints verbose info related to basic blocks and arcs ๐Ÿ”
  • -x, --hash-filenames โ€” when you're using --preserve-paths, gcov builds output filenames from the full pathname of the source files, and those can get so long they overflow filesystem limits ๐Ÿ˜ญ This option makes names like source-file##md5.gcov, where source-file is the final filename part and the md5 bit is calculated from the full mangled name that would've been used otherwise ๐Ÿซถ It's the alternative to --preserve-paths for systems that have a filesystem limit.

gcov should be run with the current directory the same as the one you were in when you invoked the compiler, bestie ๐Ÿ“ Otherwise it can't find your source files. gcov produces files called mangledname.gcov in the current directory, and those contain the coverage info for the source file they match up with. You get one .gcov file for each source (or header) file that has code and was compiled to make the data files ๐Ÿ’œ The mangledname part is usually just the source file name, but it can get more complicated if the -l or -p options are given. Check those options for the deets.

If you invoke gcov with multiple input files, the contributions from each input file get summed up โž• Typically you'd invoke it with the same list of files you used for the final link of your executable ๐Ÿ”—

The .gcov files contain the : separated fields along with your program's source code ๐Ÿ’… The format is:

<execution_count>:<line_number>:<source line text>

Additional block information may follow each line, if you ask for it with a command line option ๐Ÿงฉ The execution_count is - for lines with no code. Unexecuted lines get marked ##### or =====, depending on whether they're reachable by non-exceptional paths or only exceptional paths (like C++ exception handlers, respectively) ๐Ÿ’€ With the -a option, unexecuted blocks get marked $$$$$ or %%%%%, depending on whether a basic block is reachable via non-exceptional or exceptional paths ๐Ÿ™Œ Executed basic blocks with a statement that has a zero execution_count end with a * character, and they get colored magenta with the -k option ๐Ÿ’– This functionality is not supported in Ada ๐Ÿ™…โ€โ™€๏ธ

Heads up, bestie! GCC can completely remove the bodies of functions that aren't needed, like if they're inlined everywhere. Those functions get marked with -, which can be super confusing ๐Ÿ˜ญ Use the -fkeep-inline-functions and -fkeep-static-functions options to keep these functions and let gcov properly show their execution_count.

Some lines of info at the start have a line_number of zero. These preamble lines look like this:

-:0:<tag>:<value>
The ordering and number of these preamble lines will be augmented as gcov development progresses, so do not rely on them staying the same! Use the tag to locate a particular preamble line ๐Ÿ™

The additional block information looks like this:

<tag> <information>

The information is human readable, but it's designed to be simple enough for machine parsing too ๐Ÿค–

When printing percentages, 0% and 100% only get printed when the values are exactly 0% and 100% respectively ๐Ÿ’ฏ Other values that would normally round to 0% or 100% get printed as the nearest non-boundary value instead ๐Ÿงฎ


When using gcov, you must first compile your program with a special GCC option, --coverage ๐ŸŽ€ This tells the compiler to generate additional info gcov needs (basically a flow graph of the program), and it also adds extra code to the object files for generating the profiling info gcov needs. These additional files get placed in the directory where the object file is located ๐Ÿ“

Running the program causes profile output to be generated. For each source file compiled with -fprofile-arcs, an accompanying .gcda file gets placed in the object file directory ๐ŸŒŸ

Running gcov with your program's source file names as arguments will now produce a listing of the code, along with how often each line executed ๐Ÿ“œ For example, if your program is called tmp.cpp, this is what you see when you use the basic gcov facility:

$ g++ --coverage tmp.cpp -c
$ g++ --coverage tmp.o
$ a.out
$ gcov tmp.cpp -m
File 'tmp.cpp'
Lines executed:92.86% of 14
Creating 'tmp.cpp.gcov'

The file tmp.cpp.gcov contains the output from gcov. Here is a sample ๐Ÿ‘€

                -:    0:Source:tmp.cpp
                -:    0:Working directory:/home/gcc/testcase
                -:    0:Graph:tmp.gcno
                -:    0:Data:tmp.gcda
                -:    0:Runs:1
                -:    0:Programs:1
                -:    1:#include <stdio.h>
                -:    2:
                -:    3:template<class T>
                -:    4:class Foo
                -:    5:{
                -:    6:  public:
               1*:    7:  Foo(): b (1000) {}
        ------------------
        Foo<char>::Foo():
            #####:    7:  Foo(): b (1000) {}
        ------------------
        Foo<int>::Foo():
                1:    7:  Foo(): b (1000) {}
        ------------------
               2*:    8:  void inc () { b++; }
        ------------------
        Foo<char>::inc():
            #####:    8:  void inc () { b++; }
        ------------------
        Foo<int>::inc():
                2:    8:  void inc () { b++; }
        ------------------
                -:    9:
                -:   10:  private:
                -:   11:  int b;
                -:   12:};
                -:   13:
                -:   14:template class Foo<int>;
                -:   15:template class Foo<char>;
                -:   16:
                -:   17:int
                1:   18:main (void)
                -:   19:{
                -:   20:  int i, total;
                1:   21:  Foo<int> counter;
                -:   22:
                1:   23:  counter.inc();
                1:   24:  counter.inc();
                1:   25:  total = 0;
                -:   26:
               11:   27:  for (i = 0; i < 10; i++)
               10:   28:    total += i;
                -:   29:
               1*:   30:  int v = total > 100 ? 1 : 2;
                -:   31:
                1:   32:  if (total != 45)
            #####:   33:    printf ("Failure\n");
                -:   34:  else
                1:   35:    printf ("Success\n");
                1:   36:  return 0;
                -:   37:}

Note that line 7 is shown in the report multiple times ๐Ÿง The first occurrence shows the total number of executions of the line, and the next two belong to the instances of the Foo class constructors. As you can also see, line 30 contains some unexecuted basic blocks, so its execution count has an asterisk symbol โญ

When you use the -a option, you get individual block counts, and the output looks like this:

                       -:    0:Source:tmp.cpp
                       -:    0:Working directory:/home/gcc/testcase
                       -:    0:Graph:tmp.gcno
                       -:    0:Data:tmp.gcda
                       -:    0:Runs:1
                       -:    0:Programs:1
                       -:    1:#include <stdio.h>
                       -:    2:
                       -:    3:template<class T>
                       -:    4:class Foo
                       -:    5:{
                       -:    6:  public:
                      1*:    7:  Foo(): b (1000) {}
               ------------------
               Foo<char>::Foo():
                   #####:    7:  Foo(): b (1000) {}
               ------------------
               Foo<int>::Foo():
                       1:    7:  Foo(): b (1000) {}
               ------------------
                      2*:    8:  void inc () { b++; }
               ------------------
               Foo<char>::inc():
                   #####:    8:  void inc () { b++; }
               ------------------
               Foo<int>::inc():
                       2:    8:  void inc () { b++; }
               ------------------
                       -:    9:
                       -:   10:  private:
                       -:   11:  int b;
                       -:   12:};
                       -:   13:
                       -:   14:template class Foo<int>;
                       -:   15:template class Foo<char>;
                       -:   16:
                       -:   17:int
                       1:   18:main (void)
                       -:   19:{
                       -:   20:  int i, total;
                       1:   21:  Foo<int> counter;
                       1:   21-block  0
                       -:   22:
                       1:   23:  counter.inc();
                       1:   23-block  0
                       1:   24:  counter.inc();
                       1:   24-block  0
                       1:   25:  total = 0;
                       -:   26:
                      11:   27:  for (i = 0; i < 10; i++)
                       1:   27-block  0
                      11:   27-block  1
                      10:   28:    total += i;
                      10:   28-block  0
                       -:   29:
                      1*:   30:  int v = total > 100 ? 1 : 2;
                       1:   30-block  0
                   %%%%%:   30-block  1
                       1:   30-block  2
                       -:   31:
                       1:   32:  if (total != 45)
                       1:   32-block  0
                   #####:   33:    printf ("Failure\n");
                   %%%%%:   33-block  0
                       -:   34:  else
                       1:   35:    printf ("Success\n");
                       1:   35-block  0
                       1:   36:  return 0;
                       1:   36-block  0
                       -:   37:}

Okay so in this mode ๐Ÿ’… each basic block only gets shown on one line, and that's the last line of the block ๐Ÿซถ. A multi-line block only adds to the execution count of that last line, and the other lines won't be shown to contain code unless some previous blocks end on those lines. The total execution count of a line is shown, and the lines after it show the counts for the individual blocks that end on that line โœจ. After each block, the branch and call counts of that block get shown too, if you pass -b ๐Ÿ”ฅ.

Because of the way GCC instruments calls, a call count can pop up after a line with no individual blocks ๐Ÿ™ˆ. As you can see, line 33 has a basic block that never got executed ๐Ÿ’€.

When you slap on the -b option, your output looks like this:

                       -:    0:Source:tmp.cpp
                       -:    0:Working directory:/home/gcc/testcase
                       -:    0:Graph:tmp.gcno
                       -:    0:Data:tmp.gcda
                       -:    0:Runs:1
                       -:    0:Programs:1
                       -:    1:#include <stdio.h>
                       -:    2:
                       -:    3:template<class T>
                       -:    4:class Foo
                       -:    5:{
                       -:    6:  public:
                      1*:    7:  Foo(): b (1000) {}
               ------------------
               Foo<char>::Foo():
               function Foo<char>::Foo() called 0 returned 0% blocks executed 0%
                   #####:    7:  Foo(): b (1000) {}
               ------------------
               Foo<int>::Foo():
               function Foo<int>::Foo() called 1 returned 100% blocks executed 100%
                       1:    7:  Foo(): b (1000) {}
               ------------------
                      2*:    8:  void inc () { b++; }
               ------------------
               Foo<char>::inc():
               function Foo<char>::inc() called 0 returned 0% blocks executed 0%
                   #####:    8:  void inc () { b++; }
               ------------------
               Foo<int>::inc():
               function Foo<int>::inc() called 2 returned 100% blocks executed 100%
                       2:    8:  void inc () { b++; }
               ------------------
                       -:    9:
                       -:   10:  private:
                       -:   11:  int b;
                       -:   12:};
                       -:   13:
                       -:   14:template class Foo<int>;
                       -:   15:template class Foo<char>;
                       -:   16:
                       -:   17:int
               function main called 1 returned 100% blocks executed 81%
                       1:   18:main (void)
                       -:   19:{
                       -:   20:  int i, total;
                       1:   21:  Foo<int> counter;
               call    0 returned 100%
               branch  1 taken 100% (fallthrough)
               branch  2 taken 0% (throw)
                       -:   22:
                       1:   23:  counter.inc();
               call    0 returned 100%
               branch  1 taken 100% (fallthrough)
               branch  2 taken 0% (throw)
                       1:   24:  counter.inc();
               call    0 returned 100%
               branch  1 taken 100% (fallthrough)
               branch  2 taken 0% (throw)
                       1:   25:  total = 0;
                       -:   26:
                      11:   27:  for (i = 0; i < 10; i++)
               branch  0 taken 91% (fallthrough)
               branch  1 taken 9%
                      10:   28:    total += i;
                       -:   29:
                      1*:   30:  int v = total > 100 ? 1 : 2;
               branch  0 taken 0% (fallthrough)
               branch  1 taken 100%
                       -:   31:
                       1:   32:  if (total != 45)
               branch  0 taken 0% (fallthrough)
               branch  1 taken 100%
                   #####:   33:    printf ("Failure\n");
               call    0 never executed
               branch  1 never executed
               branch  2 never executed
                       -:   34:  else
                       1:   35:    printf ("Success\n");
               call    0 returned 100%
               branch  1 taken 100% (fallthrough)
               branch  2 taken 0% (throw)
                       1:   36:  return 0;
                       -:   37:}

For each function, a line gets printed showing how many times the function is called, how many times it returns, and what percentage of the function's blocks were executed ๐Ÿ‘‘.

For each basic block, a line is printed after the last line of the block, describing the branch or call that ends the basic block ๐Ÿฆ‹. There can be multiple branches and calls listed for a single source line if multiple basic blocks end on that line. In that case, each branch and call gets a number ๐Ÿ˜ญ. There's no simple way to map those back to source constructs, but in general, the lowest numbered branch or call corresponds to the leftmost construct on the source line.

For a branch, if it was executed at least once, a percentage gets printed showing the number of times the branch was taken divided by the number of times it was executed. Otherwise, the message never executed gets printed ๐Ÿ’€.

For a call, if it was executed at least once, a percentage gets printed showing the number of times the call returned divided by the number of times it was executed ๐ŸŒธ. This will usually be 100%, but it may be less for functions that call exit or longjmp, since they may not return every time they're called.

The execution counts are cumulative ๐ŸŒˆ. If the example program ran again without removing the .gcda file, the count for each line in the source would be added to the results of the previous run(s). This is potentially useful in a few ways. For example, it could accumulate data over a bunch of program runs as part of a test verification suite, or give you more accurate long-term info across a large number of program runs ๐Ÿงš.

The data in the .gcda files is saved immediately before the program exits. For each source file compiled with -fprofile-arcs, the profiling code first attempts to read in an existing .gcda file. If the file doesn't match the executable (differing number of basic block counts), it ignores the contents of the file. Then it adds in the new execution counts and finally writes the data to the file ๐Ÿ’–.


โœจ Using gcov with GCC Optimization โœจ

If you plan to use gcov to help optimize your code, you must first compile your program with a special GCC option, --coverage ๐ŸŽ€. Aside from that, you can use any other GCC options, but if you want to prove that every single line in your program was executed, you should not compile with optimization at the same time ๐Ÿ™…. On some machines the optimizer can eliminate some simple code lines by combining them with other lines. For example, code like this:

               if (a != b)
                 c = 1;
               else
                 c = 0;

can be compiled into one instruction on some machines ๐Ÿคฏ. In this case, there's no way for gcov to calculate separate execution counts for each line, because there isn't separate code for each line. So the gcov output looks like this if you compiled the program with optimization:

                     100:   12:if (a != b)
                     100:   13:  c = 1;
                     100:   14:else
                     100:   15:  c = 0;

The output shows that this block of code, combined by optimization, executed 100 times. In one sense that result is correct, because there was only one instruction representing all four of these lines ๐Ÿ˜ค. However, the output doesn't indicate how many times the result was 0 and how many times it was 1 ๐Ÿคท.

Inlineable functions can create unexpected line counts ๐Ÿ‘€. Line counts are shown for the source code of the inlineable function, but what gets shown depends on where the function is inlined, or whether it isn't inlined at all ๐Ÿซ .

Okay so if the function isn't inlined, the compiler has to make an out of line copy of it in every object file that needs it ๐Ÿงš. If fileA.o and fileB.o both have out of line bodies for the same inlineable function, they'll both carry coverage counts for it too ๐Ÿ’…. When you link them, the linker on lots of systems picks ONE of those bodies for every call and ignores or removes the other. Here's the tea though: it does NOT clean up the coverage counters for the unused copy ๐Ÿ’€. So when you instrument stuff, every use of that function except one shows zero counts ๐Ÿ˜ญ.

If the function gets inlined in several places, the block structure in each spot might not match up. A condition could be calculable at compile time in some instances but not others ๐Ÿ™ˆ. Since the coverage for all the uses gets shown against the same source lines, the line counts might look kinda inconsistent. Don't panic, it's just how it works ๐ŸŒธ.

Long-running apps can use the __gcov_reset and __gcov_dump facilities to limit profile collection to the part of the program you actually care about โœจ. Calling __gcov_reset(void) clears all run-time profile counters back to zero ๐Ÿงน, and calling __gcov_dump(void) dumps the profile info collected up to that point into .gcda output files ๐Ÿ“‚. Instrumented apps use a static destructor with priority 99 to invoke __gcov_dump, so __gcov_dump runs after all user defined static destructors AND handlers registered with atexit ๐Ÿ‘‘. Main character timing, honestly ๐Ÿฆ‹.

If an executable loads a dynamic shared object via dlopen functionality, you need -Wl,--dynamic-list-data to dump all profile data. Don't skip this one, bestie ๐Ÿšจ

The profiling run-time library reports various errors related to profile manipulation and profile saving ๐Ÿ™ƒ. These get printed to standard error output, or to the GCOV_ERROR_FILE file if you're using that environment variable ๐Ÿ“. If you want it to terminate immediately after an error occurs, set the GCOV_EXIT_AT_ERROR environment variable ๐Ÿ›‘. This can help you catch profile clashing, which is what leads to a misleading profile ๐Ÿ•ต๏ธโ€โ™€๏ธ.


โœจ SEE ALSO โœจ

gpl(7), gfdl(7), fsf-funding(7), gcc(1) and the Info entry for gcc ๐Ÿ“š๐ŸŒˆ

Copyright (c) 1996-2024 Free Software Foundation, Inc. ๐Ÿ’–

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation ๐ŸŽ€. The Invariant Sections are "GNU General Public License" and "Funding Free Software" ๐Ÿซถ. The Front-Cover texts are (a) (see below), and the Back-Cover Texts are (b) (see below) ๐Ÿ’œ. A copy of the license is included in the gfdl(7) man page ๐Ÿ“œ.

(a) The FSF's Front-Cover Text is:

A GNU Manual

(b) The FSF's Back-Cover Text is:

You have freedom to copy and modify this GNU Manual, like GNU software. Copies published by the Free Software Foundation raise funds for GNU development.

Bestie, that's the whole glow-up ๐ŸŒˆโœจ This page is from gcc-14, last touched on 2024-04-12, and it's officially GCOV(1) era ๐Ÿ’…๐Ÿ‘‘๐Ÿฆ‹

๐Ÿ“ฆ where this page comes from