/* Ajith - Syntax Higlighter - End ----------------------------------------------- */

2.16.2009

Gcov - analyzing code produced with GCC

How can a programmer exactly know which part of his code is frequently executed and which part of code is never traversed ? That's where CODE COVERAGE comes for rescue ...

Code Coverage is a measure used in software testing to describe the degree to which the source code of a program has been tested. It is a form of testing that inspects the code directly and checks the active and non-active parts of the source-code.

Basically there are number of code coverage criteria, the main ones being:
  • Function coverage
  • Has each function in the program been executed?

  • Statement coverage
  • Has each line of the source code been executed?

  • Decision coverage (also known as Branch coverage)
  • Has each control structure (such as an if statement) evaluated both to true
    and false?

  • Condition coverage
  • Has each boolean sub-expression evaluated both to true and false (this does
    not necessarily imply decision coverage)?

  • Modified Condition/Decision Coverage (MC/DC)
  • Has every condition in a decision taken on all possible outcomes at least
    once? Has each condition been shown to affect that decision outcome
    independently?

  • Path coverage
  • Has every possible route through a given part of the code been executed?

  • Entry/exit coverage
  • Has every possible call and return of the function been executed?

gcov is a test coverage tool. When built with an application, the gcov utility monitors an application under execution and identifies which source lines have been executed and which have not and also it can identify the number of times a particular line has been executed, making it useful for performance profiling to help discover where the optimization efforts will best affect the code and also along with the other profiling tool, gprof, to assess which parts of your code use the greatest amount of computing time.

Since gcov is bundled along with GCC [sometimes it may not be] I will be just using the gcc compiler throughout this post. To use gcov test coverage tool to produce coverage information about your application source code, you must
  • Compile your source code using a GCC compiler
  • The gcov application might not[Not sure if there are any ports available for other compilers] be compatible with embedded coverage and analysis information produced by any other compiler.

  • Use -fprofile-arcs and -ftest-coverage options when compiling your code with a GCC compiler.

  • Avoid using any of the optimization options provided by the GCC compiler
  • These options might modify the execution order or grouping of your code, which would make it difficult or impossible to map coverage information into your source files.

  • Let the programming style be simple
  • Basically gcov accumulates statistics by line (at the lowest resolution), it works best with a programming style that places only one statement on each line. If you use complicated macros that expand to loops or to other control structures, the statistics are less helpful -- they only report on the line where the macro call appears. If your complex macros behave like functions, you can replace them with inline functions to solve this problem.
Let us have a basic 'C' source file named 'test.c' to work with gcov.
#include <stdio.h>

int main (void)
{

  int i;

  for(i=1;i<13;i++)
  {
    if(i%2 == 0)
    printf("%d is divisible by 2 \n",i);

    if(i%5==0)
    printf("%d is divisible by 5 \n",i);

    if (i%14==0)
    printf("%d is divisible by 14 \n",i);
  }

  return 0;
}
NOTE: Checkout GCC version with gcc -v command. Because I had some problems regarding the types of files that are generated when compiling the code to support code coverage option. So I decided to use both the older version of GCC and a newer one to notify different types of files that are created.

Let us enable code coverage and compile the source code
[bash]$ gcc -Wall -fprofile-arcs -ftest-coverage test.c

Once compiled successfully along with the executable file 'a.out' other files are also created.
For GCC version 2.95 cygwin special two new files namely test.bb & test.bbg with .bb & .bbg file extensions are created in the current directory.
For GCC version 3.4.4 cygwin special a file named test.gcno with .gcno file extension is created in the current directory.

Run the executable file created.
[bash]$ ./a.out
2 is divisible by 2
4 is divisible by 2
5 is divisible by 5
6 is divisible by 2
8 is divisible by 2
10 is divisible by 2
10 is divisible by 5
12 is divisible by 2

For GCC version 2.95 cygwin special a new file named test.da with file extension .da is created in the current directory.

For GCC version 3.4.4 cygwin special a new file named test.gcda with .gcda file extension is created.

We have to use gcov command with the name of the sourcefile i.e. test.c in our case to perform code coverage.
[bash]$ gcov test.c
File `test.c'
Lines executed:90.00% of 10
test.c:creating `test.c.gcov'
The gcov command produces an annotated version of the original source file, with the file extension ‘.gcov’, containing counts of the number of times each line was executed. In our case a file named 'test.c.gcov' is created.

The .gcov files contain the ':' separated fields along with program source code. The format is

execution_count : line_number : source line text

Additional block information may succeed each line, when requested by command line option. The execution_count is '-' for lines containing no code and '#####' for lines which were never executed. Some lines of information at the start have line_number of zero.
[bash]$cat test.c.gcov
 -:    0:Source:test.c
 -:    0:Graph:test.gcno
 -:    0:Data:test.gcda
 -:    0:Runs:1
 -:    0:Programs:1
 -:    1:#include <stdio.h>
 -:    2:
 -:    3:int main (void)
