What is GCOV

  • GCC provides GCOV, code coverage testing tool for C/C++ programs.
  • GCOV identifies the lines of code that got executed while running the program.
  • It also gives additional information like how many times particular line got executed.
  • Also provides information about how many possible branches are there in the code and which branch path got executed more.

Use cases

Optimization

GCOV identifies the sections in the code that are heavy executed, using which we'll be able to focus on optimizing the parts of the code which are executed often.

Identifying dead code

Any code that got compiled but never got executed on any possible scenario can be found using GCOV. Removing such code can help in reducing the memory footprint of the program. This can be vital information for programs running on embedded platforms.

Reliability of testing

The coverage report can help in identifying the gaps in testing.
The coverage information can be used for writing test cases to exercise the uncovered area in the code.

Instrumenting GCOV

GCOV does not require any change in the code. The only requirement is to have the code built with -fprofile-arcs and -ftest-coverage compiler and linker flags.

yogi@u32:~/gcov_basics$ ls -l
total 8
-rwxrwx--- 1 yogi vboxsf 131 Jun  7 15:13 coverage.c
-rwxrwx--- 1 yogi vboxsf 286 Jun  7 15:56 Makefile
yogi@u32:~/gcov_basics$ cat coverage.c
#include <stdio.h>
int main(int argc, char* argv[])
{
  if (argc == 1)
    printf("True\n");
  else
    printf("False\n");
  return 0;
}
yogi@u32:~/gcov_basics$ cat Makefile
CC=gcc
CFLAGS=-fprofile-arcs -ftest-coverage
LDFLAGS=-fprofile-arcs -ftest-coverage
TARGET=cov
SRC=coverage.c

all:    obj
  $(CC) $(LDFLAGS) *.o -o $(TARGET)
obj:
  $(CC) $(CFLAGS) -c $(SRC)
clean:
  rm -f $(TARGET) *.html *.gc* *.o
gcov:
  gcovr -r . --html -o coverage.html --html-details

CFLAGS += -fprofile-arcs -ftest-coverage

CFLAGS are meant to be used during compilation. This will create .gcno file corresponding to .c/.cpp file

[email protected]:~/gcov_basics$ make obj
gcc -fprofile-arcs -ftest-coverage -c coverage.c
[email protected]:~/gcov_basics$ ls -l
total 16
-rwxrwx--- 1 yogi vboxsf  131 Jun  7 15:13 coverage.c
-rw-rw-r-- 1 yogi yogi    396 Jun  7 15:56 coverage.gcno
-rw-rw-r-- 1 yogi yogi   1824 Jun  7 15:56 coverage.o
-rwxrwx--- 1 yogi vboxsf  286 Jun  7 15:56 Makefile

LDFLAGS += -fprofile-arcs -ftest-coverage

LDFLAGS are meant to be used during linking.

[email protected]:~/gcov_basics$ make
gcc -fprofile-arcs -ftest-coverage -c coverage.c
gcc -fprofile-arcs -ftest-coverage *.o -o cov
[email protected]:~/gcov_basics$ ls -l
total 36
-rwxrwxr-x 1 yogi yogi   17295 Jun  7 15:56 cov
-rwxrwx--- 1 yogi vboxsf   131 Jun  7 15:13 coverage.c
-rw-rw-r-- 1 yogi yogi     396 Jun  7 15:56 coverage.gcno
-rw-rw-r-- 1 yogi yogi    1824 Jun  7 15:56 coverage.o
-rwxrwx--- 1 yogi vboxsf   286 Jun  7 15:56 Makefile
[email protected]:~/gcov_basics$ ./cov
True
[email protected]:~/gcov_basics$ ls -l
total 40
-rwxrwxr-x 1 yogi yogi   17295 Jun  7 15:56 cov
-rwxrwx--- 1 yogi vboxsf   131 Jun  7 15:13 coverage.c
-rw-rw-r-- 1 yogi yogi     160 Jun  7 15:56 coverage.gcda
-rw-rw-r-- 1 yogi yogi     396 Jun  7 15:56 coverage.gcno
-rw-rw-r-- 1 yogi yogi    1824 Jun  7 15:56 coverage.o
-rwxrwx--- 1 yogi vboxsf   286 Jun  7 15:56 Makefile

