This tutorial shows how to use and collect coverage data using the GNU gcov tool. As board and hardaware I’m using the NXP i.MX RT1064 EVK:
While this tutorial uses this specific board, things are pretty generic and should be applicable for any other board or MCU.
Collecting coverage or test coverage data is very useful and tells which part of the code has been executed and how many time. This is especially useful to show how much of the code has been tested (or not). For details how this works using the gcov GNU utility and for other boards, see the links to my articles in the ‘Links’ section at the end of this tutorial, especially Tutorial: GNU Coverage with MCUXpresso IDE.
The project created and used is available on GitHub.
I’m using the SDK 2.10.0 from https://mcuxpresso.nxp.com with the version 11.4.1 of the eclipse-based MCUXpresso IDE.
I recommend to start using gcov with a small project. Use the menu File > New Project and then create a new project or import the Hello World project:
Next, make sure the project builds successfully and can be debugged.
I’m using the J-Link debug connection as this one supports fully the semihosting file I/O which is required for gcov data collection.
Add the coverage stubs folder and files to the project (you can copy it from my project on GitHub). Make sure the folder is not excluded from the build:
Make sure that the newlib library is used with semihosting:
The library needs to be initialized properly, which requires the symbol __init_array_start to be present. For this create (or copy) the file main_text.ldt into a folder named linkscripts.
From the main file, call the gcov stubs like this:
Semihosting File I/O
gcov uses file I/O through semihosting to write the data to the host. Check that your debug connection is able to sucessfully open and create a file on the host. For this there is a check routine present in the stub library:
Heap and Stack
File I/O uses lots of heap and stack space. Be sure that you allocate enough space: the more the better, at least trying out things. You still can reduce it later.
My usual approach is to give it as much as possible (e.g. >8k for stack, >8k for heap) and then check with the ‘Heap and stack usage’ view in the IDE:
How much is needed depends on the library used too, but because file I/O needs large set of data, the more is better. Newlib nano should use less RAM than newlib.
Instruct the linker to link with the gcov library using the -fprofile-arcs option:
Instrumenting to collect Coverage Information
To collect coverage, individual files/folders need to use the following (file/project!) specific option:
In return if you build the project, there shall be a file with extension .gcno created:
To write the coverage information to the host, call gcov_write() at the end:
Now execute the file with the debugger and write the data. This shall create files with the .gcda extension:
Then terminate the debug session.
If you should get an error like this:
libgcov profiling error:....gcda:overwriting an existing profile data with a different timestamp
it means that there is existing profiler information data with a different time stamp and previously generated. Make sure to delete any old or existing data (delete for example the ‘debug’ folder).
💡 in case of problems, I recommend to make a clean-clean with deleting the ‘debug’ folder.
To show the collected information, double-click on the generated data and press OK in the dialog:
This opens the ‘gcov’ view with the details. Double-clicking on an entry opens the corresponding source file which shows the coverage information with green/red color.
Congratulations, you successfully instrumented and collected coverage information :-).
Collecting GNU coverage with gcov is rather straightforward with the MCUXpresso IDE, as all the necessary plugins are included. All what I have to do is to add the necessary high-level steps, add a linker symbol plus add the necessary compiler and linker options.
I hope you find this tutorial useful. Please check the links below for more details and further information, or check my project(s) on GitHub.
Happy covering 🙂
- NXP MCUXpresso SDK: https://mcuxpresso.nxp.com
- NXP MCUXpresso IDE: https://www.nxp.com/mcuxpresso/ide/download
- Code Coverage for Embedded Target with Eclipse, gcc and gcov: https://mcuoneclipse.com/2014/12/26/code-coverage-for-embedded-target-with-eclipse-gcc-and-gcov/
- Code Coverage With Eclipse and gcov for Embedded System: https://www.youtube.com/watch?v=KjrLHykZkoo
- Adding GNU Coverage Tools to Eclipse: https://mcuoneclipse.com/2017/06/18/adding-gnu-coverage-tools-to-eclipse/
- Tutorial: GNU Coverage with MCUXpresso IDE: https://mcuoneclipse.com/2021/02/01/tutorial-gnu-coverage-with-mcuxpresso-ide/
- Project of this tutorial on GitHub: https://github.com/ErichStyger/mcuoneclipse/tree/master/Examples/MCUXpresso/i.MX%20RT1064_EVK/MIMXRT1064_gcov
As always very interesting – one question, how do you determine how much additional heap/stack that is required?
LikeLiked by 1 person
thanks :-). Good point! I admit I use a ‘trial&error’ approach for the heap and stack, with a check of that dedicated view in the IDE. I have updated the article with that information.
LikeLiked by 1 person
Is there a rule of thumb for values to start with?
I’m curious because I always find myself using up all the available resources. Not necessarily because of my largess, just because I want to select the minimum device for the application and SRAM, in embedded devices, is always a resource you’d like more of,
With using semihosting file I/O, I would start with about 2 kByte of RAM for the stack. For using gcov, the amoung of heap depends on the number of instrumented modules (or better: arcs). It starts with something around 4KByte for a single module instrumented, and only the ‘sky’ is the limit.
LikeLiked by 1 person