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

12 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

      • Will you please indicate the step used to get the

        1) generate: code and compiler and link flags?

        2) send data to host: what print function?

        3) process data on host: what gcov command
        did the data sent back need modifications?

        I have used xpack-arm-none-eabi-gcc-12.2.1-1.2 but the data sent back to the host is not usable by the host gcov and gcov-tool keep producing errors. Like mismatch on version, or not a gcov file, or stamp

        Example of data returned:

        nfcg23BhC:optextendsourceHomeSK-S7G2-masterbuildCMakeFiles/SK-S7G2_Simple.elf.dir/src/hal_entry.cpp.gcda adcgh23BhN[–hS.>bhÂVÃ7’¤€håùhz¼*É£˜>²»ÀN•…cïw

        Will you please clear step on what you did?

        Like

  2. Christopher Campbell <chris.addassa@gmail.com>

    AttachmentsWed, Jun 12, 7:42 PM (14 hours ago)

    to gcc-help, me

    Please provide instructions about how to use gcov to process gcov data returned from target when using

      __gcov_info_to_gcda (*info, filename, dump, allocate, arg);

    and

    __gcov_filename_to_gcfn (f, dump, arg);

    I have tried following the stand alone tutorial.  https://gcc.gnu.org/onlinedocs/gcc/Freestanding-Environments.html#Tutorial

    That tutorial does not give example about what happened when gcov data is sent back from a target.

    I am getting data returned from target but the gcov-tool keeps give error state below

    host: windows

    compiler:  C:Program Files (x86)Arm GNU Toolchain arm-none-eabi13.2 Rel1bin

    target: Renesas S7G2

    command: C:optextendsourceHomeSK-S7G2-masterbuild>C:”Program Files (x86)Arm GNU Toolchain arm-none-eabi13.2 Rel1binarm-none-eabi-gcov-tool.exe” merge-stream CMakeFilesSK-S7G2_Simple.elf.dirsrcoutput_gcc.txt     

    error:  CMakeFilesSK-S7G2_Simple.elf.dirsrcoutput_gcc.txt:incorrect gcov version 841614340 vs 1110651434

    I have provided the output from the target

    and the code used to generate the output.

    Please give some guidance from real embedded target.

    Example from host are not explaining the type of error.

    Like

    • What you get that way is the gcov data as it would be stored locally on a file. So store the binary data as it would be stored on a file and then process it with gcov or gcovr.
      Just keep in mind that using that way you will not get incremental coverage data.

      Like

      • Can you comment on the error

        error: CMakeFilesSK-S7G2_Simple.elf.dirsrcoutput_gcc.txt:incorrect gcov version 841614340 vs 1110651434.

        Like

        • Your gcov generator (compiler and library) is not of the same version as your gcov executable. Make sure that you are using the library, the compiler and gcov of the same GNU package and version.

          Like

        • – if you have a memory device on the target (e.g. SD card), then you can store it there, and then read the memory device on the host.
          – another way is send the data over USB or serial connection: this requires that you encode the binary data and have an application on the host reading and decoding it. This is as well known as ‘standalone gcov’
          – for devices with USB MSD implementation, you can store it in a virtual memory device and then offer that data through a virtual MSD file system
          – you can use the debugger to dump the whole RAM of the device and then post-process it
          – another way would be to use a J-Link with RTT and log the data file on the host

          Like

        • To add to the discussion: I have played a little bit with the standalone environment to produce coverage data. It is actually not that hard, except that you have to decide and match the encoding and decoding part. If you have a way to transmit binary data, than you don’t need the encoding part mentioned in the tutorial.

          Like

What do you think?

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