GNU Coverage (gcov) for an Embedded Target with VS Code

An important part of every CI/CD pipeline is having a testing phase. In this article I show how to use GNU gcov (coverage) with an embedded target, using Visual Studio Code as front end:

GNU gcov with VS Code

With this, I can run the code on the embedded target which stores the coverage data on the host.

Outline

Collecting coverage metrics is an essential part of the testing phase. While collecting coverage on a host application is rather simple, it is more challenging with an embedded target. In this article I show I’m doing this with VS Code, GNU gcc and gcov on a NXP LPC55S16 embedded target.

I have published the project used in this article on GitHub.

Requirements

I’m using VS Code 1.84.2 with the NXP-LPC55S16-EVK board. If you follow the instructions below you should be able to collect coverage information for any other board.

Toolchain

The important part is that you need a toolchain and library which has implemented the gcov library. Unfortunately, it seems that the original ARM builds of the GNU toolchain after version 10 have dropped that support. GNU ARM Embedded Toolchain v12.2.1-1.2 in the XPack distribution has everything included, so I recommend using that toolchain from here: https://github.com/xpack-dev-tools/arm-none-eabi-gcc-xpack/releases/tag/v12.2.1-1.2. Other tool chains might work too.

💡 Make sure you copy the arm-none-eabi-gcov and name it gcov, as host tools will need it with that name.

Semihosting

The other aspect is file I/O semihosting which is required to write the coverage data to the host. Here again several bundled tool chains or SDK fail with this. If using the RP2040, check out my article here: Implementing File I/O Semihosting for the RP2040 and VS Code. Here again I recommend the XPack v12.2.1-1.2 toolchain mentioned above, as it comes with everything needed.

Debug Probe

Last but not least: the debug probe. You will need a debug probe which is supported by VS-Code, for example the NXP MCU-Link (CMSIS-DAP/OpenOCD) and a SEGGER J-Link EDU.

MCU-Link debugging the LPC845-BRK Board
MCU-Link debug probe

In the next sections I explain what is needed to run gcov and related tools with VS Code, with the example provided on GitHub.

gcov Directory

To make using gcov easier, I have added a folder named ‘gcov’ with support and test files to the project:

gcov directory

That CMake folder is added in the main CMakeLists.txt as below:

add_subdirectory(./gcov         gcov)

I’m using the NewLib standard library. Depending on your GNU library version, you might have a missing _sbrk() implementation. An implementation of it is provided inside gcov_support.c

Semihosting with rdimon

I need file I/O semihosting, so I had to create an add a custom rdimon library (see Implementing File I/O Semihosting for the RP2040 and VS Code). It gets added to the list of CMake directories in the main CMakeLists.txt:

add_subdirectory(${MCULIB_DIR}/rdimon   rdimon)

I have configured and enabled the library in IncludeMcuLibConfig.h:

/* ---------------------------------------------------------------------------------------*/
/* McuRdimon */
#define McuRdimon_CONFIG_IS_ENABLED       (1)       /* 1: RdiMon is enabled; 0: RdiMon is disabled*/
/* ---------------------------------------------------------------------------------------*/

Additionally, because the level of semihosting depends on the debug probe, I have to specify he debug probe I want to use, e.g. a SEGGER J-Link:

/* ---------------------------------------------------------------------------------------*/
/* McuSemihost */
#define McuSemihost_CONFIG_DEBUG_CONNECTION         McuSemihost_DEBUG_CONNECTION_SEGGER

If using the NXP MCU-Link CMSIS-DAP debug probe:

#define McuSemihost_CONFIG_DEBUG_CONNECTION  McuSemihost_DEBUG_CONNECTION_LINKSERVER

Linking Libraries

I have to link my application with the gcov (wrapper) library and the rdimon library: Below the list of libraries used:

target_link_libraries(
  ${EXECUTABLE}
  srcLib
  sdkLib
  McuLib
  rdimonLib # file I/O with semihosting
  gcovLib   # gcov wrapper library
  gcov      # GNU gcov library
)

Instrumenting Source Files

To instrument the source files, have to compile the source with the --coverage flag. I can do this for all files in a directory this way:

