Code Coverage with the ARM C/C++ Compiler

Code Coverage with the ARM C/C++ Compiler

Introduction

Developing for an embedded environment can often leave you juggling for space. Squish Coco is designed with such constraints in mind, making instrumentation and code coverage possible even on embedded environments.

Squish Coco separates the coverage data creation from the analysis, enabling users performing cross-compilation to host the compiler on a separate platform. The execution data is gathered on the target platform, while the analysis occurs on the desktop platform and can be viewed using the CoverageBrowser.

The detection of sequential blocks creates a more compact instrumentation than the traditional line coverage approach. Additional size reductions are implemented using command-line parameters to disable the condition, decision and statement coverage analysis. Because Squish Coco instruments the source code utilizing the user’s toolchain, both debug and release, or optimized code can be used. The only requirement for the target system is the malloc() function. Filesystems and Ethernet devices are not necessary.

This article demonstrates how to instrument a small ARM application that reads data from an AD device on a simulator.

Getting started with ARM Keil μVision

To setup the playground you’ll need the ARM Keil μVision toolchain and Squish Coco v3.0.1 Beta. The new Squish Coco release already includes the Blinky example.

Setup the Squish Coco toolchain

Squish Coco provides compiler and linker wrappers which replace the ARM command-line tools. It behaves identical to the original toolchain except when activated with the command-line switch --cs-on.

Specify the path of the Squish Coco toolchain in ARM Keil uVision:
  1. Open the ARM Keil μVision IDE
  2. Click Project > Manage and select Components, Environments, Books…
  3. Select Folders/Extensions, click the ARMCC Folder: box, and replace .ARMCCbin with .SquishCocobin.
Activate the code coverage analysis for the compiler and linker:
  1. Click Project and select Option for Target…
  2. Select the C/C++ tab
  3. Click the Misc Controls box and enter <strong–cs-on.
  4. Select the Linker tab
  5. Click the Misc Controls box and enter –cs-on.

Generate an execution report

For desktop applications, the default instrumentation behavior is to save an execution report to a file when the application closes. This does not apply to embedded devices because most embedded applications never close and also lack a file system. This is why it’s necessary necessary to define how and when the execution reports are generated.

Report generation trigger

For a real application it is necessary to define a trigger to save the execution report. Triggers can consist of keyboard events, digital input signals, CAN-Bus messages, etc. The trigger handler contains the following code snippet to save the code coverage information:
#ifdef __COVERAGESCANNER__
__coveragescanner_save();
__coveragescanner_clear();
#endif

The __COVERAGESCANNER__ definition is automatically defined in instrumented code. The execution report saves using __coveragescanner_save(), while the __coveragescanner_clear() resets all execution counters paving the way for a clean report in the future.

Communication link

The function __coveragescanner_set_custom_io() setups up the communication link used to transmit the coverage information. Typically three functions are required (fopen(), fputs() and fclose()) allowing an UART or CAN identifier to upload the data. The use of more sophisticated protocols, such as SFTP for networking devices, is also available.

In the simulated device, you’ll use the ITMLOG channel, a virtual serial device between an application and the ARM simulator. Only the fputs() function needs to be implemented because this channel does not require initialization or being closed:

#if defined( __COVERAGESCANNER__ )
static int csfputs(const char *s, void *stream) {
#define COVERAGESCANNER_ITM_CHANNEL 10
  if (
       (ITM->TCR & ITM_TCR_ITMENA_Msk)  /* ITM enabled */
            &&
       (ITM->TER & (1UL << COVERAGESCANNER_ITM_CHANNEL) )  /* ITM Port  enabled */
      )
    {
       while ( *s != '' )
       {
          while (ITM->PORT[COVERAGESCANNER_ITM_CHANNEL].u32 == 0);
          ITM->PORT[COVERAGESCANNER_ITM_CHANNEL].u8 = (uint8_t) *s;
          s++;
       }
    }
  return 0;
}
static void *csfopenappend(const char *path) { return (void*)1;}
static int csfclose(void *fp) { return 1; }
#endif

int main (void) {
.....
#ifdef __COVERAGESCANNER__
  __coveragescanner_set_custom_io(
          NULL,
          csfputs,
          csfopenappend,
          NULL,
          NULL,
          csfclose,
          NULL );
#endif

Test and inspect the coverage report

Simulation
  1. Setup the ITMLOG output file in the simulator command prompt prior to starting the simulation to capture the output of the channel to a file:
    ITMLOG 10 > c:coverage.csexe
  2. Start the simulation and wait approximately thirty seconds for it to complete.
  3. A message displays in the debug output window upon completion, at which time the simulation can be stopped.

Inspect the code coverage

  1. In CoverageBrowser open Blinky.axf.csmes and load the coverage.csexe execution report
  2. The executed and un-executed Blinky example code display.

Demonstration Video

Click to view a video of this tutorial:
ARM Keil uVision - Video Tutorial

Click here to request the latest version of Squish Coco containing this example