function main called 1 returned 100% blocks executed 91%
 1:    4:{
 -:    5:
 1:    6:int i;
 -:    7:
13:    8:for(i=1;i<13;i++)
 -:    9:{
12:   10:if(i%2 == 0)
 6:   11:printf("%d is divisible by 2 \n",i);
 -:   12:
12:   13:if(i%5==0)
 2:   14:printf("%d is divisible by 5 \n",i);
 -:   15:
12:   16:if (i%14==0)
#####:   17:printf("%d is divisible by 14 \n",i);
 -:   18:}
 -:   19:
 1:   20:return 0;
 -:   21:}

The line counts can be seen in the first column of the output (Indicated in red box in the image) and the line which is not executed is marked with hashes ‘######’.

NOTE: grep '######' *.gcov can be used to find parts of a program which have not been used.

Using gcov’s -b (--branch-probabilities) option when running gcov on an instrumented source module writes branch frequencies and branch summary information into the annotated filename.c.gcov module produced by gcov.
[bash]$ gcov -b test.c
File `test.c'
Lines executed:90.00% of 10
Branches executed:100.00% of 8
Taken at least once:87.50% of 8
Calls executed:75.00% of 4
test.c:creating `test.c.gcov'
New output file test.c.gcov is shown below.Branch - Annotated Source Code Showing Branch Percentages.
$cat test.c.gcov
 -:    0:Source:test.c
 -:    0:Graph:test.gcno
 -:    0:Data:test.gcda
 -:    0:Runs:1
 -:    0:Programs:1
 -:    1:#include <stdio.h>
 -:    2:
 -:    3:int main (void)
function main called 1 returned 100% blocks executed 91%
 1:    4:{
call    0 returned 100%
 -:    5:
 1:    6:int i;
 -:    7:
13:    8:for(i=1;i<13;i++)
branch  0 taken 92% (fallthrough)
branch  1 taken 8%
 -:    9:{
12:   10:if(i%2 == 0)
branch  0 taken 50% (fallthrough)
branch  1 taken 50%
 6:   11:printf("%d is divisible by 2 \n",i);
call    0 returned 100%
 -:   12:
12:   13:if(i%5==0)
branch  0 taken 17% (fallthrough)
branch  1 taken 83%
 2:   14:printf("%d is divisible by 5 \n",i);
call    0 returned 100%
 -:   15:
12:   16:if (i%14==0)
branch  0 taken 0% (fallthrough)
branch  1 taken 100%
#####:   17:printf("%d is divisible by 14 \n",i);
call    0 never executed
 -:   18:}
 -:   19:
 1:   20:return 0;
 -:   21:}
Note that the output file still identifies basic blocks and provides line execution counts, but now also identifies the line of source code associated with each possible branch and function call. The annotated source code also displays the number of times that branch is executed.

Compiling the source code with the -fprofile-arcs option enables GCC compiler to build a call graph for the application and generates a call graph file in the same directory where the source file is located. This call graph file has the same name as the original source file, but replaces the original file extension with the .gcno extension. For e.g. compiling the program test.c with the -fprofile-arcs option created the file test.gcno in the working directory. This file is created at compile time, rather than at runtime.
A call graph is a list identifying which basic blocks are called by other functions, and which functions call other functions. The representation of each call from one function to another in a call graph is known as a call arc, or simply an arc. The .gcno file contains information that enables the gcov program to reconstruct the basic block call graphs and assign source line numbers to basic blocks.
On running the application executable compiled with -fprofile-arcs created a file with the same name as the original source file but with the .gcda extension (GNU Compiler arc data) in the same directory. For e.g. on executing a.out executable that was compiled with the -fprofile-arcs option created the file test.gcda in current directory. The file with the .gcda extension contains call arc transition counts and some summary information.

References

1. GCC/gcov - link
2. Man page - gcov.
3. An Introduction to GCC - for the GNU compilers gcc and g++ - link.
4. The Definitive Guide to GCC - Amazon link.
5. Code coverage - wiki

4 comments :

  1. There is a tool called Trucov, which builds upon Gcov to output Control Flow Graphs with coverage information.

    The homepage is http://code.google.com/p/trucov/
    The wiki page is http://en.wikipedia.org/wiki/Trucov

    ReplyDelete
  2. In an embedded system like a router where will the .da for each of the source files be created. Will it be on the router or on the host system that built the code and is connected to the router?

    ReplyDelete
  3. Don't bother with the Trucov -- the cmakelists.txt is fail and it don't build. Gave up trying to fix it.

    ReplyDelete

Your comments are moderated