# Set coverage option only for some files, otherwise use add_compile_options(--coverage)
add_compile_options(--coverage) # all files

If I only want or need o instrument some files, I can use the following, where I list the source files:

set_source_files_properties(
  main.c
  PROPERTIES COMPILE_FLAGS --coverage
)

See the CMakeLists.txt in the src directory.

Platform Configuration

I like the idea to be able to turn coverage information on or off. One thing I have defined is a macro in platform.h which I can use to turn it on or off:

#define PL_CONFIG_USE_GCOV              (1 && McuRdimon_CONFIG_IS_ENABLED) /* if using gcov */

In platform.c I initialize the libraries accordingly. First I need the necessary header files:

#if McuRdimon_CONFIG_IS_ENABLED
  #include "rdimon/McuRdimon.h"
#endif
#if PL_CONFIG_USE_GCOV
  #include "gcov_support.h"
#endif

Then I initialize the libraries:

#if McuRdimon_CONFIG_IS_ENABLED
  McuRdimon_Init();
#endif
#if PL_CONFIG_USE_GCOV
  gcov_init();  /* initialize library */
#endif

Writing Coverage Data

The application can write the coverage data with the following call:

gcov_write_files();

In a FreeRTOS application, I can exit the scheduler with:

vPortEndScheduler();

and then write the code at the place after where I have started the scheduler previously:

  vTaskStartScheduler();
#if PL_CONFIG_USE_GCOV
  gcov_write_files();
#endif

With this, I can cleanly exit the scheduler and at the end of the application I write the coverage data files.

.gcno and .gcda Files

When compiling, the gcc compiler generates for the instrumented files a file with .gcno extension. You can observer this in the build folder:

GNU gcov instrumented files (.gcno)

Running the application with the debugger and writing the data with semihosting (see above) then adds the data files with .gcda extension:

gcov data files (.gdca)

It is possible to run the application multiple times, in multiple test runs. Then the data gets accumulated in the .gdca files.

VS Code

It is possible to see the coverage data in the sources using the VS Code ‘Gcov Viewer’ from Jacques Lucke:

VS Code Gcov Extension

The extension offers commands to reload the data and view the information:

VS Code Gcov Commands

With this, I see everything covered with green background color:

Coverage Data shown in VS Code

gcovr

One can use the gcov command to generate text reports. A better approach is to use the gcovr utility. Install it with

pip install gcovr

To generate a text report, run

gcovr .
gcovr text report

To generate a HTML report, use

gcovr --html-details -o ./cov_report/main.html

To simply calling the gcovr tool, I have created an entry in .vscode/tasks.json:

        {
            "type": "shell",
            "label": "gcovr",
            "command": "gcovr --html-details -o ./cov_report/main.html",
            "options": {
                "cwd": "${workspaceFolder}"
            },
            "group": {
                "kind": "build",
                "isDefault": false
            },
        },

This report can be opened with a web browser or directly in VS Code:

Coverage report in VS Code

Summary

Generating test coverage data can be challenging with an embedded target, file I/O semihosting is a working solution. File I/O semihosting can be intrusive, but because I’m using a direct semihosting and outside of the scheduler, things work out very well.

I hope this article gets you up and running with gcov and an embedded target.

Happy covering 🙂

Links

2 thoughts on “GNU Coverage (gcov) for an Embedded Target with VS Code

  1. Hi Erich! I’m impressed seeing you recommend the xPack arm-none-eabi-gcc binaries! There is also an xPack release of GCC 13.2.1. Any special reason for using 12.2.1? Perhaps it would also be useful to add the project in the list at the end.

    Like

    • Hi Liviu,
      I switched from the ARM toolchain to the xPack one because this was the only toolchain I have found which worked without issues. As noted, recent ARM libraries are not complete, e.g. implementing the gcov only with empty stubs. I have read somewhere a ticket where you have fixed something in the build for the libraries (but cannot find it right now, sorry). I have not switched from 12.2.1 because 12.2.1 works perfectly, and I did had the time to switch to a newer one. And most projects at the university are using 12.2.1 too, as there seems to be no compelling reason to move to another one.
      PS: link added (thanks, missed that one)

      Like

What do you think?

This site uses Akismet to reduce spam. Learn how your comment data is processed.