.gcno has static information about the file.

.gcda has dynamic information about the file based on the path taken during execution.

.gcno and .gcda files together are required to generate the coverage report.

Generating Report

Either gcov or gcovr can be used for generating coverage report.

gcov utility will be installed as part of gcc in most of the Linux distributions.

yogi@u32:~/gcov_basics$ gcov coverage.c
File 'coverage.c'
Lines executed:80.00% of 5
coverage.c:creating 'coverage.c.gcov'

yogi@u32:~/gcov_basics$ cat coverage.c.gcov
        -:    0:Source:coverage.c
        -:    0:Graph:coverage.gcno
        -:    0:Data:coverage.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#include <stdio.h>
        1:    2:int main(int argc, char* argv[])
        -:    3:{
        1:    4:    if (argc == 1)
        1:    5:        printf("True\n");
        -:    6:    else
    #####:    7:        printf("False\n");
        1:    8:    return 0;
        -:    9:}
Legend:
  -       indicates not an executable statement
  #####   indicates statement the did not get executed
  1       (or any number) indicates the number of times the statement got executed.

gcovr is a python utility on top of gcov. It can be installed using pip.

$ pip install gcovr
[email protected]:~/gcov_basics$ gcovr -r . --html -o coverage.html --html-details

The above command will generate coverage report in html format.

Cool features

  • GCOV takes care of conditional compilation. If a file has 100 lines of code but only 50 lines of code got conditionally compiled using ifdef, then, only 50 lines is taken into account for calculating the code coverage.

  • GCOV when enabled on shared library and called from two different applications, will consolidate the coverage based on execution of both the applications.

  • GCOV works across reboot. The execution information can be collected and consolidated across reboot.

  • Running the same executable multiple instances, appends execution information to .gcda file.

  • Coverage can be collected from different physical machines by copying the executable and .gcda files.

Pointer to ponder

  • The number of lines in the file does not match exactly with the number of lines considered for testing coverage. One reason is, not all lines are statement to be executed. Say, '{' in a line is not a candidate for execution. Or it could be due to compiler optimization.

  • The version of .gcno file and .gcda file should exactly match to generate report. If the code was compiled again even without any change, report will not get generated as there is mismatch in the version of .gcno and .gcda files.

  • If .gcno file was created again even without changing anything in the code it will not match with .gcda file.

  • The program should gracefully exit to create/append-to .gcda file.

  • If the program is a daemon, better to add exit(0) in the SIGINT and SIGTERM signal handler. to do a graceful exit.

  • GCOV will try to create .gcda file in the same folder structure as it was compiled. But the problem could be when used on embedded platforms, where the filesystem is mostly readonly. In this case, GCOV_PREFIX enviromental variable can be used.

[email protected]:~/gcov_basics$ make
gcc -fprofile-arcs -ftest-coverage -c coverage.c
gcc -fprofile-arcs -ftest-coverage *.o -o cov

Note: Copying the executable to /tmp folder

[email protected]:~/gcov_basics$ cp cov /tmp/
[email protected]:~/gcov_basics$ ls -l
total 36
-rwxrwxr-x 1 yogi yogi   17295 Jun  8 07:30 cov
-rwxrwx--- 1 yogi vboxsf   131 Jun  7 15:13 coverage.c
-rw-rw-r-- 1 yogi yogi     396 Jun  8 07:30 coverage.gcno
-rw-rw-r-- 1 yogi yogi    1824 Jun  8 07:30 coverage.o
-rwxrwx--- 1 yogi vboxsf   286 Jun  7 16:32 Makefile
[email protected]:~/gcov_basics$ cd /tmp/
[email protected]:/tmp$ ./cov
True
[email protected]:/tmp$ find . -name "*.gcda"

Note: gcda file will not get created in the current working directory, instead
will be created in the same folder structure as it got compiled.

[email protected]:/tmp$ cd -
/home/yogi/gcov_basics

Note: gcda file getting created where the code was actually compiled.

