Code Coverage with gcov, launchpad tools and Eclipse Kinetis Design Studio V3.0.0

What makes Eclipse great: using open source tools there are a lot of tools and techniques available which usually are only provided for desktop development.

A while back I described how to do code coverage with Eclipse Kepler and the GNU ARM Embedded (launchpad) tools (see “Code Coverage for Embedded Target with Eclipse, gcc and gcov“). With Kinetis Design Studio out, time to do the same with that Eclipse distribution, especially as Freescale is now using the stock GNU ARM Embedded tools too.

Coverage with multiple Files

Coverage with multiple Files

In order not to repeat what I said in “Code Coverage for Embedded Target with Eclipse, gcc and gcov“, I describe here only the things or changes needed to generated coverage information with the Kinetis Design Studio v3.0.0.

Newlib-nano

Kinetis Design Studio is using the newlib-nano libraries by default which is a good thing. However, in contrast to the newlib library, the newlib-nano library is optimized for lower memory usage. Instead of writing the coverage data in one chunk, it writes it byte-by-byte.

For this, I have to delay dumping the data with the debugger, and I use a file buffer instead:

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

#if 0
  /* In case of the file is written in one piece (newlib), you can dump it now.
   * But if using newlib-nano, it writes to the file byte by byte, so dumping is postponed
   */
  /* construct gdb command string to write gcda file */
  UTIL1_strcpy(gdb_cmd, sizeof(gdb_cmd), (unsigned char*)"dump binary memory ");
  UTIL1_strcat(gdb_cmd, sizeof(gdb_cmd), COV_Buffer.fileName);
  UTIL1_strcat(gdb_cmd, sizeof(gdb_cmd), (unsigned char*)" 0x");
  UTIL1_strcatNum32Hex(gdb_cmd, sizeof(gdb_cmd), (uint32_t)ptr);
  UTIL1_strcat(gdb_cmd, sizeof(gdb_cmd), (unsigned char*)" 0x");
  UTIL1_strcatNum32Hex(gdb_cmd, sizeof(gdb_cmd), (uint32_t)(ptr+len));
#endif
  if (file==COV_FILE_HANDLE) {
      size = len;
      while(size>0) {
          if (COV_Buffer.bufPtr>=&COV_Buffer.buffer[COV_FILE_BUFFER_SIZE]) {
              /* buffer overflow! */
              for(;;);
          }
          *COV_Buffer.bufPtr++ = *ptr++;
          size--;
      }
      COV_Buffer.fileSize = COV_Buffer.bufPtr-&COV_Buffer.buffer[0];
  }
  return len; /* on success, return number of bytes written */
}

Writing the file buffer is delayed until the file is closed:

int _close(int file) {
  if (file==COV_FILE_HANDLE) {
      /* construct gdb command string to write .gcda file */
      UTIL1_strcpy(gdb_cmd, sizeof(gdb_cmd), (unsigned char*)"dump binary memory ");
      UTIL1_strcat(gdb_cmd, sizeof(gdb_cmd), COV_Buffer.fileName);
      UTIL1_strcat(gdb_cmd, sizeof(gdb_cmd), (unsigned char*)" 0x");
      UTIL1_strcatNum32Hex(gdb_cmd, sizeof(gdb_cmd), (uint32_t)&COV_Buffer.buffer[0]);
      UTIL1_strcat(gdb_cmd, sizeof(gdb_cmd), (unsigned char*)" 0x");
      UTIL1_strcatNum32Hex(gdb_cmd, sizeof(gdb_cmd), (uint32_t)(&COV_Buffer.buffer[COV_Buffer.fileSize]));
  }
  return 0; /* success closing file */
}

So set a breakpoint in the _close() routine and dump the files with the debugger.

The downside of that approach is that it uses a local buffer for the file content, defined in coverage_stubs.c:

#define COV_FILE_BUFFER_SIZE     0x5000
#define COV_FILE_HANDLE          17 /* valid file handle is >2 (which is STDERR_FILENO) */

static struct {
    const char *fileName;
    int fileSize;
    uint8_t *bufPtr;
    uint8_t buffer[COV_FILE_BUFFER_SIZE];
} COV_Buffer;

So make sure the buffer size is large enough for the coverage file content.

Problem with PATH

So far so good. But I faced the problem that the gcov views in Eclipse were not working correctly:

gcov problem

gcov problem

Timestamp information and information shown was clearly wrong. Checking the workspace .metadata/.log file had this in it:

