Skip to content
Ken Peters edited this page Feb 2, 2022 · 1 revision

Welcome to the embedded-gcov wiki!

Customization to your system

First, you want to select options in gcov_public.h by uncommenting or adjusting desired preprocessor definitions. The primary selection is which form(s) of coverage data output you want:

Binary file output

Uncomment #define GCOV_OPT_OUTPUT_BINARY_FILE in gcov_public.h. Also adjust the definition of GCOV_OUTPUT_BINARY_FILENAME if desired to select the output filename.

Then, you need to check the definitions of

  • typedef GCOV_FILE_TYPE - file reference data type returned by the file open function and used by write and close
  • GCOV_OPEN_FILE(filename) - opens a file for writing, returns a GCOV_FILE_TYPE "fileref" to use for file access
  • GCOV_OPEN_ERROR(fileref) - checks the fileref returned from GCOV_OPEN_FILE for error status
  • GCOV_CLOSE_FILE(fileref) - closes the file
  • GCOV_WRITE_BYTE(fileref, char_var) - writes a character to the file (character value, not a pointer)

Provided with embedded gcov, you can select whether you want to use the "open, write, close" C library functions (by leaving the #if 1), or the "fopen, fwrite, fclose" C library functions (by changing the #if 1 to #if 0). If your embedded system does not use the C libraries and does not provide your own duplications of those functions, you can edit the preprocessor definitions to call file functions provided in your embedded system.

Binary memory output

Uncomment #define GCOV_OPT_OUTPUT_BINARY_MEMORY in gcov_public.h.

Then, you need to edit gcov_public.c to set the memory address of the memory block to use: static unsigned char *gcov_output_buffer = (unsigned char *)(0x42000000);

Size used will depend on size and complexity of source code that you have compiled for coverage. Embedded gcov does not do memory checking, but it does determine the bytesNeeded for each source file as part of the output, and you should be able to extract that info to add up the amount.

After your call to __gcov_exit() stores the coverage data at the defined memory location, you will have to extract that as appropriate to your embedded system (possibly a debugger dump, or some operational memory dump interface).

Serial hexdump output

Uncomment #define GCOV_OPT_OUTPUT_SERIAL_HEXDUMP in gcov_public.h.

Then, you need to check the definitions of the preprocessor macros

  • GCOV_PRINT_STR(str) - prints a string to the serial port (see also GCOV_OPT_PRINT_STATUS below)
  • GCOV_PRINT_NUM(num) - prints a number in decimal (see also GCOV_OPT_PRINT_STATUS below)
  • GCOV_PRINT_HEXDUMP_ADDR(num) - prints the address of a hexdump line
  • GCOV_PRINT_HEXDUMP_DATA(num) - prints one hex data number in a hexdump line

Provided with embedded gcov, there are example definitions using fputs(), printf(), print_num() (a custom function in my personal system), and gcov_printf() (a small custom printf imitation provided with embedded gcov). If none of these will work in your embedded system, change the definitions to your own functions as needed.

If you do NOT want to use gcov_printf(), then edit gcov_public.h and comment out #define GCOV_OPT_PROVIDE_PRINTF_IMITATION. Commenting this out will eliminate the function prototype and the body of gcov_printf.c, and you do not need to include gcov_printf.c in your build.

gcov_printf for serial output

If you want to use gcov_printf in your system, then uncomment #define GCOV_OPT_PROVIDE_PRINTF_IMITATION in gcov_public.h.

Then, you need to edit gcov_printf.c to check the definition of the function write_bytes() that does the actual sending of a byte out the serial port. The function prototype is shown in gcov_printf.c, and a preprocessor macro is provided using the C library function putchar(), but you may need to adjust to your system.

Note that although the function prototype provides for a "file descriptor" (to select from multiple serial ports) and a byte count, embedded gcov as provided only uses the function to write 1 byte to "fd 1", so the "fd" can be ignored if not relevant to your system.

Finally, if using gcov_printf you need to include the file gcov_printf.c in your build.

Additional customization options

GCOV_OPT_PRINT_STATUS

This allows embedded gcov to print status and error messages to serial port using preprocessor macros GCOV_PRINT_STR and GCOV_PRINT_NUM (described above). If you want to allow this, then uncomment #define GCOV_OPT_PRINT_STATUS in gcov_public.h. If you do not allow this, you might consider editing gcov_public.c to use some other method of reporting status suitable to your embedded system.

GCOV_OPT_USE_MALLOC

This allows embedded gcov to use malloc() to get space for the coverage data of each source file. If your embedded system allows malloc, then uncomment #define GCOV_OPT_USE_MALLOC in gcov_public.h.

If you do not want to allow malloc, then comment out #define GCOV_OPT_USE_MALLOC in gcov_public.h. You will also have to edit gcov_public.c and check the sizes set for

  • static GcovInfo gcov_GcovInfo[100]; // Need one entry per file compiled for coverage.
  • gcov_unsigned_t gcov_buf[8192]; // Needs to be enough for the largest single file coverage data. Size used will depend on size and complexity of source code that you have compiled for coverage.

GCOV_OPT_USE_STDLIB

This allows embedded gcov to use some C stdlib functions (namely fflush() and exit()). If you want to allow this, then uncomment #define GCOV_OPT_USE_STDLIB in gcov_public.h. Otherwise, comment it out.

GCOV_OPT_RESET_WATCHDOG

Embedded gcov can take noticeable time to emit the coverage data, especially if using the serial port for output, or if you have compiled a very large number of source files for coverage.

If your embedded system has a watchdog timer, you may need to uncomment #define GCOV_OPT_RESET_WATCHDOG in gcov_public.h and then edit gcov_gcc.c to search for GCOV_OPT_RESET_WATCHDOG and insert whatever watchdog reset header files and function calls are needed for your embedded system.

GCOV_OPT_PROVIDE_CLEAR_COUNTERS

Embedded gcov can be compiled to provide a function __gcov_clear() that you can call from your code to clear the code coverage counters. You might want the ability to do this between test runs, or to avoid counting startup code.

If you want this function, then uncomment #define GCOV_OPT_PROVIDE_CLEAR_COUNTERS in gcov_public.h and place calls to __gcov_clear() where desired in your source code. Otherwise, comment out this definition, and the __gcov_clear() function will not be compiled in.

GCOV_OPT_PROVIDE_CALL_CONSTRUCTORS

Provides a function __gcov_call_constructors() to call constructor list (even in plain C), to call the gcc-generated code that calls __gcov_init. Might be needed if you are not running a standard C program startup that already does this (for instance, if your system has custom boot code).

If you need this, then uncomment #define GCOV_OPT_PROVIDE_CALL_CONSTRUCTORS in gcov_public.h and place a call to __gcov_call_constructors() in your code somewhere near the startup. You may have to experiment a little to find the best place to insert it (not too early or too late in the startup).

If you need this, you also need to ensure that the symbols __ctor_list and __ctor_end are defined to mark the start and end of the constructors. Typically this would be done by providing instructions in a linker file, see gcov_public.h for an example, but you may have to adapt this to your embedded system.

GCC Internals

Embedded gcov does rely on some GCC internals, that GCC could change from version to version. Historically these have remained pretty consistent, since gcov is a long-standing feature of GCC, but it is not a publicly defined interface that they promise to maintain. The embedded gcov files gcov_gcc.h and gcov_gcc.c have comments indicating what GCC source files to look at if you suspect there might be a mismatch with your version of GCC. Embedded gcov has been tested with GCC 7.5.0 and GCC 11.1.0, but should work with many other versions, and hopefully many future versions.