Retrieve code coverage data via a remote debugger connection

Retrieve code coverage data via a remote debugger connection

Introduction

By default, the code coverage tool Squish Coco creates a file with the extension .csexe upon the application exit with the code coverage information of the current execution. When working on an embedded device, this is not convenient because:

  1. Some embedded OSes do not have a file system
  2. Even if they have one, they are mostly running on a remote target on which it is difficult to access
  3. Most embedded applications never exit; they are running whenever the device is on

There are several solutions which handle these issues: using a serial device (RS232, CAN-Bus, …) or a TCP port to trigger and dump the generation of the execution report, or using a remote file system. Here, we will present a way to store the execution report in RAM and retrieve it using the GDB debugger. Do do this, we use two features of GDB: the ability to execute custom target functions, and the ability to dump a memory area to a file.

Generating the execution report into the RAM

As an example, let’s take a small daemon code which never exits:

#include <stdio.h>

int main()
{
    int idx = 0;
    for ( ;; )
    {
        int i;
        for ( i = 0; i < 100000000; i++ ) ;
        printf( "Hello World (%i)\n", idx++ );
    }
    return 0;
}

We will then compile it with a small piece of code which lets us save the coverage report to a global variable called coverage when the function saveCov() is called:

char coverage[ 64000 ]; /* contains the execution report */

/* Append the string 's' to 'stream' 
 *
 * 'stream' points to the next unwritten character in the global variable
 * 'coverage' and is updated after this call.
 */
static int csfputs(const char *s, void *stream)
{
    char **buffer_p = (char**)(stream);
    do
    {
        **buffer_p = *s ;
        (*buffer_p)++;
        s++;
    }
    while ( *s );
    return 1;
}

/* Initialize the environment variable 'coverage'.
 *
 * A pointer to 'coverage' is returned and is corresponding to the location of
 * the next free character into the array.
 * The array coverage is initialized with '\n' because empty lines are ignored
 * in the execution report.
 */
static void *csfopenappend(const char *path)
{
    int i;
    static char * pp = 0 ;
    for ( i = 0 ; i < sizeof( coverage )/sizeof( coverage[0] ); i++ )
        coverage[i] = '\n';
    pp = coverage ;
    return (void*)( & pp );
}

/* Close function does nothing */
static int csfclose(void *fp)
{
    return 1;
}

const char *  saveCov() /* Generate an execution report */
{
#ifdef __COVERAGESCANNER__
  /* redirection of the execution report to the variable 'coverage' */
  __coveragescanner_set_custom_io( 
          0,
          csfputs,
          csfopenappend,
          0,
          0,
          csfclose,
          0);

  /* generation of the execution report */
  __coveragescanner_save();

  return "Execution report saved into the variable 'coverage'.\n" ;
#else
  return "Code not compiled with Squish Coco!\n" ;
#endif
}

When executing saveCov(), the I/O function usually used to generate an execution report is overwritten to write it into the global variable coverage using __coveragescanner_set_custom_io() and then the report itself is generated through __coveragescanner_save(). coverage is a character array, and it is important to ensure that enough space is reserved for the coverage report since this implementation does not check for buffer overflows.

To compile this code:

csgcc -g -c -O2 -o app.o app.c
csgcc -g -c -O2 -o coverage.o coverage.c
csgcc -g -O2 -o app app.o coverage.o

-g is necessary here because we need to generate the debug symbols to access it through the debugger, but optimizing the code is allowed since we don’t need to debug it. In our case, we use the native compiler but for a real embedded system, the correct cross compiler should be used.

Executing on a remote system

We will first start the gdbserver on target.loc:

$ ssh -t target.loc 'gdbserver --multi :12345'
Listening on port 12345

Once started, it is possible to connect through the TCP port number 12345 with GDB or any front-end (cgdb, DDD, eclipse, …) and debug any application remotely.

Now, we can transfer our application ‘app‘ and debug it with GDB using the following command:

$ scp app target.loc:/tmp/app && gdb -ex 'target extended-remote target.loc:12345' -ex 'file ./app' -ex 'set remote exec-file /tmp/app'
GNU gdb (Debian 7.12-6) 7.12.0.20161007-git
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
Remote debugging using vera:12345
Reading symbols from ./app...done.
(gdb) 

This usage allows us to connect to the gdbserver (target extended-remote), load the debug symbols (file) and select the remote application to debug (set remote exec-file). GDB behaves now as if we were debugging our application locally.

We can now start the execution with the GDB command run, execute our tests, and stop with Ctrl-C in the GDB session the execution:

(gdb) run
Starting program: /home/sfri/DEV/squishcoco/build/debug/blog/app 
Reading /lib64/ld-linux-x86-64.so.2 from remote target...
warning: File transfers from remote targets can be slow. Use "set sysroot" to access files locally instead.
Reading /lib64/ld-linux-x86-64.so.2 from remote target...
Reading /lib64/ld-2.19.so from remote target...
Reading /lib64/.debug/ld-2.19.so from remote target...
Reading /lib/x86_64-linux-gnu/libdl.so.2 from remote target...
Reading /lib/x86_64-linux-gnu/libc.so.6 from remote target...
Reading /lib/x86_64-linux-gnu/libdl-2.19.so from remote target...
Reading /lib/x86_64-linux-gnu/.debug/libdl-2.19.so from remote target...
Reading /lib/x86_64-linux-gnu/libc-2.19.so from remote target...
Reading /lib/x86_64-linux-gnu/.debug/libc-2.19.so from remote target...
^C
Program received signal SIGINT, Interrupt.
0x0000555555554d3b in main () at app.c:10
10              for ( i = 0; i < 100000000; i++ ) ;
(gdb)

On the gdbserver console, we see the output of our application and up to now, we can generate the coverage report with the command p saveCov():

(gdb) p coverage
$1 = '\000' <repeats 63999 times>
(gdb) p saveCov()
$2 = 0x5555555575d0 "Execution report saved into the variable 'coverage'.\n"
(gdb) p coverage
$3 = "\n\n# Measurements\n/7:498545012:/home/sfri/DEV/squishcoco/build/debug/blog/app.c\n\\B5F8E6C00000001E-+0000001FB5F8E6C00000001E\n/10:1025183049:/home/sfri/DEV/squishcoco/build/debug/blog/coverage.c\n\\--0000F"...
(gdb)

As we can see, the global variable coverage now contains a text which is the contents of a .csexe file. To transfer it to the file app.csexe we use the command:

(gdb) dump value app.csexe coverage
(gdb)

The file app.csexe is generated on our host system and it is possible to import it with cmcsexeimport,

$ cmcsexeimport -m app.csmes --delete --title="a test" app.csexe

or open it directly with Coco’s CoverageBrowser which would give:

0 Comments

Leave a reply

Your email address will not be published. Required fields are marked *

*