[email protected]:~/gcov_basics$ ls -l
total 40
-rwxrwxr-x 1 yogi yogi   17295 Jun  8 07:30 cov
-rwxrwx--- 1 yogi vboxsf   131 Jun  7 15:13 coverage.c
-rw-rw-r-- 1 yogi yogi     160 Jun  8 07:30 coverage.gcda
-rw-rw-r-- 1 yogi yogi     396 Jun  8 07:30 coverage.gcno
-rw-rw-r-- 1 yogi yogi    1824 Jun  8 07:30 coverage.o
-rwxrwx--- 1 yogi vboxsf   286 Jun  7 16:32 Makefile
[email protected]:~/gcov_basics$
[email protected]:~/gcov_basics$ export GCOV_PREFIX=/tmp
[email protected]:~/gcov_basics$ rm coverage.gcda
[email protected]:~/gcov_basics$ cd -
/tmp
[email protected]:/tmp$ ./cov
True

Note: By setting GCOV_PREFIX environmental variable we'll be able to direct
the files to a particular base folder.

[email protected]:/tmp$ ls -l /tmp/home/yogi/gcov_basics/coverage.gcda
-rw-rw-r-- 1 yogi yogi 160 Jun  8 07:31 /tmp/home/yogi/gcov_basics/coverage.gcda
  • GCOV_PREFIX_STRIP environmental variable can be handy when we are not interested in complete folder structure but to remove certain part of it.
[email protected]:/tmp$ export GCOV_PREFIX=/tmp
[email protected]:/tmp$ export GCOV_PREFIX_STRIP=2
[email protected]:/tmp$ ./cov
True

Note:  Earlier .gcda file was getting created in /tmp/home/yogi/gcov_basics/ folder.
Now by exporting GCOV_PREFIX_STRIP=2 environmental variable, will strip two levels - /home/yogi/ folder
is stripped off and .gcda file will get create in /tmp/gcov_basics/

[email protected]:/tmp$ ls -l /tmp/gcov_basics/coverage.gcda
-rw-rw-r-- 1 yogi yogi 160 Jun  8 07:44 /tmp/gcov_basics/coverage.gcda

FAQs

1.undefined reference to __gcov_init

[email protected]:~/gcov_basics$ make
gcc -fprofile-arcs -ftest-coverage -c coverage.c
gcc  *.o -o cov
coverage.o: In function `_GLOBAL__sub_I_65535_0_main':
coverage.c:(.text+0xae): undefined reference to `__gcov_init'
coverage.o:(.data+0x24): undefined reference to `__gcov_merge_add'
collect2: ld returned 1 exit status
make: *** [all] Error 1

The reason for this is, -fprofile-arcs and -ftest-coverage where used during compilation(in CFLAGS), but missed during linking (in LDFLAGS).

2..gcda file not getting created as the result of execution.

Check if gcov symbols are there in the binary using strings or nm command.

ldd command will not help because there will not be any extra libraries linked specifically for gcov.

Binary without gcov symbols will look like the one shown below.

[email protected]:~/gcov_basics$ strings cov
/lib/ld-linux.so.2
__gmon_start__
libc.so.6
_IO_stdin_used
puts
__libc_start_main
GLIBC_2.0
PTRh
UWVS
[^_]
True
False
;*2$"

Binary with gcov symbols will look like the one shown below.

[email protected]:~/gcov_basics$ strings cov
/lib/ld-linux.so.2
__gmon_start__
libc.so.6
_IO_stdin_used
...
[^_]
True
False
/home/yogi/gcov_basics/coverage.gcda
profiling:%s:Version mismatch - expected %.4s got %.4s
profiling:%s:Overflow merging
profiling:%s:Overflow writing
profiling:%s:Cannot create directory
profiling:%s:Not a gcov data file
profiling:%s:Merge mismatch for %s
profiling:%s:Invocation mismatch - some data files may have been removed%s
function
summaries
profiling:%s:Error merging
profiling:%s:Error writing
GCOV_PREFIX_STRIP
GCOV_PREFIX
profiling:%s:Skip
profiling:%s:Cannot open
...

The reason for this could be -fprofile-arcs and -ftest-coverage CFLAGS were missed during compilation.

3.gcov symbols are seen in the binary but still .gcda file is not getting created.

The reason could be the program did not do a graceful exit.

We’d love to hear more about your experiences with c/c++ code coverage. Please share your thoughts in the comments below.

This article was first published in chennainerd.in on JUN 8TH, 2014.



Comments

comments powered by Disqus