!ENTRY org.eclipse.linuxtools.gcov.core 4 4 2015-05-31 17:27:49.143
!MESSAGE An error occured during analysis: unable to retrieve gcov data
!STACK 0
java.io.IOException
    at org.eclipse.linuxtools.internal.gcov.parser.CovManager.getGCDALocations(CovManager.java:390)
    at org.eclipse.linuxtools.internal.gcov.view.CovView.displayCovResults(CovView.java:182)
    at org.eclipse.linuxtools.internal.gcov.action.OpenGCAction.open(OpenGCAction.java:105)
    at org.eclipse.ui.internal.WorkbenchPage$29.run(WorkbenchPage.java:5215)
    at org.eclipse.swt.custom.BusyIndicator.showWhile(BusyIndicator.java:70)
    at org.eclipse.ui.internal.WorkbenchPage.openExternalEditor(WorkbenchPage.java:5206)
    at org.eclipse.ui.internal.WorkbenchPage.busyOpenEditor(WorkbenchPage.java:3202)
    at org.eclipse.ui.internal.WorkbenchPage.access$23(WorkbenchPage.java:3125)
    at org.eclipse.ui.internal.WorkbenchPage$9.run(WorkbenchPage.java:3107)
    at org.eclipse.swt.custom.BusyIndicator.showWhile(BusyIndicator.java:70)
    at org.eclipse.ui.internal.WorkbenchPage.openEditor(WorkbenchPage.java:3102)
    at org.eclipse.ui.internal.WorkbenchPage.openEditor(WorkbenchPage.java:3066)
    at org.eclipse.ui.internal.WorkbenchPage.openEditor(WorkbenchPage.java:3056)
    at org.eclipse.ui.ide.IDE.openEditor(IDE.java:541)
    at org.eclipse.ui.ide.IDE.openEditor(IDE.java:500)
    at org.eclipse.ui.actions.OpenFileAction.openFile(OpenFileAction.java:99)
    at org.eclipse.ui.actions.OpenSystemEditorAction.run(OpenSystemEditorAction.java:99)
    at org.eclipse.ui.actions.RetargetAction.run(RetargetAction.java:229)
    at org.eclipse.ui.navigator.CommonNavigatorManager$3.open(CommonNavigatorManager.java:185)
    at org.eclipse.ui.OpenAndLinkWithEditorHelper$InternalListener.open(OpenAndLinkWithEditorHelper.java:48)
    at org.eclipse.jface.viewers.StructuredViewer$2.run(StructuredViewer.java:853)
    at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:42)
    at org.eclipse.ui.internal.JFaceUtil$1.run(JFaceUtil.java:50)
    at org.eclipse.jface.util.SafeRunnable.run(SafeRunnable.java:178)
    at org.eclipse.jface.viewers.StructuredViewer.fireOpen(StructuredViewer.java:850)
    at org.eclipse.jface.viewers.StructuredViewer.handleOpen(StructuredViewer.java:1142)
    at org.eclipse.ui.navigator.CommonViewer.handleOpen(CommonViewer.java:462)
    at org.eclipse.jface.viewers.StructuredViewer$6.handleOpen(StructuredViewer.java:1249)
    at org.eclipse.jface.util.OpenStrategy.fireOpenEvent(OpenStrategy.java:278)
    at org.eclipse.jface.util.OpenStrategy.access$2(OpenStrategy.java:272)
    at org.eclipse.jface.util.OpenStrategy$1.handleEvent(OpenStrategy.java:313)
    at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:84)
    at org.eclipse.swt.widgets.Display.sendEvent(Display.java:4353)
    at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1061)
    at org.eclipse.swt.widgets.Display.runDeferredEvents(Display.java:4172)
    at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:3761)
    at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine$9.run(PartRenderingEngine.java:1151)
    at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:332)
    at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine.run(PartRenderingEngine.java:1032)
    at org.eclipse.e4.ui.internal.workbench.E4Workbench.createAndRunUI(E4Workbench.java:148)
    at org.eclipse.ui.internal.Workbench$5.run(Workbench.java:636)
    at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:332)
    at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:579)
    at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:150)
    at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:135)
    at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:196)
    at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:134)
    at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:104)
    at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:380)
    at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:235)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:648)
    at org.eclipse.equinox.launcher.Main.basicRun(Main.java:603)
    at org.eclipse.equinox.launcher.Main.run(Main.java:1465)

