Tuesday, July 22, 2014

Code coverage - embedded target

As I have promised before now I will try to explain how to get code coverage from an embedded target in "the proper way".
The proper way means that it will be done automatically and that files with coverage will be merged instead of being overwritten.

To achieve all of the above two thing are needed:
  1. Interface used for handling files from the target
  2. PC application that will interact with the target through the provided interface and handle code coverage files
For the interface I have used serial port. The methods for: _open(), _read(), _write() and _close() have been changed so that they will interface with PC application and send the name of the file, check if it exists and finally read and write the file. Source code of newlib_stub.c with methods that interface serial port is available here.

The PC application is quite simple. It just captures the data from the serial port and searches for some special KEY words like fopen, fread, fwrite.
The program can be downloaded from my sourceforge page.
Source code available on GitHub.

newlib_stubs.c
/*
 * newlib_stubs.c
 *
 *  Created: 22.07.2014 / 2 Nov 2010
 *  Author: Filip Ryborz / nanoage.co.uk
 *  Website: simply-embedded.blogspot.com
 */

#include <errno.h>
#include "stm32f10x_usart.h"

#undef errno
extern int errno;

/*
 environ
 A pointer to a list of environment variables and their values.
 For a minimal environment, this empty list is adequate:
 */
char *__env[1] = { 0 };
char **environ = __env;

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

/*
 Status of an open file. For consistency with other minimal implementations in these examples,
 all files are regarded as character special devices.
 The `sys/stat.h' header file required is distributed in the `include' subdirectory for this C library.
 */
int _fstat(int file, struct stat *st)
{
 st->st_mode = S_IFCHR;

 return 0;
}

/*
 Process-ID; this is sometimes used to generate strings unlikely to conflict with other processes. Minimal implementation, for a system without processes:
 */
int _getpid()
{
    return 1;
}

/*
 Query whether output stream is a terminal. For consistency with the other minimal implementations,
 */
int _isatty(int file)
{
    switch (file)
    {
    case STDOUT_FILENO:
    case STDERR_FILENO:
    case STDIN_FILENO:
        return 1;
    default:
        errno = EBADF;
        return 0;
    }
}

/*
 Send a signal. Minimal implementation:
 */
int _kill(int pid, int sig)
{
 errno = EINVAL;

 return (-1);
}

/*
 Set position in a file. Minimal implementation:
 */
int _lseek(int file, int ptr, int dir)
{
 return 0;
}

/*
 Increase program data space.
 Malloc and related functions depend on this
 */
caddr_t _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  (caddr_t) -1;
 }

 heap_end += incr;

 return (caddr_t) prev_heap_end;
}

/*
 Remove a file's directory entry. Minimal implementation:
 */
int _unlink(char *name)
{
 errno = ENOENT;

 return -1;
}

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

int _open (const char *ptr, int mode)
{
 int ret = 0;
 unsigned char c;

 // send file open command
 tiny_printf("fOpen(%s, %d) %\0", ptr, mode);

 while (tiny_get(&c) == 0) {}
 ret = c << 8;
 while (tiny_get(&c) == 0) {}
 ret += c;

 return ret;   /* Number of bytes written. */
}

/*
 Read a character to a file. `libc' subroutines will use this system routine for input from all files, including stdin
 Returns -1 on error or blocks until the number of characters have been read.
 */
int _read(int file, char *ptr, int len)
{
 int n;
 int num = 0;
 int size = 0;
 unsigned char c;

 // send file read command
 tiny_printf("fRead %d, %d| %\0", file, len);

 // get file size
 while (tiny_get(&c) == 0) {}
 size = c << 8;
 while (tiny_get(&c) == 0) {}
 size += c;
 if (size < len) {len = size;}

 switch (file) {
 case STDIN_FILENO:
   for (n = 0; n < len; n++)
   {
    while (tiny_get(&c) == 0) {}
    *ptr++ = c;
    num++;
   }
   break;
 default:
   errno = EBADF;
   return -1;
 }

 return num;
}

/*
 Write a character to a file. `libc' subroutines will use this system routine for output to all files, including stdout
 Returns -1 on error or number of bytes sent
 */
int _write(int file, char *ptr, int len) {

 int n;
 unsigned char c;

 // send file write command
 tiny_printf("fWrite %d, %d| %\0", file, len);

 while (tiny_get(&c) == 0) {} // wait for ack

 switch (file) {
 case STDOUT_FILENO: /*stdout*/
   for (n = 0; n < 1; n++)
   {
   }
   break;
 case STDERR_FILENO: /* stderr */
   for (n = 0; n < 1; n++)
   {
   }
   break;
 default:
   //errno = EBADF;
   //return -1;
  for (n = 0; n < len; n++)
  {
   fputc(*ptr++, NULL);
  }
 }

 return len;
}

int _close(int file)
{
 unsigned char c;

 // send close file command
 tiny_printf("fClose() %\0");

 while (tiny_get(&c) == 0) {} // wait for ack

 return -1;
}


No comments :

Post a Comment