Table of Contents

GCC GCOV support by Anton Kuijsten

This wiki page is based on what was originally in Anton's <minix/gcov.h>.

Introduction

Code coverage testing is offered by gcc through gcov. Special compiler flags add execution counters to a binary, along with counter increment operations around each basic block of code. This way, the counters indicate how many times a line of code has run.

The regular gcov interface uses fopen() and fwrite() directly, which is impossible from minix system processes and the kernel, of course. Anton changed libgcc to call gcov_ wrapper functions instead, with are implemented separately in libc (for normal programs) and libsys (for system processes.)

How to use

Simply set MKCOVERAGE=yes as an environment or make variable. This will cause bsd.gcov.mk (included by bsd.prog.mk) to set the compiler to gcc, and the CFLAGS to extra necessary gcov options. Of course you have to compile everything like this (both for gcov to work and to not mix ack and gcc object files), so do 'make clean' first. e.g.:

# cd /usr/src/servers/vfs
# make MKCOVERAGE=yes clean all

Significantly, this build process also writes gcc .gcno files in this directory, which is line number information. These are needed later by lcov.

The env/make variable means you can do:

# cd /usr/src/tools
# make MKCOVERAGE=yes clean hdboot

in order to generate coverage code and data for all servers and drivers. (Except that that doesn't work literally because the kernel is a special case currently - it won't build with gcov so you have to do make clean, make kernel without gcov, then make hdboot with gcov.)

Reboot with the new gcov-enabled system.

To retrieve the data, run gcov-pull with the pid of the server as an argument (not endpoint/slot number). This pretty much has to happen in the server's own directory in order for lcov to use the .gcno files:

# cd /usr/src/servers/vfs
# gcov-pull 7

Now you can run lcov to interpret the gcov data and generate a pretty html report. -c means capture, -d . specifies the directory with .gcda files and implies it has to read from those files as opposed to retrieve the data from the kernel (linux). Install lcov first if it's not installed yet.

# pkgin -y in lcov
# lcov -c -d . >lcov.vfs  # make lcov is a shortcut
# genhtml lcov

That's it; the html report is in that directory. More advanced use combines this coverage data into a single report; from a higher level:

# genhtml -o report ''find . -name 'lcov.*'''

How to implement in new servers

In theory, no peculiar extra work has to be done if your process uses SEF. By default, the standard libsys gcov call handler is invoked which handles the VFS request properly. If you are unusual, such as if you are the kernel, you need a different mechanism to provide the gcov buffer.

Implementation

libgcov is linked to each executable. This library writes the counters to disk (gcda files) whenever the executable exits or forks. The gcov tool can transform those gcda files, along with gcno files that are created at compile time, to gcov files. Those gcov files are human readable, and contain the source code with an execution count at the beginning of each line. Lcov is a tool made by the Linux test project, which can transform those gcov files into a nice bundle of html files.

GCOV IN MINIX SERVERS

When making a GCOV call to a server, the gcov library linked into the server will try to write gcov data to disk. This writing is normally done with calls to the vfs, using stdio library calls. This is not correct behavior for servers, especially vfs itself. Therefore, the server catches those attempts. Instead, the gcov data is stored in a buffer. When the gcov operation is done, the buffer is copied from the server to a helping user space process, from where the calls are finally made to the vfs. GCOV calls to the various servers are all routed trough vfs.

PARTS OF THE SYSTEM

gcc, gcov, libgcov

In the gcc source package, the header file gcc/gcov-minix-fs-wrapper.h is added. That file is included in gcc/libgcov.c. The header file add pointers to file system calls, and redefine all file system function calls as calls to these pointers. In effect, these pointers are an extra layer between libgcov and the file system calls. These pointers can be pointed to functions that do not call the file system, but transfer the gcov data to the user space buffer. Gcc and gcov are also used when compiling the servers and converting the gcov data.

sysutil library

In the sysutil library, the functions that replace the standard file system calls are implemented (in gcov.c). Also, there is one main entry point that does the function replacement, and calls the libgcov functionality. This function is called do_gcov_flush_impl.

the minix servers

In the minix servers, the gcov function call is added. It's called do_gcov_flush. That function call calls the do_gcov_flush_impl in the sysutil library. Among other arguments, it passes a pointer to the actual gcov_flush implementation in libgcov, and a function pointer that replaces the file system function calls with the sysutil versions. Sysutil does not know about those pointers, because it is not linked to libgcov.

gcov-tools

There are three tools for using gcov with minix servers. These are located in /usr/src/commands/gcov-tools. First of all, gcov-pretty adds extra new lines to the server source code, so that all statements get their own line. This way, one gets more information on coverage of each statement. Secondly, gcov-pull creates a buffer, makes a gcov call to vfs, and then writes to disk all the gcov data that was placed in the buffer by a server. Thirdly, gcov-lcov is a shell script that combines a gcov-pull call with an subsequent lcov call. That way, all gcov data is transformed into html. Also, When previous gcov-data is found, the two data sets are combined by adding the counters together.

BUGS

Shortcomings of the system are..

  1. If you give a non-server/driver but existing pid to vfs, it will try to sendrec() to that pid anyway, but never receive a reply, hanging the system.
  2. You have to give the pid to gcov-pull, which is a pain to do with many different processes in batches. The problem with name lookups is that they're not unique (mfs specifically).