After digging and searching, I have found finally the issue: The Eclipse gcov viewer launches the gcov and addr2line utilities and expects it to be present in the PATH which was not the case in my environment.

From gcov installation help:

Gprof plugin depends on binutils (such as addr2line, c++filt and nm). Gprof can be used on any platform as soon as these binutils are in PATH. For example, you can use it on Windows with Cygwin.

Here is what I did:

  1. The following tools need to be present in PATH (at least based on my research):
    gcov
    addr2line
    nm
    strings
    c++filt
  2. Inside C:\Freescale\KDS_3.0.0\toolchain\bin, I copy arm-none-eabi-addr2line.exe and arm-none-eabi-gcov.exe and remove thearm-none-eabi from the copied file name

    Copied gcov and addr2line

    Copied gcov and addr2line

  3. Add C:\Freescale\KDS_3.0.0\toolchain\bin to your system PATH variable, so that gcov and addr2line are found.

💡 If you have somewhere gcov and addr2line present in your PATH, the gcov Eclipse view might still work. However, it is advised to use a matching gcov/addr2line to the version of GNU gcc used to instrument the code and collecting coverage information.

With gcov and addr2line found by Eclipse, the coverage views in Eclipse/KDS are working properly.

💡 Alternatively, instead using a global PATH setting, it would be possible to change the system path in Eclipse only (workspace setting), or launch the eclipse executable as part of setting that path.

Multiple Coverage Files

The current implementation rewrites the coverage files. If you want to combine multiple gcov runs, you would need to use the lcov tool to combine them (I have not tried that myself yet). Using multiple coverage files works 🙂 :

gcov in Eclipse Kinetis Design Studio

gcov in Eclipse Kinetis Design Studio

Same for bar charts:

Coverage Bar Charts

Coverage Bar Charts

And I love the way how the Eclipse Editor view shows what is covered (green) and what is not (red):

gcov Eclipse Editor View

gcov Eclipse Editor View

Summary

Generating coverage information with Kinetis Design Studio is very easy, especially as it is using now the GNU ARM Eclipse (launchpad) tools. Dealing with newlib-nano requires different buffer handling and is supported with the new coverage stubs. Right now I’m using GDB to dump the data files to the host. This approach is universal, but not ideal for automation. I already investigate how I can use the Segger J-Link serial debug connection to make things easier. I will post about that once I have everything sorted out :-).

I have posted an example project with the sources on GitHub here: https://github.com/ErichStyger/mcuoneclipse/tree/master/Examples/KDS/FRDM-K64F120M/FRDM-K64F_Coverage

Happy Covering 🙂

Links

14 thoughts on “Code Coverage with gcov, launchpad tools and Eclipse Kinetis Design Studio V3.0.0

  1. Thank you for this great tutorial. I don’t know how people can code without the ability to at least check coverage…

    I’ve recently taken over quite complex C-code of an unspecified communication protocol and doing a coverage analysis I just found out that 50% of those lines are practically useless. This made my work a whole lot easier.

    Like

  2. Pingback: Sneak Preview: Profiling Bare Metal Microcontroller Applications with GNU gprof | MCU on Eclipse

  3. Pingback: Tutorial: Using GNU Profiling (gprof) with ARM Cortex-M | MCU on Eclipse

  4. Pingback: A Raspberry Pi for $5! What are your decision factors? | MCU on Eclipse

  5. Hi Erich, this blog has been a tremendous help for me on a Kinetis Cortex-M0+ project. I have been banging my head against the wall trying to get gcov to work well with a test project for the MKL26Z4, to no avail. A major source of the problem is the reduced RAM (16kB). I am thinking about redirecting the coverage data out the UART by overloading _write() again. Would you have other suggestions for getting gcov working on the MKL26Z4? Thanks again for publishing your work!

    Like

    • Hi Jason,
      well, 16kBytes is not much. But I guess you have not all that 16KByte available. You could only instrument a portion of the code and then combine the coverage data?

      Like

  6. Pingback: Adding GNU Coverage Tools to Eclipse | MCU on Eclipse

  7. Pingback: GNU Code Coverage on Embedded Target with Eclipse Neon and ARM gcc 5 | MCU on Eclipse

  8. Pingback: MCUXpresso IDE V11.3.0 for 2021 | MCU on Eclipse

  9. Pingback: Tutorial: GNU Coverage with MCUXpresso IDE | MCU on Eclipse

What do you think?

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

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