Monday, July 21, 2014

Code coverage - embedded target - simple approach

In this example I will try to show how to get code coverage data from an embedded target using only GDB debugger.
Project is written for STM32F105 in C under Eclipse IDE using GCC.

As I said before in order to build the project we need to provide some stubs for the methods described in Code coverage - introduction. I have used file with stubs from nano-age and made most of the methods empty. It is enough for the code to compile and it will do in the simple approach. In the next post I will show how to do it "the proper way".

In the simple approach we will use GDB and dump command to dump the file with code coverage from the target to hard drive.
The reason why I'm not calling this "the proper way" is that this approach is not fully automated (our intervention is needed to store the files) and we loose data because we overwrite files instead of merging them. Normally the files should be merged in order to accumulate code coverage from different runs.

The newlib_stubs.c file with needed stubs (most of them empty) is located at the end of this post.

Now that stubs are provided we can successfully compile the code.
In the example I have implemented simple serial port methods and checking character received from serial port in the main program loop.
After receiving 'e' character program calls _exit(0) method and starts generating code coverage.

Step by step instructions:
  1. Build project with "--coverage" option both for compiling and linking
  2. Set breakpoints in stubs of _open() and _write() methods
  3. Start debugging session with GDB
  4. Perform some operations
  5.  Call _exit(0) either from an external interface or by setting PC to that method (address can be found in lss file)
  6. When program reaches breakpoint in _open method then copy the name of file  (FILENAME) to be opened from ptr argument and run the program
  7. When program reaches breakpoint in _write method then copy the memory address (HEX_START_ADDR) from the ptr argument and size from len argument
  8. Open GDB console and dump memory using: "dump binary memory FILENAME.gcda HEX_START_ADDR HEX_END_ADDR"
  9. Repeat 6-8 if coverage for more than one file is needed
  10. Now both .gcno and .gcda files are present. Refresh the project to invoke gcov and obtain code coverage (eclipse gcov plugin needed)
Here is a short video with demonstration:


And here is how the output will more or less look like in eclipse:


newlib_stubs.c
/*
 * newlib_stubs.c
 *
 *  Created on: 2 Nov 2010
 *      Author: nanoage.co.uk
 */
extern int errno;

char *__env[1] = { 0 };
char **environ = __env;

int _write(int file, char *ptr, int len);

void _exit(int status)
{
 __gcov_flush();
}

int _close(int file)
{
    return -1;
}

int _fstat(int file, struct stat *st)
{
    st->st_mode = S_IFCHR;
    return 0;
}

int _getpid()
{
    return 1;
}

int _isatty(int file) 
{
    switch (file)
    {
    case STDOUT_FILENO:
    case STDERR_FILENO:
    case STDIN_FILENO:
        return 1;
    default:
        errno = EBADF;
        return 0;
    }
}

int _kill(int pid, int sig)
{
    errno = EINVAL;
    return (-1);
}

int _lseek(int file, int ptr, int dir)
{
    return 0;
}

void *_sbrk(int incr)
{
    extern char _ebss; // Defined by the linker
    static char *heap_end;
    char *prev_heap_end;

    if (heap_end == 0)
    {
        heap_end = &_ebss;
    }
    prev_heap_end = heap_end;

    char * stack = (char*) __get_MSP();

     if (heap_end + incr >  stack)
     {
         _write (STDERR_FILENO, "Heap and stack collision\n", 25);
         errno = ENOMEM;
         return  (void *) -1;
     }

    heap_end += incr;
    return (void *) prev_heap_end;
}

int _read(int file, char *ptr, int len)
{
 return 0;
}

int _unlink(char *name)
{
    errno = ENOENT;
    return -1;
}

int _write(int file, char *ptr, int len)
{
    return 0;
}

int _open (const char *ptr, int mode)
{
 return 0;
}

No comments :

Post a Comment